##// END OF EJS Templates
sshpeer: enable+fix warning about sshpeers not being closed explicitly...
Valentin Gatien-Baron -
r47804:a4c19a16 default
parent child Browse files
Show More
@@ -1,1376 +1,1382 b''
1 # Infinite push
1 # Infinite push
2 #
2 #
3 # Copyright 2016 Facebook, Inc.
3 # Copyright 2016 Facebook, Inc.
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 """ store some pushes in a remote blob store on the server (EXPERIMENTAL)
7 """ store some pushes in a remote blob store on the server (EXPERIMENTAL)
8
8
9 IMPORTANT: if you use this extension, please contact
9 IMPORTANT: if you use this extension, please contact
10 mercurial-devel@mercurial-scm.org ASAP. This extension is believed to
10 mercurial-devel@mercurial-scm.org ASAP. This extension is believed to
11 be unused and barring learning of users of this functionality, we will
11 be unused and barring learning of users of this functionality, we will
12 delete this code at the end of 2020.
12 delete this code at the end of 2020.
13
13
14 [infinitepush]
14 [infinitepush]
15 # Server-side and client-side option. Pattern of the infinitepush bookmark
15 # Server-side and client-side option. Pattern of the infinitepush bookmark
16 branchpattern = PATTERN
16 branchpattern = PATTERN
17
17
18 # Server or client
18 # Server or client
19 server = False
19 server = False
20
20
21 # Server-side option. Possible values: 'disk' or 'sql'. Fails if not set
21 # Server-side option. Possible values: 'disk' or 'sql'. Fails if not set
22 indextype = disk
22 indextype = disk
23
23
24 # Server-side option. Used only if indextype=sql.
24 # Server-side option. Used only if indextype=sql.
25 # Format: 'IP:PORT:DB_NAME:USER:PASSWORD'
25 # Format: 'IP:PORT:DB_NAME:USER:PASSWORD'
26 sqlhost = IP:PORT:DB_NAME:USER:PASSWORD
26 sqlhost = IP:PORT:DB_NAME:USER:PASSWORD
27
27
28 # Server-side option. Used only if indextype=disk.
28 # Server-side option. Used only if indextype=disk.
29 # Filesystem path to the index store
29 # Filesystem path to the index store
30 indexpath = PATH
30 indexpath = PATH
31
31
32 # Server-side option. Possible values: 'disk' or 'external'
32 # Server-side option. Possible values: 'disk' or 'external'
33 # Fails if not set
33 # Fails if not set
34 storetype = disk
34 storetype = disk
35
35
36 # Server-side option.
36 # Server-side option.
37 # Path to the binary that will save bundle to the bundlestore
37 # Path to the binary that will save bundle to the bundlestore
38 # Formatted cmd line will be passed to it (see `put_args`)
38 # Formatted cmd line will be passed to it (see `put_args`)
39 put_binary = put
39 put_binary = put
40
40
41 # Serser-side option. Used only if storetype=external.
41 # Serser-side option. Used only if storetype=external.
42 # Format cmd-line string for put binary. Placeholder: {filename}
42 # Format cmd-line string for put binary. Placeholder: {filename}
43 put_args = {filename}
43 put_args = {filename}
44
44
45 # Server-side option.
45 # Server-side option.
46 # Path to the binary that get bundle from the bundlestore.
46 # Path to the binary that get bundle from the bundlestore.
47 # Formatted cmd line will be passed to it (see `get_args`)
47 # Formatted cmd line will be passed to it (see `get_args`)
48 get_binary = get
48 get_binary = get
49
49
50 # Serser-side option. Used only if storetype=external.
50 # Serser-side option. Used only if storetype=external.
51 # Format cmd-line string for get binary. Placeholders: {filename} {handle}
51 # Format cmd-line string for get binary. Placeholders: {filename} {handle}
52 get_args = {filename} {handle}
52 get_args = {filename} {handle}
53
53
54 # Server-side option
54 # Server-side option
55 logfile = FIlE
55 logfile = FIlE
56
56
57 # Server-side option
57 # Server-side option
58 loglevel = DEBUG
58 loglevel = DEBUG
59
59
60 # Server-side option. Used only if indextype=sql.
60 # Server-side option. Used only if indextype=sql.
61 # Sets mysql wait_timeout option.
61 # Sets mysql wait_timeout option.
62 waittimeout = 300
62 waittimeout = 300
63
63
64 # Server-side option. Used only if indextype=sql.
64 # Server-side option. Used only if indextype=sql.
65 # Sets mysql innodb_lock_wait_timeout option.
65 # Sets mysql innodb_lock_wait_timeout option.
66 locktimeout = 120
66 locktimeout = 120
67
67
68 # Server-side option. Used only if indextype=sql.
68 # Server-side option. Used only if indextype=sql.
69 # Name of the repository
69 # Name of the repository
70 reponame = ''
70 reponame = ''
71
71
72 # Client-side option. Used by --list-remote option. List of remote scratch
72 # Client-side option. Used by --list-remote option. List of remote scratch
73 # patterns to list if no patterns are specified.
73 # patterns to list if no patterns are specified.
74 defaultremotepatterns = ['*']
74 defaultremotepatterns = ['*']
75
75
76 # Instructs infinitepush to forward all received bundle2 parts to the
76 # Instructs infinitepush to forward all received bundle2 parts to the
77 # bundle for storage. Defaults to False.
77 # bundle for storage. Defaults to False.
78 storeallparts = True
78 storeallparts = True
79
79
80 # routes each incoming push to the bundlestore. defaults to False
80 # routes each incoming push to the bundlestore. defaults to False
81 pushtobundlestore = True
81 pushtobundlestore = True
82
82
83 [remotenames]
83 [remotenames]
84 # Client-side option
84 # Client-side option
85 # This option should be set only if remotenames extension is enabled.
85 # This option should be set only if remotenames extension is enabled.
86 # Whether remote bookmarks are tracked by remotenames extension.
86 # Whether remote bookmarks are tracked by remotenames extension.
87 bookmarks = True
87 bookmarks = True
88 """
88 """
89
89
90 from __future__ import absolute_import
90 from __future__ import absolute_import
91
91
92 import collections
92 import collections
93 import contextlib
93 import contextlib
94 import errno
94 import errno
95 import functools
95 import functools
96 import logging
96 import logging
97 import os
97 import os
98 import random
98 import random
99 import re
99 import re
100 import socket
100 import socket
101 import subprocess
101 import subprocess
102 import time
102 import time
103
103
104 from mercurial.node import (
104 from mercurial.node import (
105 bin,
105 bin,
106 hex,
106 hex,
107 )
107 )
108
108
109 from mercurial.i18n import _
109 from mercurial.i18n import _
110
110
111 from mercurial.pycompat import (
111 from mercurial.pycompat import (
112 getattr,
112 getattr,
113 open,
113 open,
114 )
114 )
115
115
116 from mercurial.utils import (
116 from mercurial.utils import (
117 procutil,
117 procutil,
118 stringutil,
118 stringutil,
119 )
119 )
120
120
121 from mercurial import (
121 from mercurial import (
122 bundle2,
122 bundle2,
123 changegroup,
123 changegroup,
124 commands,
124 commands,
125 discovery,
125 discovery,
126 encoding,
126 encoding,
127 error,
127 error,
128 exchange,
128 exchange,
129 extensions,
129 extensions,
130 hg,
130 hg,
131 localrepo,
131 localrepo,
132 phases,
132 phases,
133 pushkey,
133 pushkey,
134 pycompat,
134 pycompat,
135 registrar,
135 registrar,
136 util,
136 util,
137 wireprototypes,
137 wireprototypes,
138 wireprotov1peer,
138 wireprotov1peer,
139 wireprotov1server,
139 wireprotov1server,
140 )
140 )
141
141
142 from . import (
142 from . import (
143 bundleparts,
143 bundleparts,
144 common,
144 common,
145 )
145 )
146
146
147 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
147 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
148 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
148 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
149 # be specifying the version(s) of Mercurial they are tested with, or
149 # be specifying the version(s) of Mercurial they are tested with, or
150 # leave the attribute unspecified.
150 # leave the attribute unspecified.
151 testedwith = b'ships-with-hg-core'
151 testedwith = b'ships-with-hg-core'
152
152
153 configtable = {}
153 configtable = {}
154 configitem = registrar.configitem(configtable)
154 configitem = registrar.configitem(configtable)
155
155
156 configitem(
156 configitem(
157 b'infinitepush',
157 b'infinitepush',
158 b'server',
158 b'server',
159 default=False,
159 default=False,
160 )
160 )
161 configitem(
161 configitem(
162 b'infinitepush',
162 b'infinitepush',
163 b'storetype',
163 b'storetype',
164 default=b'',
164 default=b'',
165 )
165 )
166 configitem(
166 configitem(
167 b'infinitepush',
167 b'infinitepush',
168 b'indextype',
168 b'indextype',
169 default=b'',
169 default=b'',
170 )
170 )
171 configitem(
171 configitem(
172 b'infinitepush',
172 b'infinitepush',
173 b'indexpath',
173 b'indexpath',
174 default=b'',
174 default=b'',
175 )
175 )
176 configitem(
176 configitem(
177 b'infinitepush',
177 b'infinitepush',
178 b'storeallparts',
178 b'storeallparts',
179 default=False,
179 default=False,
180 )
180 )
181 configitem(
181 configitem(
182 b'infinitepush',
182 b'infinitepush',
183 b'reponame',
183 b'reponame',
184 default=b'',
184 default=b'',
185 )
185 )
186 configitem(
186 configitem(
187 b'scratchbranch',
187 b'scratchbranch',
188 b'storepath',
188 b'storepath',
189 default=b'',
189 default=b'',
190 )
190 )
191 configitem(
191 configitem(
192 b'infinitepush',
192 b'infinitepush',
193 b'branchpattern',
193 b'branchpattern',
194 default=b'',
194 default=b'',
195 )
195 )
196 configitem(
196 configitem(
197 b'infinitepush',
197 b'infinitepush',
198 b'pushtobundlestore',
198 b'pushtobundlestore',
199 default=False,
199 default=False,
200 )
200 )
201 configitem(
201 configitem(
202 b'experimental',
202 b'experimental',
203 b'server-bundlestore-bookmark',
203 b'server-bundlestore-bookmark',
204 default=b'',
204 default=b'',
205 )
205 )
206 configitem(
206 configitem(
207 b'experimental',
207 b'experimental',
208 b'infinitepush-scratchpush',
208 b'infinitepush-scratchpush',
209 default=False,
209 default=False,
210 )
210 )
211
211
212 experimental = b'experimental'
212 experimental = b'experimental'
213 configbookmark = b'server-bundlestore-bookmark'
213 configbookmark = b'server-bundlestore-bookmark'
214 configscratchpush = b'infinitepush-scratchpush'
214 configscratchpush = b'infinitepush-scratchpush'
215
215
216 scratchbranchparttype = bundleparts.scratchbranchparttype
216 scratchbranchparttype = bundleparts.scratchbranchparttype
217 revsetpredicate = registrar.revsetpredicate()
217 revsetpredicate = registrar.revsetpredicate()
218 templatekeyword = registrar.templatekeyword()
218 templatekeyword = registrar.templatekeyword()
219 _scratchbranchmatcher = lambda x: False
219 _scratchbranchmatcher = lambda x: False
220 _maybehash = re.compile('^[a-f0-9]+$').search
220 _maybehash = re.compile('^[a-f0-9]+$').search
221
221
222
222
223 def _buildexternalbundlestore(ui):
223 def _buildexternalbundlestore(ui):
224 put_args = ui.configlist(b'infinitepush', b'put_args', [])
224 put_args = ui.configlist(b'infinitepush', b'put_args', [])
225 put_binary = ui.config(b'infinitepush', b'put_binary')
225 put_binary = ui.config(b'infinitepush', b'put_binary')
226 if not put_binary:
226 if not put_binary:
227 raise error.Abort(b'put binary is not specified')
227 raise error.Abort(b'put binary is not specified')
228 get_args = ui.configlist(b'infinitepush', b'get_args', [])
228 get_args = ui.configlist(b'infinitepush', b'get_args', [])
229 get_binary = ui.config(b'infinitepush', b'get_binary')
229 get_binary = ui.config(b'infinitepush', b'get_binary')
230 if not get_binary:
230 if not get_binary:
231 raise error.Abort(b'get binary is not specified')
231 raise error.Abort(b'get binary is not specified')
232 from . import store
232 from . import store
233
233
234 return store.externalbundlestore(put_binary, put_args, get_binary, get_args)
234 return store.externalbundlestore(put_binary, put_args, get_binary, get_args)
235
235
236
236
237 def _buildsqlindex(ui):
237 def _buildsqlindex(ui):
238 sqlhost = ui.config(b'infinitepush', b'sqlhost')
238 sqlhost = ui.config(b'infinitepush', b'sqlhost')
239 if not sqlhost:
239 if not sqlhost:
240 raise error.Abort(_(b'please set infinitepush.sqlhost'))
240 raise error.Abort(_(b'please set infinitepush.sqlhost'))
241 host, port, db, user, password = sqlhost.split(b':')
241 host, port, db, user, password = sqlhost.split(b':')
242 reponame = ui.config(b'infinitepush', b'reponame')
242 reponame = ui.config(b'infinitepush', b'reponame')
243 if not reponame:
243 if not reponame:
244 raise error.Abort(_(b'please set infinitepush.reponame'))
244 raise error.Abort(_(b'please set infinitepush.reponame'))
245
245
246 logfile = ui.config(b'infinitepush', b'logfile', b'')
246 logfile = ui.config(b'infinitepush', b'logfile', b'')
247 waittimeout = ui.configint(b'infinitepush', b'waittimeout', 300)
247 waittimeout = ui.configint(b'infinitepush', b'waittimeout', 300)
248 locktimeout = ui.configint(b'infinitepush', b'locktimeout', 120)
248 locktimeout = ui.configint(b'infinitepush', b'locktimeout', 120)
249 from . import sqlindexapi
249 from . import sqlindexapi
250
250
251 return sqlindexapi.sqlindexapi(
251 return sqlindexapi.sqlindexapi(
252 reponame,
252 reponame,
253 host,
253 host,
254 port,
254 port,
255 db,
255 db,
256 user,
256 user,
257 password,
257 password,
258 logfile,
258 logfile,
259 _getloglevel(ui),
259 _getloglevel(ui),
260 waittimeout=waittimeout,
260 waittimeout=waittimeout,
261 locktimeout=locktimeout,
261 locktimeout=locktimeout,
262 )
262 )
263
263
264
264
265 def _getloglevel(ui):
265 def _getloglevel(ui):
266 loglevel = ui.config(b'infinitepush', b'loglevel', b'DEBUG')
266 loglevel = ui.config(b'infinitepush', b'loglevel', b'DEBUG')
267 numeric_loglevel = getattr(logging, loglevel.upper(), None)
267 numeric_loglevel = getattr(logging, loglevel.upper(), None)
268 if not isinstance(numeric_loglevel, int):
268 if not isinstance(numeric_loglevel, int):
269 raise error.Abort(_(b'invalid log level %s') % loglevel)
269 raise error.Abort(_(b'invalid log level %s') % loglevel)
270 return numeric_loglevel
270 return numeric_loglevel
271
271
272
272
273 def _tryhoist(ui, remotebookmark):
273 def _tryhoist(ui, remotebookmark):
274 """returns a bookmarks with hoisted part removed
274 """returns a bookmarks with hoisted part removed
275
275
276 Remotenames extension has a 'hoist' config that allows to use remote
276 Remotenames extension has a 'hoist' config that allows to use remote
277 bookmarks without specifying remote path. For example, 'hg update master'
277 bookmarks without specifying remote path. For example, 'hg update master'
278 works as well as 'hg update remote/master'. We want to allow the same in
278 works as well as 'hg update remote/master'. We want to allow the same in
279 infinitepush.
279 infinitepush.
280 """
280 """
281
281
282 if common.isremotebooksenabled(ui):
282 if common.isremotebooksenabled(ui):
283 hoist = ui.config(b'remotenames', b'hoistedpeer') + b'/'
283 hoist = ui.config(b'remotenames', b'hoistedpeer') + b'/'
284 if remotebookmark.startswith(hoist):
284 if remotebookmark.startswith(hoist):
285 return remotebookmark[len(hoist) :]
285 return remotebookmark[len(hoist) :]
286 return remotebookmark
286 return remotebookmark
287
287
288
288
289 class bundlestore(object):
289 class bundlestore(object):
290 def __init__(self, repo):
290 def __init__(self, repo):
291 self._repo = repo
291 self._repo = repo
292 storetype = self._repo.ui.config(b'infinitepush', b'storetype')
292 storetype = self._repo.ui.config(b'infinitepush', b'storetype')
293 if storetype == b'disk':
293 if storetype == b'disk':
294 from . import store
294 from . import store
295
295
296 self.store = store.filebundlestore(self._repo.ui, self._repo)
296 self.store = store.filebundlestore(self._repo.ui, self._repo)
297 elif storetype == b'external':
297 elif storetype == b'external':
298 self.store = _buildexternalbundlestore(self._repo.ui)
298 self.store = _buildexternalbundlestore(self._repo.ui)
299 else:
299 else:
300 raise error.Abort(
300 raise error.Abort(
301 _(b'unknown infinitepush store type specified %s') % storetype
301 _(b'unknown infinitepush store type specified %s') % storetype
302 )
302 )
303
303
304 indextype = self._repo.ui.config(b'infinitepush', b'indextype')
304 indextype = self._repo.ui.config(b'infinitepush', b'indextype')
305 if indextype == b'disk':
305 if indextype == b'disk':
306 from . import fileindexapi
306 from . import fileindexapi
307
307
308 self.index = fileindexapi.fileindexapi(self._repo)
308 self.index = fileindexapi.fileindexapi(self._repo)
309 elif indextype == b'sql':
309 elif indextype == b'sql':
310 self.index = _buildsqlindex(self._repo.ui)
310 self.index = _buildsqlindex(self._repo.ui)
311 else:
311 else:
312 raise error.Abort(
312 raise error.Abort(
313 _(b'unknown infinitepush index type specified %s') % indextype
313 _(b'unknown infinitepush index type specified %s') % indextype
314 )
314 )
315
315
316
316
317 def _isserver(ui):
317 def _isserver(ui):
318 return ui.configbool(b'infinitepush', b'server')
318 return ui.configbool(b'infinitepush', b'server')
319
319
320
320
321 def reposetup(ui, repo):
321 def reposetup(ui, repo):
322 if _isserver(ui) and repo.local():
322 if _isserver(ui) and repo.local():
323 repo.bundlestore = bundlestore(repo)
323 repo.bundlestore = bundlestore(repo)
324
324
325
325
326 def extsetup(ui):
326 def extsetup(ui):
327 commonsetup(ui)
327 commonsetup(ui)
328 if _isserver(ui):
328 if _isserver(ui):
329 serverextsetup(ui)
329 serverextsetup(ui)
330 else:
330 else:
331 clientextsetup(ui)
331 clientextsetup(ui)
332
332
333
333
334 def commonsetup(ui):
334 def commonsetup(ui):
335 wireprotov1server.commands[b'listkeyspatterns'] = (
335 wireprotov1server.commands[b'listkeyspatterns'] = (
336 wireprotolistkeyspatterns,
336 wireprotolistkeyspatterns,
337 b'namespace patterns',
337 b'namespace patterns',
338 )
338 )
339 scratchbranchpat = ui.config(b'infinitepush', b'branchpattern')
339 scratchbranchpat = ui.config(b'infinitepush', b'branchpattern')
340 if scratchbranchpat:
340 if scratchbranchpat:
341 global _scratchbranchmatcher
341 global _scratchbranchmatcher
342 kind, pat, _scratchbranchmatcher = stringutil.stringmatcher(
342 kind, pat, _scratchbranchmatcher = stringutil.stringmatcher(
343 scratchbranchpat
343 scratchbranchpat
344 )
344 )
345
345
346
346
347 def serverextsetup(ui):
347 def serverextsetup(ui):
348 origpushkeyhandler = bundle2.parthandlermapping[b'pushkey']
348 origpushkeyhandler = bundle2.parthandlermapping[b'pushkey']
349
349
350 def newpushkeyhandler(*args, **kwargs):
350 def newpushkeyhandler(*args, **kwargs):
351 bundle2pushkey(origpushkeyhandler, *args, **kwargs)
351 bundle2pushkey(origpushkeyhandler, *args, **kwargs)
352
352
353 newpushkeyhandler.params = origpushkeyhandler.params
353 newpushkeyhandler.params = origpushkeyhandler.params
354 bundle2.parthandlermapping[b'pushkey'] = newpushkeyhandler
354 bundle2.parthandlermapping[b'pushkey'] = newpushkeyhandler
355
355
356 orighandlephasehandler = bundle2.parthandlermapping[b'phase-heads']
356 orighandlephasehandler = bundle2.parthandlermapping[b'phase-heads']
357 newphaseheadshandler = lambda *args, **kwargs: bundle2handlephases(
357 newphaseheadshandler = lambda *args, **kwargs: bundle2handlephases(
358 orighandlephasehandler, *args, **kwargs
358 orighandlephasehandler, *args, **kwargs
359 )
359 )
360 newphaseheadshandler.params = orighandlephasehandler.params
360 newphaseheadshandler.params = orighandlephasehandler.params
361 bundle2.parthandlermapping[b'phase-heads'] = newphaseheadshandler
361 bundle2.parthandlermapping[b'phase-heads'] = newphaseheadshandler
362
362
363 extensions.wrapfunction(
363 extensions.wrapfunction(
364 localrepo.localrepository, b'listkeys', localrepolistkeys
364 localrepo.localrepository, b'listkeys', localrepolistkeys
365 )
365 )
366 wireprotov1server.commands[b'lookup'] = (
366 wireprotov1server.commands[b'lookup'] = (
367 _lookupwrap(wireprotov1server.commands[b'lookup'][0]),
367 _lookupwrap(wireprotov1server.commands[b'lookup'][0]),
368 b'key',
368 b'key',
369 )
369 )
370 extensions.wrapfunction(exchange, b'getbundlechunks', getbundlechunks)
370 extensions.wrapfunction(exchange, b'getbundlechunks', getbundlechunks)
371
371
372 extensions.wrapfunction(bundle2, b'processparts', processparts)
372 extensions.wrapfunction(bundle2, b'processparts', processparts)
373
373
374
374
375 def clientextsetup(ui):
375 def clientextsetup(ui):
376 entry = extensions.wrapcommand(commands.table, b'push', _push)
376 entry = extensions.wrapcommand(commands.table, b'push', _push)
377
377
378 entry[1].append(
378 entry[1].append(
379 (
379 (
380 b'',
380 b'',
381 b'bundle-store',
381 b'bundle-store',
382 None,
382 None,
383 _(b'force push to go to bundle store (EXPERIMENTAL)'),
383 _(b'force push to go to bundle store (EXPERIMENTAL)'),
384 )
384 )
385 )
385 )
386
386
387 extensions.wrapcommand(commands.table, b'pull', _pull)
387 extensions.wrapcommand(commands.table, b'pull', _pull)
388
388
389 extensions.wrapfunction(discovery, b'checkheads', _checkheads)
389 extensions.wrapfunction(discovery, b'checkheads', _checkheads)
390
390
391 wireprotov1peer.wirepeer.listkeyspatterns = listkeyspatterns
391 wireprotov1peer.wirepeer.listkeyspatterns = listkeyspatterns
392
392
393 partorder = exchange.b2partsgenorder
393 partorder = exchange.b2partsgenorder
394 index = partorder.index(b'changeset')
394 index = partorder.index(b'changeset')
395 partorder.insert(
395 partorder.insert(
396 index, partorder.pop(partorder.index(scratchbranchparttype))
396 index, partorder.pop(partorder.index(scratchbranchparttype))
397 )
397 )
398
398
399
399
400 def _checkheads(orig, pushop):
400 def _checkheads(orig, pushop):
401 if pushop.ui.configbool(experimental, configscratchpush, False):
401 if pushop.ui.configbool(experimental, configscratchpush, False):
402 return
402 return
403 return orig(pushop)
403 return orig(pushop)
404
404
405
405
406 def wireprotolistkeyspatterns(repo, proto, namespace, patterns):
406 def wireprotolistkeyspatterns(repo, proto, namespace, patterns):
407 patterns = wireprototypes.decodelist(patterns)
407 patterns = wireprototypes.decodelist(patterns)
408 d = pycompat.iteritems(repo.listkeys(encoding.tolocal(namespace), patterns))
408 d = pycompat.iteritems(repo.listkeys(encoding.tolocal(namespace), patterns))
409 return pushkey.encodekeys(d)
409 return pushkey.encodekeys(d)
410
410
411
411
412 def localrepolistkeys(orig, self, namespace, patterns=None):
412 def localrepolistkeys(orig, self, namespace, patterns=None):
413 if namespace == b'bookmarks' and patterns:
413 if namespace == b'bookmarks' and patterns:
414 index = self.bundlestore.index
414 index = self.bundlestore.index
415 results = {}
415 results = {}
416 bookmarks = orig(self, namespace)
416 bookmarks = orig(self, namespace)
417 for pattern in patterns:
417 for pattern in patterns:
418 results.update(index.getbookmarks(pattern))
418 results.update(index.getbookmarks(pattern))
419 if pattern.endswith(b'*'):
419 if pattern.endswith(b'*'):
420 pattern = b're:^' + pattern[:-1] + b'.*'
420 pattern = b're:^' + pattern[:-1] + b'.*'
421 kind, pat, matcher = stringutil.stringmatcher(pattern)
421 kind, pat, matcher = stringutil.stringmatcher(pattern)
422 for bookmark, node in pycompat.iteritems(bookmarks):
422 for bookmark, node in pycompat.iteritems(bookmarks):
423 if matcher(bookmark):
423 if matcher(bookmark):
424 results[bookmark] = node
424 results[bookmark] = node
425 return results
425 return results
426 else:
426 else:
427 return orig(self, namespace)
427 return orig(self, namespace)
428
428
429
429
430 @wireprotov1peer.batchable
430 @wireprotov1peer.batchable
431 def listkeyspatterns(self, namespace, patterns):
431 def listkeyspatterns(self, namespace, patterns):
432 if not self.capable(b'pushkey'):
432 if not self.capable(b'pushkey'):
433 yield {}, None
433 yield {}, None
434 f = wireprotov1peer.future()
434 f = wireprotov1peer.future()
435 self.ui.debug(b'preparing listkeys for "%s"\n' % namespace)
435 self.ui.debug(b'preparing listkeys for "%s"\n' % namespace)
436 yield {
436 yield {
437 b'namespace': encoding.fromlocal(namespace),
437 b'namespace': encoding.fromlocal(namespace),
438 b'patterns': wireprototypes.encodelist(patterns),
438 b'patterns': wireprototypes.encodelist(patterns),
439 }, f
439 }, f
440 d = f.value
440 d = f.value
441 self.ui.debug(
441 self.ui.debug(
442 b'received listkey for "%s": %i bytes\n' % (namespace, len(d))
442 b'received listkey for "%s": %i bytes\n' % (namespace, len(d))
443 )
443 )
444 yield pushkey.decodekeys(d)
444 yield pushkey.decodekeys(d)
445
445
446
446
447 def _readbundlerevs(bundlerepo):
447 def _readbundlerevs(bundlerepo):
448 return list(bundlerepo.revs(b'bundle()'))
448 return list(bundlerepo.revs(b'bundle()'))
449
449
450
450
451 def _includefilelogstobundle(bundlecaps, bundlerepo, bundlerevs, ui):
451 def _includefilelogstobundle(bundlecaps, bundlerepo, bundlerevs, ui):
452 """Tells remotefilelog to include all changed files to the changegroup
452 """Tells remotefilelog to include all changed files to the changegroup
453
453
454 By default remotefilelog doesn't include file content to the changegroup.
454 By default remotefilelog doesn't include file content to the changegroup.
455 But we need to include it if we are fetching from bundlestore.
455 But we need to include it if we are fetching from bundlestore.
456 """
456 """
457 changedfiles = set()
457 changedfiles = set()
458 cl = bundlerepo.changelog
458 cl = bundlerepo.changelog
459 for r in bundlerevs:
459 for r in bundlerevs:
460 # [3] means changed files
460 # [3] means changed files
461 changedfiles.update(cl.read(r)[3])
461 changedfiles.update(cl.read(r)[3])
462 if not changedfiles:
462 if not changedfiles:
463 return bundlecaps
463 return bundlecaps
464
464
465 changedfiles = b'\0'.join(changedfiles)
465 changedfiles = b'\0'.join(changedfiles)
466 newcaps = []
466 newcaps = []
467 appended = False
467 appended = False
468 for cap in bundlecaps or []:
468 for cap in bundlecaps or []:
469 if cap.startswith(b'excludepattern='):
469 if cap.startswith(b'excludepattern='):
470 newcaps.append(b'\0'.join((cap, changedfiles)))
470 newcaps.append(b'\0'.join((cap, changedfiles)))
471 appended = True
471 appended = True
472 else:
472 else:
473 newcaps.append(cap)
473 newcaps.append(cap)
474 if not appended:
474 if not appended:
475 # Not found excludepattern cap. Just append it
475 # Not found excludepattern cap. Just append it
476 newcaps.append(b'excludepattern=' + changedfiles)
476 newcaps.append(b'excludepattern=' + changedfiles)
477
477
478 return newcaps
478 return newcaps
479
479
480
480
481 def _rebundle(bundlerepo, bundleroots, unknownhead):
481 def _rebundle(bundlerepo, bundleroots, unknownhead):
482 """
482 """
483 Bundle may include more revision then user requested. For example,
483 Bundle may include more revision then user requested. For example,
484 if user asks for revision but bundle also consists its descendants.
484 if user asks for revision but bundle also consists its descendants.
485 This function will filter out all revision that user is not requested.
485 This function will filter out all revision that user is not requested.
486 """
486 """
487 parts = []
487 parts = []
488
488
489 version = b'02'
489 version = b'02'
490 outgoing = discovery.outgoing(
490 outgoing = discovery.outgoing(
491 bundlerepo, commonheads=bundleroots, ancestorsof=[unknownhead]
491 bundlerepo, commonheads=bundleroots, ancestorsof=[unknownhead]
492 )
492 )
493 cgstream = changegroup.makestream(bundlerepo, outgoing, version, b'pull')
493 cgstream = changegroup.makestream(bundlerepo, outgoing, version, b'pull')
494 cgstream = util.chunkbuffer(cgstream).read()
494 cgstream = util.chunkbuffer(cgstream).read()
495 cgpart = bundle2.bundlepart(b'changegroup', data=cgstream)
495 cgpart = bundle2.bundlepart(b'changegroup', data=cgstream)
496 cgpart.addparam(b'version', version)
496 cgpart.addparam(b'version', version)
497 parts.append(cgpart)
497 parts.append(cgpart)
498
498
499 return parts
499 return parts
500
500
501
501
502 def _getbundleroots(oldrepo, bundlerepo, bundlerevs):
502 def _getbundleroots(oldrepo, bundlerepo, bundlerevs):
503 cl = bundlerepo.changelog
503 cl = bundlerepo.changelog
504 bundleroots = []
504 bundleroots = []
505 for rev in bundlerevs:
505 for rev in bundlerevs:
506 node = cl.node(rev)
506 node = cl.node(rev)
507 parents = cl.parents(node)
507 parents = cl.parents(node)
508 for parent in parents:
508 for parent in parents:
509 # include all revs that exist in the main repo
509 # include all revs that exist in the main repo
510 # to make sure that bundle may apply client-side
510 # to make sure that bundle may apply client-side
511 if parent in oldrepo:
511 if parent in oldrepo:
512 bundleroots.append(parent)
512 bundleroots.append(parent)
513 return bundleroots
513 return bundleroots
514
514
515
515
516 def _needsrebundling(head, bundlerepo):
516 def _needsrebundling(head, bundlerepo):
517 bundleheads = list(bundlerepo.revs(b'heads(bundle())'))
517 bundleheads = list(bundlerepo.revs(b'heads(bundle())'))
518 return not (
518 return not (
519 len(bundleheads) == 1 and bundlerepo[bundleheads[0]].node() == head
519 len(bundleheads) == 1 and bundlerepo[bundleheads[0]].node() == head
520 )
520 )
521
521
522
522
523 def _generateoutputparts(head, bundlerepo, bundleroots, bundlefile):
523 def _generateoutputparts(head, bundlerepo, bundleroots, bundlefile):
524 """generates bundle that will be send to the user
524 """generates bundle that will be send to the user
525
525
526 returns tuple with raw bundle string and bundle type
526 returns tuple with raw bundle string and bundle type
527 """
527 """
528 parts = []
528 parts = []
529 if not _needsrebundling(head, bundlerepo):
529 if not _needsrebundling(head, bundlerepo):
530 with util.posixfile(bundlefile, b"rb") as f:
530 with util.posixfile(bundlefile, b"rb") as f:
531 unbundler = exchange.readbundle(bundlerepo.ui, f, bundlefile)
531 unbundler = exchange.readbundle(bundlerepo.ui, f, bundlefile)
532 if isinstance(unbundler, changegroup.cg1unpacker):
532 if isinstance(unbundler, changegroup.cg1unpacker):
533 part = bundle2.bundlepart(
533 part = bundle2.bundlepart(
534 b'changegroup', data=unbundler._stream.read()
534 b'changegroup', data=unbundler._stream.read()
535 )
535 )
536 part.addparam(b'version', b'01')
536 part.addparam(b'version', b'01')
537 parts.append(part)
537 parts.append(part)
538 elif isinstance(unbundler, bundle2.unbundle20):
538 elif isinstance(unbundler, bundle2.unbundle20):
539 haschangegroup = False
539 haschangegroup = False
540 for part in unbundler.iterparts():
540 for part in unbundler.iterparts():
541 if part.type == b'changegroup':
541 if part.type == b'changegroup':
542 haschangegroup = True
542 haschangegroup = True
543 newpart = bundle2.bundlepart(part.type, data=part.read())
543 newpart = bundle2.bundlepart(part.type, data=part.read())
544 for key, value in pycompat.iteritems(part.params):
544 for key, value in pycompat.iteritems(part.params):
545 newpart.addparam(key, value)
545 newpart.addparam(key, value)
546 parts.append(newpart)
546 parts.append(newpart)
547
547
548 if not haschangegroup:
548 if not haschangegroup:
549 raise error.Abort(
549 raise error.Abort(
550 b'unexpected bundle without changegroup part, '
550 b'unexpected bundle without changegroup part, '
551 + b'head: %s' % hex(head),
551 + b'head: %s' % hex(head),
552 hint=b'report to administrator',
552 hint=b'report to administrator',
553 )
553 )
554 else:
554 else:
555 raise error.Abort(b'unknown bundle type')
555 raise error.Abort(b'unknown bundle type')
556 else:
556 else:
557 parts = _rebundle(bundlerepo, bundleroots, head)
557 parts = _rebundle(bundlerepo, bundleroots, head)
558
558
559 return parts
559 return parts
560
560
561
561
562 def getbundlechunks(orig, repo, source, heads=None, bundlecaps=None, **kwargs):
562 def getbundlechunks(orig, repo, source, heads=None, bundlecaps=None, **kwargs):
563 heads = heads or []
563 heads = heads or []
564 # newheads are parents of roots of scratch bundles that were requested
564 # newheads are parents of roots of scratch bundles that were requested
565 newphases = {}
565 newphases = {}
566 scratchbundles = []
566 scratchbundles = []
567 newheads = []
567 newheads = []
568 scratchheads = []
568 scratchheads = []
569 nodestobundle = {}
569 nodestobundle = {}
570 allbundlestocleanup = []
570 allbundlestocleanup = []
571 try:
571 try:
572 for head in heads:
572 for head in heads:
573 if not repo.changelog.index.has_node(head):
573 if not repo.changelog.index.has_node(head):
574 if head not in nodestobundle:
574 if head not in nodestobundle:
575 newbundlefile = common.downloadbundle(repo, head)
575 newbundlefile = common.downloadbundle(repo, head)
576 bundlepath = b"bundle:%s+%s" % (repo.root, newbundlefile)
576 bundlepath = b"bundle:%s+%s" % (repo.root, newbundlefile)
577 bundlerepo = hg.repository(repo.ui, bundlepath)
577 bundlerepo = hg.repository(repo.ui, bundlepath)
578
578
579 allbundlestocleanup.append((bundlerepo, newbundlefile))
579 allbundlestocleanup.append((bundlerepo, newbundlefile))
580 bundlerevs = set(_readbundlerevs(bundlerepo))
580 bundlerevs = set(_readbundlerevs(bundlerepo))
581 bundlecaps = _includefilelogstobundle(
581 bundlecaps = _includefilelogstobundle(
582 bundlecaps, bundlerepo, bundlerevs, repo.ui
582 bundlecaps, bundlerepo, bundlerevs, repo.ui
583 )
583 )
584 cl = bundlerepo.changelog
584 cl = bundlerepo.changelog
585 bundleroots = _getbundleroots(repo, bundlerepo, bundlerevs)
585 bundleroots = _getbundleroots(repo, bundlerepo, bundlerevs)
586 for rev in bundlerevs:
586 for rev in bundlerevs:
587 node = cl.node(rev)
587 node = cl.node(rev)
588 newphases[hex(node)] = str(phases.draft)
588 newphases[hex(node)] = str(phases.draft)
589 nodestobundle[node] = (
589 nodestobundle[node] = (
590 bundlerepo,
590 bundlerepo,
591 bundleroots,
591 bundleroots,
592 newbundlefile,
592 newbundlefile,
593 )
593 )
594
594
595 scratchbundles.append(
595 scratchbundles.append(
596 _generateoutputparts(head, *nodestobundle[head])
596 _generateoutputparts(head, *nodestobundle[head])
597 )
597 )
598 newheads.extend(bundleroots)
598 newheads.extend(bundleroots)
599 scratchheads.append(head)
599 scratchheads.append(head)
600 finally:
600 finally:
601 for bundlerepo, bundlefile in allbundlestocleanup:
601 for bundlerepo, bundlefile in allbundlestocleanup:
602 bundlerepo.close()
602 bundlerepo.close()
603 try:
603 try:
604 os.unlink(bundlefile)
604 os.unlink(bundlefile)
605 except (IOError, OSError):
605 except (IOError, OSError):
606 # if we can't cleanup the file then just ignore the error,
606 # if we can't cleanup the file then just ignore the error,
607 # no need to fail
607 # no need to fail
608 pass
608 pass
609
609
610 pullfrombundlestore = bool(scratchbundles)
610 pullfrombundlestore = bool(scratchbundles)
611 wrappedchangegrouppart = False
611 wrappedchangegrouppart = False
612 wrappedlistkeys = False
612 wrappedlistkeys = False
613 oldchangegrouppart = exchange.getbundle2partsmapping[b'changegroup']
613 oldchangegrouppart = exchange.getbundle2partsmapping[b'changegroup']
614 try:
614 try:
615
615
616 def _changegrouppart(bundler, *args, **kwargs):
616 def _changegrouppart(bundler, *args, **kwargs):
617 # Order is important here. First add non-scratch part
617 # Order is important here. First add non-scratch part
618 # and only then add parts with scratch bundles because
618 # and only then add parts with scratch bundles because
619 # non-scratch part contains parents of roots of scratch bundles.
619 # non-scratch part contains parents of roots of scratch bundles.
620 result = oldchangegrouppart(bundler, *args, **kwargs)
620 result = oldchangegrouppart(bundler, *args, **kwargs)
621 for bundle in scratchbundles:
621 for bundle in scratchbundles:
622 for part in bundle:
622 for part in bundle:
623 bundler.addpart(part)
623 bundler.addpart(part)
624 return result
624 return result
625
625
626 exchange.getbundle2partsmapping[b'changegroup'] = _changegrouppart
626 exchange.getbundle2partsmapping[b'changegroup'] = _changegrouppart
627 wrappedchangegrouppart = True
627 wrappedchangegrouppart = True
628
628
629 def _listkeys(orig, self, namespace):
629 def _listkeys(orig, self, namespace):
630 origvalues = orig(self, namespace)
630 origvalues = orig(self, namespace)
631 if namespace == b'phases' and pullfrombundlestore:
631 if namespace == b'phases' and pullfrombundlestore:
632 if origvalues.get(b'publishing') == b'True':
632 if origvalues.get(b'publishing') == b'True':
633 # Make repo non-publishing to preserve draft phase
633 # Make repo non-publishing to preserve draft phase
634 del origvalues[b'publishing']
634 del origvalues[b'publishing']
635 origvalues.update(newphases)
635 origvalues.update(newphases)
636 return origvalues
636 return origvalues
637
637
638 extensions.wrapfunction(
638 extensions.wrapfunction(
639 localrepo.localrepository, b'listkeys', _listkeys
639 localrepo.localrepository, b'listkeys', _listkeys
640 )
640 )
641 wrappedlistkeys = True
641 wrappedlistkeys = True
642 heads = list((set(newheads) | set(heads)) - set(scratchheads))
642 heads = list((set(newheads) | set(heads)) - set(scratchheads))
643 result = orig(
643 result = orig(
644 repo, source, heads=heads, bundlecaps=bundlecaps, **kwargs
644 repo, source, heads=heads, bundlecaps=bundlecaps, **kwargs
645 )
645 )
646 finally:
646 finally:
647 if wrappedchangegrouppart:
647 if wrappedchangegrouppart:
648 exchange.getbundle2partsmapping[b'changegroup'] = oldchangegrouppart
648 exchange.getbundle2partsmapping[b'changegroup'] = oldchangegrouppart
649 if wrappedlistkeys:
649 if wrappedlistkeys:
650 extensions.unwrapfunction(
650 extensions.unwrapfunction(
651 localrepo.localrepository, b'listkeys', _listkeys
651 localrepo.localrepository, b'listkeys', _listkeys
652 )
652 )
653 return result
653 return result
654
654
655
655
656 def _lookupwrap(orig):
656 def _lookupwrap(orig):
657 def _lookup(repo, proto, key):
657 def _lookup(repo, proto, key):
658 localkey = encoding.tolocal(key)
658 localkey = encoding.tolocal(key)
659
659
660 if isinstance(localkey, str) and _scratchbranchmatcher(localkey):
660 if isinstance(localkey, str) and _scratchbranchmatcher(localkey):
661 scratchnode = repo.bundlestore.index.getnode(localkey)
661 scratchnode = repo.bundlestore.index.getnode(localkey)
662 if scratchnode:
662 if scratchnode:
663 return b"%d %s\n" % (1, scratchnode)
663 return b"%d %s\n" % (1, scratchnode)
664 else:
664 else:
665 return b"%d %s\n" % (
665 return b"%d %s\n" % (
666 0,
666 0,
667 b'scratch branch %s not found' % localkey,
667 b'scratch branch %s not found' % localkey,
668 )
668 )
669 else:
669 else:
670 try:
670 try:
671 r = hex(repo.lookup(localkey))
671 r = hex(repo.lookup(localkey))
672 return b"%d %s\n" % (1, r)
672 return b"%d %s\n" % (1, r)
673 except Exception as inst:
673 except Exception as inst:
674 if repo.bundlestore.index.getbundle(localkey):
674 if repo.bundlestore.index.getbundle(localkey):
675 return b"%d %s\n" % (1, localkey)
675 return b"%d %s\n" % (1, localkey)
676 else:
676 else:
677 r = stringutil.forcebytestr(inst)
677 r = stringutil.forcebytestr(inst)
678 return b"%d %s\n" % (0, r)
678 return b"%d %s\n" % (0, r)
679
679
680 return _lookup
680 return _lookup
681
681
682
682
683 def _pull(orig, ui, repo, source=b"default", **opts):
683 def _pull(orig, ui, repo, source=b"default", **opts):
684 opts = pycompat.byteskwargs(opts)
684 opts = pycompat.byteskwargs(opts)
685 # Copy paste from `pull` command
685 # Copy paste from `pull` command
686 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
686 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
687
687
688 scratchbookmarks = {}
688 scratchbookmarks = {}
689 unfi = repo.unfiltered()
689 unfi = repo.unfiltered()
690 unknownnodes = []
690 unknownnodes = []
691 for rev in opts.get(b'rev', []):
691 for rev in opts.get(b'rev', []):
692 if rev not in unfi:
692 if rev not in unfi:
693 unknownnodes.append(rev)
693 unknownnodes.append(rev)
694 if opts.get(b'bookmark'):
694 if opts.get(b'bookmark'):
695 bookmarks = []
695 bookmarks = []
696 revs = opts.get(b'rev') or []
696 revs = opts.get(b'rev') or []
697 for bookmark in opts.get(b'bookmark'):
697 for bookmark in opts.get(b'bookmark'):
698 if _scratchbranchmatcher(bookmark):
698 if _scratchbranchmatcher(bookmark):
699 # rev is not known yet
699 # rev is not known yet
700 # it will be fetched with listkeyspatterns next
700 # it will be fetched with listkeyspatterns next
701 scratchbookmarks[bookmark] = b'REVTOFETCH'
701 scratchbookmarks[bookmark] = b'REVTOFETCH'
702 else:
702 else:
703 bookmarks.append(bookmark)
703 bookmarks.append(bookmark)
704
704
705 if scratchbookmarks:
705 if scratchbookmarks:
706 other = hg.peer(repo, opts, source)
706 other = hg.peer(repo, opts, source)
707 fetchedbookmarks = other.listkeyspatterns(
707 try:
708 b'bookmarks', patterns=scratchbookmarks
708 fetchedbookmarks = other.listkeyspatterns(
709 )
709 b'bookmarks', patterns=scratchbookmarks
710 for bookmark in scratchbookmarks:
710 )
711 if bookmark not in fetchedbookmarks:
711 for bookmark in scratchbookmarks:
712 raise error.Abort(
712 if bookmark not in fetchedbookmarks:
713 b'remote bookmark %s not found!' % bookmark
713 raise error.Abort(
714 )
714 b'remote bookmark %s not found!' % bookmark
715 scratchbookmarks[bookmark] = fetchedbookmarks[bookmark]
715 )
716 revs.append(fetchedbookmarks[bookmark])
716 scratchbookmarks[bookmark] = fetchedbookmarks[bookmark]
717 revs.append(fetchedbookmarks[bookmark])
718 finally:
719 other.close()
717 opts[b'bookmark'] = bookmarks
720 opts[b'bookmark'] = bookmarks
718 opts[b'rev'] = revs
721 opts[b'rev'] = revs
719
722
720 if scratchbookmarks or unknownnodes:
723 if scratchbookmarks or unknownnodes:
721 # Set anyincoming to True
724 # Set anyincoming to True
722 extensions.wrapfunction(
725 extensions.wrapfunction(
723 discovery, b'findcommonincoming', _findcommonincoming
726 discovery, b'findcommonincoming', _findcommonincoming
724 )
727 )
725 try:
728 try:
726 # Remote scratch bookmarks will be deleted because remotenames doesn't
729 # Remote scratch bookmarks will be deleted because remotenames doesn't
727 # know about them. Let's save it before pull and restore after
730 # know about them. Let's save it before pull and restore after
728 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, source)
731 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, source)
729 result = orig(ui, repo, source, **pycompat.strkwargs(opts))
732 result = orig(ui, repo, source, **pycompat.strkwargs(opts))
730 # TODO(stash): race condition is possible
733 # TODO(stash): race condition is possible
731 # if scratch bookmarks was updated right after orig.
734 # if scratch bookmarks was updated right after orig.
732 # But that's unlikely and shouldn't be harmful.
735 # But that's unlikely and shouldn't be harmful.
733 if common.isremotebooksenabled(ui):
736 if common.isremotebooksenabled(ui):
734 remotescratchbookmarks.update(scratchbookmarks)
737 remotescratchbookmarks.update(scratchbookmarks)
735 _saveremotebookmarks(repo, remotescratchbookmarks, source)
738 _saveremotebookmarks(repo, remotescratchbookmarks, source)
736 else:
739 else:
737 _savelocalbookmarks(repo, scratchbookmarks)
740 _savelocalbookmarks(repo, scratchbookmarks)
738 return result
741 return result
739 finally:
742 finally:
740 if scratchbookmarks:
743 if scratchbookmarks:
741 extensions.unwrapfunction(discovery, b'findcommonincoming')
744 extensions.unwrapfunction(discovery, b'findcommonincoming')
742
745
743
746
744 def _readscratchremotebookmarks(ui, repo, other):
747 def _readscratchremotebookmarks(ui, repo, other):
745 if common.isremotebooksenabled(ui):
748 if common.isremotebooksenabled(ui):
746 remotenamesext = extensions.find(b'remotenames')
749 remotenamesext = extensions.find(b'remotenames')
747 remotepath = remotenamesext.activepath(repo.ui, other)
750 remotepath = remotenamesext.activepath(repo.ui, other)
748 result = {}
751 result = {}
749 # Let's refresh remotenames to make sure we have it up to date
752 # Let's refresh remotenames to make sure we have it up to date
750 # Seems that `repo.names['remotebookmarks']` may return stale bookmarks
753 # Seems that `repo.names['remotebookmarks']` may return stale bookmarks
751 # and it results in deleting scratch bookmarks. Our best guess how to
754 # and it results in deleting scratch bookmarks. Our best guess how to
752 # fix it is to use `clearnames()`
755 # fix it is to use `clearnames()`
753 repo._remotenames.clearnames()
756 repo._remotenames.clearnames()
754 for remotebookmark in repo.names[b'remotebookmarks'].listnames(repo):
757 for remotebookmark in repo.names[b'remotebookmarks'].listnames(repo):
755 path, bookname = remotenamesext.splitremotename(remotebookmark)
758 path, bookname = remotenamesext.splitremotename(remotebookmark)
756 if path == remotepath and _scratchbranchmatcher(bookname):
759 if path == remotepath and _scratchbranchmatcher(bookname):
757 nodes = repo.names[b'remotebookmarks'].nodes(
760 nodes = repo.names[b'remotebookmarks'].nodes(
758 repo, remotebookmark
761 repo, remotebookmark
759 )
762 )
760 if nodes:
763 if nodes:
761 result[bookname] = hex(nodes[0])
764 result[bookname] = hex(nodes[0])
762 return result
765 return result
763 else:
766 else:
764 return {}
767 return {}
765
768
766
769
767 def _saveremotebookmarks(repo, newbookmarks, remote):
770 def _saveremotebookmarks(repo, newbookmarks, remote):
768 remotenamesext = extensions.find(b'remotenames')
771 remotenamesext = extensions.find(b'remotenames')
769 remotepath = remotenamesext.activepath(repo.ui, remote)
772 remotepath = remotenamesext.activepath(repo.ui, remote)
770 branches = collections.defaultdict(list)
773 branches = collections.defaultdict(list)
771 bookmarks = {}
774 bookmarks = {}
772 remotenames = remotenamesext.readremotenames(repo)
775 remotenames = remotenamesext.readremotenames(repo)
773 for hexnode, nametype, remote, rname in remotenames:
776 for hexnode, nametype, remote, rname in remotenames:
774 if remote != remotepath:
777 if remote != remotepath:
775 continue
778 continue
776 if nametype == b'bookmarks':
779 if nametype == b'bookmarks':
777 if rname in newbookmarks:
780 if rname in newbookmarks:
778 # It's possible if we have a normal bookmark that matches
781 # It's possible if we have a normal bookmark that matches
779 # scratch branch pattern. In this case just use the current
782 # scratch branch pattern. In this case just use the current
780 # bookmark node
783 # bookmark node
781 del newbookmarks[rname]
784 del newbookmarks[rname]
782 bookmarks[rname] = hexnode
785 bookmarks[rname] = hexnode
783 elif nametype == b'branches':
786 elif nametype == b'branches':
784 # saveremotenames expects 20 byte binary nodes for branches
787 # saveremotenames expects 20 byte binary nodes for branches
785 branches[rname].append(bin(hexnode))
788 branches[rname].append(bin(hexnode))
786
789
787 for bookmark, hexnode in pycompat.iteritems(newbookmarks):
790 for bookmark, hexnode in pycompat.iteritems(newbookmarks):
788 bookmarks[bookmark] = hexnode
791 bookmarks[bookmark] = hexnode
789 remotenamesext.saveremotenames(repo, remotepath, branches, bookmarks)
792 remotenamesext.saveremotenames(repo, remotepath, branches, bookmarks)
790
793
791
794
792 def _savelocalbookmarks(repo, bookmarks):
795 def _savelocalbookmarks(repo, bookmarks):
793 if not bookmarks:
796 if not bookmarks:
794 return
797 return
795 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
798 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
796 changes = []
799 changes = []
797 for scratchbook, node in pycompat.iteritems(bookmarks):
800 for scratchbook, node in pycompat.iteritems(bookmarks):
798 changectx = repo[node]
801 changectx = repo[node]
799 changes.append((scratchbook, changectx.node()))
802 changes.append((scratchbook, changectx.node()))
800 repo._bookmarks.applychanges(repo, tr, changes)
803 repo._bookmarks.applychanges(repo, tr, changes)
801
804
802
805
803 def _findcommonincoming(orig, *args, **kwargs):
806 def _findcommonincoming(orig, *args, **kwargs):
804 common, inc, remoteheads = orig(*args, **kwargs)
807 common, inc, remoteheads = orig(*args, **kwargs)
805 return common, True, remoteheads
808 return common, True, remoteheads
806
809
807
810
808 def _push(orig, ui, repo, dest=None, *args, **opts):
811 def _push(orig, ui, repo, dest=None, *args, **opts):
809 opts = pycompat.byteskwargs(opts)
812 opts = pycompat.byteskwargs(opts)
810 bookmark = opts.get(b'bookmark')
813 bookmark = opts.get(b'bookmark')
811 # we only support pushing one infinitepush bookmark at once
814 # we only support pushing one infinitepush bookmark at once
812 if len(bookmark) == 1:
815 if len(bookmark) == 1:
813 bookmark = bookmark[0]
816 bookmark = bookmark[0]
814 else:
817 else:
815 bookmark = b''
818 bookmark = b''
816
819
817 oldphasemove = None
820 oldphasemove = None
818 overrides = {(experimental, configbookmark): bookmark}
821 overrides = {(experimental, configbookmark): bookmark}
819
822
820 with ui.configoverride(overrides, b'infinitepush'):
823 with ui.configoverride(overrides, b'infinitepush'):
821 scratchpush = opts.get(b'bundle_store')
824 scratchpush = opts.get(b'bundle_store')
822 if _scratchbranchmatcher(bookmark):
825 if _scratchbranchmatcher(bookmark):
823 scratchpush = True
826 scratchpush = True
824 # bundle2 can be sent back after push (for example, bundle2
827 # bundle2 can be sent back after push (for example, bundle2
825 # containing `pushkey` part to update bookmarks)
828 # containing `pushkey` part to update bookmarks)
826 ui.setconfig(experimental, b'bundle2.pushback', True)
829 ui.setconfig(experimental, b'bundle2.pushback', True)
827
830
828 if scratchpush:
831 if scratchpush:
829 # this is an infinitepush, we don't want the bookmark to be applied
832 # this is an infinitepush, we don't want the bookmark to be applied
830 # rather that should be stored in the bundlestore
833 # rather that should be stored in the bundlestore
831 opts[b'bookmark'] = []
834 opts[b'bookmark'] = []
832 ui.setconfig(experimental, configscratchpush, True)
835 ui.setconfig(experimental, configscratchpush, True)
833 oldphasemove = extensions.wrapfunction(
836 oldphasemove = extensions.wrapfunction(
834 exchange, b'_localphasemove', _phasemove
837 exchange, b'_localphasemove', _phasemove
835 )
838 )
836 # Copy-paste from `push` command
839 # Copy-paste from `push` command
837 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
840 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
838 if not path:
841 if not path:
839 raise error.Abort(
842 raise error.Abort(
840 _(b'default repository not configured!'),
843 _(b'default repository not configured!'),
841 hint=_(b"see 'hg help config.paths'"),
844 hint=_(b"see 'hg help config.paths'"),
842 )
845 )
843 destpath = path.pushloc or path.loc
846 destpath = path.pushloc or path.loc
844 # Remote scratch bookmarks will be deleted because remotenames doesn't
847 # Remote scratch bookmarks will be deleted because remotenames doesn't
845 # know about them. Let's save it before push and restore after
848 # know about them. Let's save it before push and restore after
846 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, destpath)
849 remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, destpath)
847 result = orig(ui, repo, dest, *args, **pycompat.strkwargs(opts))
850 result = orig(ui, repo, dest, *args, **pycompat.strkwargs(opts))
848 if common.isremotebooksenabled(ui):
851 if common.isremotebooksenabled(ui):
849 if bookmark and scratchpush:
852 if bookmark and scratchpush:
850 other = hg.peer(repo, opts, destpath)
853 other = hg.peer(repo, opts, destpath)
851 fetchedbookmarks = other.listkeyspatterns(
854 try:
852 b'bookmarks', patterns=[bookmark]
855 fetchedbookmarks = other.listkeyspatterns(
853 )
856 b'bookmarks', patterns=[bookmark]
854 remotescratchbookmarks.update(fetchedbookmarks)
857 )
858 remotescratchbookmarks.update(fetchedbookmarks)
859 finally:
860 other.close()
855 _saveremotebookmarks(repo, remotescratchbookmarks, destpath)
861 _saveremotebookmarks(repo, remotescratchbookmarks, destpath)
856 if oldphasemove:
862 if oldphasemove:
857 exchange._localphasemove = oldphasemove
863 exchange._localphasemove = oldphasemove
858 return result
864 return result
859
865
860
866
861 def _deleteinfinitepushbookmarks(ui, repo, path, names):
867 def _deleteinfinitepushbookmarks(ui, repo, path, names):
862 """Prune remote names by removing the bookmarks we don't want anymore,
868 """Prune remote names by removing the bookmarks we don't want anymore,
863 then writing the result back to disk
869 then writing the result back to disk
864 """
870 """
865 remotenamesext = extensions.find(b'remotenames')
871 remotenamesext = extensions.find(b'remotenames')
866
872
867 # remotename format is:
873 # remotename format is:
868 # (node, nametype ("branches" or "bookmarks"), remote, name)
874 # (node, nametype ("branches" or "bookmarks"), remote, name)
869 nametype_idx = 1
875 nametype_idx = 1
870 remote_idx = 2
876 remote_idx = 2
871 name_idx = 3
877 name_idx = 3
872 remotenames = [
878 remotenames = [
873 remotename
879 remotename
874 for remotename in remotenamesext.readremotenames(repo)
880 for remotename in remotenamesext.readremotenames(repo)
875 if remotename[remote_idx] == path
881 if remotename[remote_idx] == path
876 ]
882 ]
877 remote_bm_names = [
883 remote_bm_names = [
878 remotename[name_idx]
884 remotename[name_idx]
879 for remotename in remotenames
885 for remotename in remotenames
880 if remotename[nametype_idx] == b"bookmarks"
886 if remotename[nametype_idx] == b"bookmarks"
881 ]
887 ]
882
888
883 for name in names:
889 for name in names:
884 if name not in remote_bm_names:
890 if name not in remote_bm_names:
885 raise error.Abort(
891 raise error.Abort(
886 _(
892 _(
887 b"infinitepush bookmark '{}' does not exist "
893 b"infinitepush bookmark '{}' does not exist "
888 b"in path '{}'"
894 b"in path '{}'"
889 ).format(name, path)
895 ).format(name, path)
890 )
896 )
891
897
892 bookmarks = {}
898 bookmarks = {}
893 branches = collections.defaultdict(list)
899 branches = collections.defaultdict(list)
894 for node, nametype, remote, name in remotenames:
900 for node, nametype, remote, name in remotenames:
895 if nametype == b"bookmarks" and name not in names:
901 if nametype == b"bookmarks" and name not in names:
896 bookmarks[name] = node
902 bookmarks[name] = node
897 elif nametype == b"branches":
903 elif nametype == b"branches":
898 # saveremotenames wants binary nodes for branches
904 # saveremotenames wants binary nodes for branches
899 branches[name].append(bin(node))
905 branches[name].append(bin(node))
900
906
901 remotenamesext.saveremotenames(repo, path, branches, bookmarks)
907 remotenamesext.saveremotenames(repo, path, branches, bookmarks)
902
908
903
909
904 def _phasemove(orig, pushop, nodes, phase=phases.public):
910 def _phasemove(orig, pushop, nodes, phase=phases.public):
905 """prevent commits from being marked public
911 """prevent commits from being marked public
906
912
907 Since these are going to a scratch branch, they aren't really being
913 Since these are going to a scratch branch, they aren't really being
908 published."""
914 published."""
909
915
910 if phase != phases.public:
916 if phase != phases.public:
911 orig(pushop, nodes, phase)
917 orig(pushop, nodes, phase)
912
918
913
919
914 @exchange.b2partsgenerator(scratchbranchparttype)
920 @exchange.b2partsgenerator(scratchbranchparttype)
915 def partgen(pushop, bundler):
921 def partgen(pushop, bundler):
916 bookmark = pushop.ui.config(experimental, configbookmark)
922 bookmark = pushop.ui.config(experimental, configbookmark)
917 scratchpush = pushop.ui.configbool(experimental, configscratchpush)
923 scratchpush = pushop.ui.configbool(experimental, configscratchpush)
918 if b'changesets' in pushop.stepsdone or not scratchpush:
924 if b'changesets' in pushop.stepsdone or not scratchpush:
919 return
925 return
920
926
921 if scratchbranchparttype not in bundle2.bundle2caps(pushop.remote):
927 if scratchbranchparttype not in bundle2.bundle2caps(pushop.remote):
922 return
928 return
923
929
924 pushop.stepsdone.add(b'changesets')
930 pushop.stepsdone.add(b'changesets')
925 if not pushop.outgoing.missing:
931 if not pushop.outgoing.missing:
926 pushop.ui.status(_(b'no changes found\n'))
932 pushop.ui.status(_(b'no changes found\n'))
927 pushop.cgresult = 0
933 pushop.cgresult = 0
928 return
934 return
929
935
930 # This parameter tells the server that the following bundle is an
936 # This parameter tells the server that the following bundle is an
931 # infinitepush. This let's it switch the part processing to our infinitepush
937 # infinitepush. This let's it switch the part processing to our infinitepush
932 # code path.
938 # code path.
933 bundler.addparam(b"infinitepush", b"True")
939 bundler.addparam(b"infinitepush", b"True")
934
940
935 scratchparts = bundleparts.getscratchbranchparts(
941 scratchparts = bundleparts.getscratchbranchparts(
936 pushop.repo, pushop.remote, pushop.outgoing, pushop.ui, bookmark
942 pushop.repo, pushop.remote, pushop.outgoing, pushop.ui, bookmark
937 )
943 )
938
944
939 for scratchpart in scratchparts:
945 for scratchpart in scratchparts:
940 bundler.addpart(scratchpart)
946 bundler.addpart(scratchpart)
941
947
942 def handlereply(op):
948 def handlereply(op):
943 # server either succeeds or aborts; no code to read
949 # server either succeeds or aborts; no code to read
944 pushop.cgresult = 1
950 pushop.cgresult = 1
945
951
946 return handlereply
952 return handlereply
947
953
948
954
949 bundle2.capabilities[bundleparts.scratchbranchparttype] = ()
955 bundle2.capabilities[bundleparts.scratchbranchparttype] = ()
950
956
951
957
952 def _getrevs(bundle, oldnode, force, bookmark):
958 def _getrevs(bundle, oldnode, force, bookmark):
953 b'extracts and validates the revs to be imported'
959 b'extracts and validates the revs to be imported'
954 revs = [bundle[r] for r in bundle.revs(b'sort(bundle())')]
960 revs = [bundle[r] for r in bundle.revs(b'sort(bundle())')]
955
961
956 # new bookmark
962 # new bookmark
957 if oldnode is None:
963 if oldnode is None:
958 return revs
964 return revs
959
965
960 # Fast forward update
966 # Fast forward update
961 if oldnode in bundle and list(bundle.set(b'bundle() & %s::', oldnode)):
967 if oldnode in bundle and list(bundle.set(b'bundle() & %s::', oldnode)):
962 return revs
968 return revs
963
969
964 return revs
970 return revs
965
971
966
972
967 @contextlib.contextmanager
973 @contextlib.contextmanager
968 def logservicecall(logger, service, **kwargs):
974 def logservicecall(logger, service, **kwargs):
969 start = time.time()
975 start = time.time()
970 logger(service, eventtype=b'start', **kwargs)
976 logger(service, eventtype=b'start', **kwargs)
971 try:
977 try:
972 yield
978 yield
973 logger(
979 logger(
974 service,
980 service,
975 eventtype=b'success',
981 eventtype=b'success',
976 elapsedms=(time.time() - start) * 1000,
982 elapsedms=(time.time() - start) * 1000,
977 **kwargs
983 **kwargs
978 )
984 )
979 except Exception as e:
985 except Exception as e:
980 logger(
986 logger(
981 service,
987 service,
982 eventtype=b'failure',
988 eventtype=b'failure',
983 elapsedms=(time.time() - start) * 1000,
989 elapsedms=(time.time() - start) * 1000,
984 errormsg=stringutil.forcebytestr(e),
990 errormsg=stringutil.forcebytestr(e),
985 **kwargs
991 **kwargs
986 )
992 )
987 raise
993 raise
988
994
989
995
990 def _getorcreateinfinitepushlogger(op):
996 def _getorcreateinfinitepushlogger(op):
991 logger = op.records[b'infinitepushlogger']
997 logger = op.records[b'infinitepushlogger']
992 if not logger:
998 if not logger:
993 ui = op.repo.ui
999 ui = op.repo.ui
994 try:
1000 try:
995 username = procutil.getuser()
1001 username = procutil.getuser()
996 except Exception:
1002 except Exception:
997 username = b'unknown'
1003 username = b'unknown'
998 # Generate random request id to be able to find all logged entries
1004 # Generate random request id to be able to find all logged entries
999 # for the same request. Since requestid is pseudo-generated it may
1005 # for the same request. Since requestid is pseudo-generated it may
1000 # not be unique, but we assume that (hostname, username, requestid)
1006 # not be unique, but we assume that (hostname, username, requestid)
1001 # is unique.
1007 # is unique.
1002 random.seed()
1008 random.seed()
1003 requestid = random.randint(0, 2000000000)
1009 requestid = random.randint(0, 2000000000)
1004 hostname = socket.gethostname()
1010 hostname = socket.gethostname()
1005 logger = functools.partial(
1011 logger = functools.partial(
1006 ui.log,
1012 ui.log,
1007 b'infinitepush',
1013 b'infinitepush',
1008 user=username,
1014 user=username,
1009 requestid=requestid,
1015 requestid=requestid,
1010 hostname=hostname,
1016 hostname=hostname,
1011 reponame=ui.config(b'infinitepush', b'reponame'),
1017 reponame=ui.config(b'infinitepush', b'reponame'),
1012 )
1018 )
1013 op.records.add(b'infinitepushlogger', logger)
1019 op.records.add(b'infinitepushlogger', logger)
1014 else:
1020 else:
1015 logger = logger[0]
1021 logger = logger[0]
1016 return logger
1022 return logger
1017
1023
1018
1024
1019 def storetobundlestore(orig, repo, op, unbundler):
1025 def storetobundlestore(orig, repo, op, unbundler):
1020 """stores the incoming bundle coming from push command to the bundlestore
1026 """stores the incoming bundle coming from push command to the bundlestore
1021 instead of applying on the revlogs"""
1027 instead of applying on the revlogs"""
1022
1028
1023 repo.ui.status(_(b"storing changesets on the bundlestore\n"))
1029 repo.ui.status(_(b"storing changesets on the bundlestore\n"))
1024 bundler = bundle2.bundle20(repo.ui)
1030 bundler = bundle2.bundle20(repo.ui)
1025
1031
1026 # processing each part and storing it in bundler
1032 # processing each part and storing it in bundler
1027 with bundle2.partiterator(repo, op, unbundler) as parts:
1033 with bundle2.partiterator(repo, op, unbundler) as parts:
1028 for part in parts:
1034 for part in parts:
1029 bundlepart = None
1035 bundlepart = None
1030 if part.type == b'replycaps':
1036 if part.type == b'replycaps':
1031 # This configures the current operation to allow reply parts.
1037 # This configures the current operation to allow reply parts.
1032 bundle2._processpart(op, part)
1038 bundle2._processpart(op, part)
1033 else:
1039 else:
1034 bundlepart = bundle2.bundlepart(part.type, data=part.read())
1040 bundlepart = bundle2.bundlepart(part.type, data=part.read())
1035 for key, value in pycompat.iteritems(part.params):
1041 for key, value in pycompat.iteritems(part.params):
1036 bundlepart.addparam(key, value)
1042 bundlepart.addparam(key, value)
1037
1043
1038 # Certain parts require a response
1044 # Certain parts require a response
1039 if part.type in (b'pushkey', b'changegroup'):
1045 if part.type in (b'pushkey', b'changegroup'):
1040 if op.reply is not None:
1046 if op.reply is not None:
1041 rpart = op.reply.newpart(b'reply:%s' % part.type)
1047 rpart = op.reply.newpart(b'reply:%s' % part.type)
1042 rpart.addparam(
1048 rpart.addparam(
1043 b'in-reply-to', b'%d' % part.id, mandatory=False
1049 b'in-reply-to', b'%d' % part.id, mandatory=False
1044 )
1050 )
1045 rpart.addparam(b'return', b'1', mandatory=False)
1051 rpart.addparam(b'return', b'1', mandatory=False)
1046
1052
1047 op.records.add(
1053 op.records.add(
1048 part.type,
1054 part.type,
1049 {
1055 {
1050 b'return': 1,
1056 b'return': 1,
1051 },
1057 },
1052 )
1058 )
1053 if bundlepart:
1059 if bundlepart:
1054 bundler.addpart(bundlepart)
1060 bundler.addpart(bundlepart)
1055
1061
1056 # storing the bundle in the bundlestore
1062 # storing the bundle in the bundlestore
1057 buf = util.chunkbuffer(bundler.getchunks())
1063 buf = util.chunkbuffer(bundler.getchunks())
1058 fd, bundlefile = pycompat.mkstemp()
1064 fd, bundlefile = pycompat.mkstemp()
1059 try:
1065 try:
1060 try:
1066 try:
1061 fp = os.fdopen(fd, 'wb')
1067 fp = os.fdopen(fd, 'wb')
1062 fp.write(buf.read())
1068 fp.write(buf.read())
1063 finally:
1069 finally:
1064 fp.close()
1070 fp.close()
1065 storebundle(op, {}, bundlefile)
1071 storebundle(op, {}, bundlefile)
1066 finally:
1072 finally:
1067 try:
1073 try:
1068 os.unlink(bundlefile)
1074 os.unlink(bundlefile)
1069 except Exception:
1075 except Exception:
1070 # we would rather see the original exception
1076 # we would rather see the original exception
1071 pass
1077 pass
1072
1078
1073
1079
1074 def processparts(orig, repo, op, unbundler):
1080 def processparts(orig, repo, op, unbundler):
1075
1081
1076 # make sure we don't wrap processparts in case of `hg unbundle`
1082 # make sure we don't wrap processparts in case of `hg unbundle`
1077 if op.source == b'unbundle':
1083 if op.source == b'unbundle':
1078 return orig(repo, op, unbundler)
1084 return orig(repo, op, unbundler)
1079
1085
1080 # this server routes each push to bundle store
1086 # this server routes each push to bundle store
1081 if repo.ui.configbool(b'infinitepush', b'pushtobundlestore'):
1087 if repo.ui.configbool(b'infinitepush', b'pushtobundlestore'):
1082 return storetobundlestore(orig, repo, op, unbundler)
1088 return storetobundlestore(orig, repo, op, unbundler)
1083
1089
1084 if unbundler.params.get(b'infinitepush') != b'True':
1090 if unbundler.params.get(b'infinitepush') != b'True':
1085 return orig(repo, op, unbundler)
1091 return orig(repo, op, unbundler)
1086
1092
1087 handleallparts = repo.ui.configbool(b'infinitepush', b'storeallparts')
1093 handleallparts = repo.ui.configbool(b'infinitepush', b'storeallparts')
1088
1094
1089 bundler = bundle2.bundle20(repo.ui)
1095 bundler = bundle2.bundle20(repo.ui)
1090 cgparams = None
1096 cgparams = None
1091 with bundle2.partiterator(repo, op, unbundler) as parts:
1097 with bundle2.partiterator(repo, op, unbundler) as parts:
1092 for part in parts:
1098 for part in parts:
1093 bundlepart = None
1099 bundlepart = None
1094 if part.type == b'replycaps':
1100 if part.type == b'replycaps':
1095 # This configures the current operation to allow reply parts.
1101 # This configures the current operation to allow reply parts.
1096 bundle2._processpart(op, part)
1102 bundle2._processpart(op, part)
1097 elif part.type == bundleparts.scratchbranchparttype:
1103 elif part.type == bundleparts.scratchbranchparttype:
1098 # Scratch branch parts need to be converted to normal
1104 # Scratch branch parts need to be converted to normal
1099 # changegroup parts, and the extra parameters stored for later
1105 # changegroup parts, and the extra parameters stored for later
1100 # when we upload to the store. Eventually those parameters will
1106 # when we upload to the store. Eventually those parameters will
1101 # be put on the actual bundle instead of this part, then we can
1107 # be put on the actual bundle instead of this part, then we can
1102 # send a vanilla changegroup instead of the scratchbranch part.
1108 # send a vanilla changegroup instead of the scratchbranch part.
1103 cgversion = part.params.get(b'cgversion', b'01')
1109 cgversion = part.params.get(b'cgversion', b'01')
1104 bundlepart = bundle2.bundlepart(
1110 bundlepart = bundle2.bundlepart(
1105 b'changegroup', data=part.read()
1111 b'changegroup', data=part.read()
1106 )
1112 )
1107 bundlepart.addparam(b'version', cgversion)
1113 bundlepart.addparam(b'version', cgversion)
1108 cgparams = part.params
1114 cgparams = part.params
1109
1115
1110 # If we're not dumping all parts into the new bundle, we need to
1116 # If we're not dumping all parts into the new bundle, we need to
1111 # alert the future pushkey and phase-heads handler to skip
1117 # alert the future pushkey and phase-heads handler to skip
1112 # the part.
1118 # the part.
1113 if not handleallparts:
1119 if not handleallparts:
1114 op.records.add(
1120 op.records.add(
1115 scratchbranchparttype + b'_skippushkey', True
1121 scratchbranchparttype + b'_skippushkey', True
1116 )
1122 )
1117 op.records.add(
1123 op.records.add(
1118 scratchbranchparttype + b'_skipphaseheads', True
1124 scratchbranchparttype + b'_skipphaseheads', True
1119 )
1125 )
1120 else:
1126 else:
1121 if handleallparts:
1127 if handleallparts:
1122 # Ideally we would not process any parts, and instead just
1128 # Ideally we would not process any parts, and instead just
1123 # forward them to the bundle for storage, but since this
1129 # forward them to the bundle for storage, but since this
1124 # differs from previous behavior, we need to put it behind a
1130 # differs from previous behavior, we need to put it behind a
1125 # config flag for incremental rollout.
1131 # config flag for incremental rollout.
1126 bundlepart = bundle2.bundlepart(part.type, data=part.read())
1132 bundlepart = bundle2.bundlepart(part.type, data=part.read())
1127 for key, value in pycompat.iteritems(part.params):
1133 for key, value in pycompat.iteritems(part.params):
1128 bundlepart.addparam(key, value)
1134 bundlepart.addparam(key, value)
1129
1135
1130 # Certain parts require a response
1136 # Certain parts require a response
1131 if part.type == b'pushkey':
1137 if part.type == b'pushkey':
1132 if op.reply is not None:
1138 if op.reply is not None:
1133 rpart = op.reply.newpart(b'reply:pushkey')
1139 rpart = op.reply.newpart(b'reply:pushkey')
1134 rpart.addparam(
1140 rpart.addparam(
1135 b'in-reply-to', str(part.id), mandatory=False
1141 b'in-reply-to', str(part.id), mandatory=False
1136 )
1142 )
1137 rpart.addparam(b'return', b'1', mandatory=False)
1143 rpart.addparam(b'return', b'1', mandatory=False)
1138 else:
1144 else:
1139 bundle2._processpart(op, part)
1145 bundle2._processpart(op, part)
1140
1146
1141 if handleallparts:
1147 if handleallparts:
1142 op.records.add(
1148 op.records.add(
1143 part.type,
1149 part.type,
1144 {
1150 {
1145 b'return': 1,
1151 b'return': 1,
1146 },
1152 },
1147 )
1153 )
1148 if bundlepart:
1154 if bundlepart:
1149 bundler.addpart(bundlepart)
1155 bundler.addpart(bundlepart)
1150
1156
1151 # If commits were sent, store them
1157 # If commits were sent, store them
1152 if cgparams:
1158 if cgparams:
1153 buf = util.chunkbuffer(bundler.getchunks())
1159 buf = util.chunkbuffer(bundler.getchunks())
1154 fd, bundlefile = pycompat.mkstemp()
1160 fd, bundlefile = pycompat.mkstemp()
1155 try:
1161 try:
1156 try:
1162 try:
1157 fp = os.fdopen(fd, 'wb')
1163 fp = os.fdopen(fd, 'wb')
1158 fp.write(buf.read())
1164 fp.write(buf.read())
1159 finally:
1165 finally:
1160 fp.close()
1166 fp.close()
1161 storebundle(op, cgparams, bundlefile)
1167 storebundle(op, cgparams, bundlefile)
1162 finally:
1168 finally:
1163 try:
1169 try:
1164 os.unlink(bundlefile)
1170 os.unlink(bundlefile)
1165 except Exception:
1171 except Exception:
1166 # we would rather see the original exception
1172 # we would rather see the original exception
1167 pass
1173 pass
1168
1174
1169
1175
1170 def storebundle(op, params, bundlefile):
1176 def storebundle(op, params, bundlefile):
1171 log = _getorcreateinfinitepushlogger(op)
1177 log = _getorcreateinfinitepushlogger(op)
1172 parthandlerstart = time.time()
1178 parthandlerstart = time.time()
1173 log(scratchbranchparttype, eventtype=b'start')
1179 log(scratchbranchparttype, eventtype=b'start')
1174 index = op.repo.bundlestore.index
1180 index = op.repo.bundlestore.index
1175 store = op.repo.bundlestore.store
1181 store = op.repo.bundlestore.store
1176 op.records.add(scratchbranchparttype + b'_skippushkey', True)
1182 op.records.add(scratchbranchparttype + b'_skippushkey', True)
1177
1183
1178 bundle = None
1184 bundle = None
1179 try: # guards bundle
1185 try: # guards bundle
1180 bundlepath = b"bundle:%s+%s" % (op.repo.root, bundlefile)
1186 bundlepath = b"bundle:%s+%s" % (op.repo.root, bundlefile)
1181 bundle = hg.repository(op.repo.ui, bundlepath)
1187 bundle = hg.repository(op.repo.ui, bundlepath)
1182
1188
1183 bookmark = params.get(b'bookmark')
1189 bookmark = params.get(b'bookmark')
1184 bookprevnode = params.get(b'bookprevnode', b'')
1190 bookprevnode = params.get(b'bookprevnode', b'')
1185 force = params.get(b'force')
1191 force = params.get(b'force')
1186
1192
1187 if bookmark:
1193 if bookmark:
1188 oldnode = index.getnode(bookmark)
1194 oldnode = index.getnode(bookmark)
1189 else:
1195 else:
1190 oldnode = None
1196 oldnode = None
1191 bundleheads = bundle.revs(b'heads(bundle())')
1197 bundleheads = bundle.revs(b'heads(bundle())')
1192 if bookmark and len(bundleheads) > 1:
1198 if bookmark and len(bundleheads) > 1:
1193 raise error.Abort(
1199 raise error.Abort(
1194 _(b'cannot push more than one head to a scratch branch')
1200 _(b'cannot push more than one head to a scratch branch')
1195 )
1201 )
1196
1202
1197 revs = _getrevs(bundle, oldnode, force, bookmark)
1203 revs = _getrevs(bundle, oldnode, force, bookmark)
1198
1204
1199 # Notify the user of what is being pushed
1205 # Notify the user of what is being pushed
1200 plural = b's' if len(revs) > 1 else b''
1206 plural = b's' if len(revs) > 1 else b''
1201 op.repo.ui.warn(_(b"pushing %d commit%s:\n") % (len(revs), plural))
1207 op.repo.ui.warn(_(b"pushing %d commit%s:\n") % (len(revs), plural))
1202 maxoutput = 10
1208 maxoutput = 10
1203 for i in range(0, min(len(revs), maxoutput)):
1209 for i in range(0, min(len(revs), maxoutput)):
1204 firstline = bundle[revs[i]].description().split(b'\n')[0][:50]
1210 firstline = bundle[revs[i]].description().split(b'\n')[0][:50]
1205 op.repo.ui.warn(b" %s %s\n" % (revs[i], firstline))
1211 op.repo.ui.warn(b" %s %s\n" % (revs[i], firstline))
1206
1212
1207 if len(revs) > maxoutput + 1:
1213 if len(revs) > maxoutput + 1:
1208 op.repo.ui.warn(b" ...\n")
1214 op.repo.ui.warn(b" ...\n")
1209 firstline = bundle[revs[-1]].description().split(b'\n')[0][:50]
1215 firstline = bundle[revs[-1]].description().split(b'\n')[0][:50]
1210 op.repo.ui.warn(b" %s %s\n" % (revs[-1], firstline))
1216 op.repo.ui.warn(b" %s %s\n" % (revs[-1], firstline))
1211
1217
1212 nodesctx = [bundle[rev] for rev in revs]
1218 nodesctx = [bundle[rev] for rev in revs]
1213 inindex = lambda rev: bool(index.getbundle(bundle[rev].hex()))
1219 inindex = lambda rev: bool(index.getbundle(bundle[rev].hex()))
1214 if bundleheads:
1220 if bundleheads:
1215 newheadscount = sum(not inindex(rev) for rev in bundleheads)
1221 newheadscount = sum(not inindex(rev) for rev in bundleheads)
1216 else:
1222 else:
1217 newheadscount = 0
1223 newheadscount = 0
1218 # If there's a bookmark specified, there should be only one head,
1224 # If there's a bookmark specified, there should be only one head,
1219 # so we choose the last node, which will be that head.
1225 # so we choose the last node, which will be that head.
1220 # If a bug or malicious client allows there to be a bookmark
1226 # If a bug or malicious client allows there to be a bookmark
1221 # with multiple heads, we will place the bookmark on the last head.
1227 # with multiple heads, we will place the bookmark on the last head.
1222 bookmarknode = nodesctx[-1].hex() if nodesctx else None
1228 bookmarknode = nodesctx[-1].hex() if nodesctx else None
1223 key = None
1229 key = None
1224 if newheadscount:
1230 if newheadscount:
1225 with open(bundlefile, b'rb') as f:
1231 with open(bundlefile, b'rb') as f:
1226 bundledata = f.read()
1232 bundledata = f.read()
1227 with logservicecall(
1233 with logservicecall(
1228 log, b'bundlestore', bundlesize=len(bundledata)
1234 log, b'bundlestore', bundlesize=len(bundledata)
1229 ):
1235 ):
1230 bundlesizelimit = 100 * 1024 * 1024 # 100 MB
1236 bundlesizelimit = 100 * 1024 * 1024 # 100 MB
1231 if len(bundledata) > bundlesizelimit:
1237 if len(bundledata) > bundlesizelimit:
1232 error_msg = (
1238 error_msg = (
1233 b'bundle is too big: %d bytes. '
1239 b'bundle is too big: %d bytes. '
1234 + b'max allowed size is 100 MB'
1240 + b'max allowed size is 100 MB'
1235 )
1241 )
1236 raise error.Abort(error_msg % (len(bundledata),))
1242 raise error.Abort(error_msg % (len(bundledata),))
1237 key = store.write(bundledata)
1243 key = store.write(bundledata)
1238
1244
1239 with logservicecall(log, b'index', newheadscount=newheadscount), index:
1245 with logservicecall(log, b'index', newheadscount=newheadscount), index:
1240 if key:
1246 if key:
1241 index.addbundle(key, nodesctx)
1247 index.addbundle(key, nodesctx)
1242 if bookmark:
1248 if bookmark:
1243 index.addbookmark(bookmark, bookmarknode)
1249 index.addbookmark(bookmark, bookmarknode)
1244 _maybeaddpushbackpart(
1250 _maybeaddpushbackpart(
1245 op, bookmark, bookmarknode, bookprevnode, params
1251 op, bookmark, bookmarknode, bookprevnode, params
1246 )
1252 )
1247 log(
1253 log(
1248 scratchbranchparttype,
1254 scratchbranchparttype,
1249 eventtype=b'success',
1255 eventtype=b'success',
1250 elapsedms=(time.time() - parthandlerstart) * 1000,
1256 elapsedms=(time.time() - parthandlerstart) * 1000,
1251 )
1257 )
1252
1258
1253 except Exception as e:
1259 except Exception as e:
1254 log(
1260 log(
1255 scratchbranchparttype,
1261 scratchbranchparttype,
1256 eventtype=b'failure',
1262 eventtype=b'failure',
1257 elapsedms=(time.time() - parthandlerstart) * 1000,
1263 elapsedms=(time.time() - parthandlerstart) * 1000,
1258 errormsg=stringutil.forcebytestr(e),
1264 errormsg=stringutil.forcebytestr(e),
1259 )
1265 )
1260 raise
1266 raise
1261 finally:
1267 finally:
1262 if bundle:
1268 if bundle:
1263 bundle.close()
1269 bundle.close()
1264
1270
1265
1271
1266 @bundle2.parthandler(
1272 @bundle2.parthandler(
1267 scratchbranchparttype,
1273 scratchbranchparttype,
1268 (
1274 (
1269 b'bookmark',
1275 b'bookmark',
1270 b'bookprevnode',
1276 b'bookprevnode',
1271 b'force',
1277 b'force',
1272 b'pushbackbookmarks',
1278 b'pushbackbookmarks',
1273 b'cgversion',
1279 b'cgversion',
1274 ),
1280 ),
1275 )
1281 )
1276 def bundle2scratchbranch(op, part):
1282 def bundle2scratchbranch(op, part):
1277 '''unbundle a bundle2 part containing a changegroup to store'''
1283 '''unbundle a bundle2 part containing a changegroup to store'''
1278
1284
1279 bundler = bundle2.bundle20(op.repo.ui)
1285 bundler = bundle2.bundle20(op.repo.ui)
1280 cgversion = part.params.get(b'cgversion', b'01')
1286 cgversion = part.params.get(b'cgversion', b'01')
1281 cgpart = bundle2.bundlepart(b'changegroup', data=part.read())
1287 cgpart = bundle2.bundlepart(b'changegroup', data=part.read())
1282 cgpart.addparam(b'version', cgversion)
1288 cgpart.addparam(b'version', cgversion)
1283 bundler.addpart(cgpart)
1289 bundler.addpart(cgpart)
1284 buf = util.chunkbuffer(bundler.getchunks())
1290 buf = util.chunkbuffer(bundler.getchunks())
1285
1291
1286 fd, bundlefile = pycompat.mkstemp()
1292 fd, bundlefile = pycompat.mkstemp()
1287 try:
1293 try:
1288 try:
1294 try:
1289 fp = os.fdopen(fd, 'wb')
1295 fp = os.fdopen(fd, 'wb')
1290 fp.write(buf.read())
1296 fp.write(buf.read())
1291 finally:
1297 finally:
1292 fp.close()
1298 fp.close()
1293 storebundle(op, part.params, bundlefile)
1299 storebundle(op, part.params, bundlefile)
1294 finally:
1300 finally:
1295 try:
1301 try:
1296 os.unlink(bundlefile)
1302 os.unlink(bundlefile)
1297 except OSError as e:
1303 except OSError as e:
1298 if e.errno != errno.ENOENT:
1304 if e.errno != errno.ENOENT:
1299 raise
1305 raise
1300
1306
1301 return 1
1307 return 1
1302
1308
1303
1309
1304 def _maybeaddpushbackpart(op, bookmark, newnode, oldnode, params):
1310 def _maybeaddpushbackpart(op, bookmark, newnode, oldnode, params):
1305 if params.get(b'pushbackbookmarks'):
1311 if params.get(b'pushbackbookmarks'):
1306 if op.reply and b'pushback' in op.reply.capabilities:
1312 if op.reply and b'pushback' in op.reply.capabilities:
1307 params = {
1313 params = {
1308 b'namespace': b'bookmarks',
1314 b'namespace': b'bookmarks',
1309 b'key': bookmark,
1315 b'key': bookmark,
1310 b'new': newnode,
1316 b'new': newnode,
1311 b'old': oldnode,
1317 b'old': oldnode,
1312 }
1318 }
1313 op.reply.newpart(
1319 op.reply.newpart(
1314 b'pushkey', mandatoryparams=pycompat.iteritems(params)
1320 b'pushkey', mandatoryparams=pycompat.iteritems(params)
1315 )
1321 )
1316
1322
1317
1323
1318 def bundle2pushkey(orig, op, part):
1324 def bundle2pushkey(orig, op, part):
1319 """Wrapper of bundle2.handlepushkey()
1325 """Wrapper of bundle2.handlepushkey()
1320
1326
1321 The only goal is to skip calling the original function if flag is set.
1327 The only goal is to skip calling the original function if flag is set.
1322 It's set if infinitepush push is happening.
1328 It's set if infinitepush push is happening.
1323 """
1329 """
1324 if op.records[scratchbranchparttype + b'_skippushkey']:
1330 if op.records[scratchbranchparttype + b'_skippushkey']:
1325 if op.reply is not None:
1331 if op.reply is not None:
1326 rpart = op.reply.newpart(b'reply:pushkey')
1332 rpart = op.reply.newpart(b'reply:pushkey')
1327 rpart.addparam(b'in-reply-to', str(part.id), mandatory=False)
1333 rpart.addparam(b'in-reply-to', str(part.id), mandatory=False)
1328 rpart.addparam(b'return', b'1', mandatory=False)
1334 rpart.addparam(b'return', b'1', mandatory=False)
1329 return 1
1335 return 1
1330
1336
1331 return orig(op, part)
1337 return orig(op, part)
1332
1338
1333
1339
1334 def bundle2handlephases(orig, op, part):
1340 def bundle2handlephases(orig, op, part):
1335 """Wrapper of bundle2.handlephases()
1341 """Wrapper of bundle2.handlephases()
1336
1342
1337 The only goal is to skip calling the original function if flag is set.
1343 The only goal is to skip calling the original function if flag is set.
1338 It's set if infinitepush push is happening.
1344 It's set if infinitepush push is happening.
1339 """
1345 """
1340
1346
1341 if op.records[scratchbranchparttype + b'_skipphaseheads']:
1347 if op.records[scratchbranchparttype + b'_skipphaseheads']:
1342 return
1348 return
1343
1349
1344 return orig(op, part)
1350 return orig(op, part)
1345
1351
1346
1352
1347 def _asyncsavemetadata(root, nodes):
1353 def _asyncsavemetadata(root, nodes):
1348 """starts a separate process that fills metadata for the nodes
1354 """starts a separate process that fills metadata for the nodes
1349
1355
1350 This function creates a separate process and doesn't wait for it's
1356 This function creates a separate process and doesn't wait for it's
1351 completion. This was done to avoid slowing down pushes
1357 completion. This was done to avoid slowing down pushes
1352 """
1358 """
1353
1359
1354 maxnodes = 50
1360 maxnodes = 50
1355 if len(nodes) > maxnodes:
1361 if len(nodes) > maxnodes:
1356 return
1362 return
1357 nodesargs = []
1363 nodesargs = []
1358 for node in nodes:
1364 for node in nodes:
1359 nodesargs.append(b'--node')
1365 nodesargs.append(b'--node')
1360 nodesargs.append(node)
1366 nodesargs.append(node)
1361 with open(os.devnull, b'w+b') as devnull:
1367 with open(os.devnull, b'w+b') as devnull:
1362 cmdline = [
1368 cmdline = [
1363 util.hgexecutable(),
1369 util.hgexecutable(),
1364 b'debugfillinfinitepushmetadata',
1370 b'debugfillinfinitepushmetadata',
1365 b'-R',
1371 b'-R',
1366 root,
1372 root,
1367 ] + nodesargs
1373 ] + nodesargs
1368 # Process will run in background. We don't care about the return code
1374 # Process will run in background. We don't care about the return code
1369 subprocess.Popen(
1375 subprocess.Popen(
1370 pycompat.rapply(procutil.tonativestr, cmdline),
1376 pycompat.rapply(procutil.tonativestr, cmdline),
1371 close_fds=True,
1377 close_fds=True,
1372 shell=False,
1378 shell=False,
1373 stdin=devnull,
1379 stdin=devnull,
1374 stdout=devnull,
1380 stdout=devnull,
1375 stderr=devnull,
1381 stderr=devnull,
1376 )
1382 )
@@ -1,671 +1,677 b''
1 # narrowcommands.py - command modifications for narrowhg extension
1 # narrowcommands.py - command modifications for narrowhg extension
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
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 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import itertools
9 import itertools
10 import os
10 import os
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial.node import (
13 from mercurial.node import (
14 hex,
14 hex,
15 nullid,
15 nullid,
16 short,
16 short,
17 )
17 )
18 from mercurial import (
18 from mercurial import (
19 bundle2,
19 bundle2,
20 cmdutil,
20 cmdutil,
21 commands,
21 commands,
22 discovery,
22 discovery,
23 encoding,
23 encoding,
24 error,
24 error,
25 exchange,
25 exchange,
26 extensions,
26 extensions,
27 hg,
27 hg,
28 narrowspec,
28 narrowspec,
29 pathutil,
29 pathutil,
30 pycompat,
30 pycompat,
31 registrar,
31 registrar,
32 repair,
32 repair,
33 repoview,
33 repoview,
34 requirements,
34 requirements,
35 sparse,
35 sparse,
36 util,
36 util,
37 wireprototypes,
37 wireprototypes,
38 )
38 )
39
39
40 table = {}
40 table = {}
41 command = registrar.command(table)
41 command = registrar.command(table)
42
42
43
43
44 def setup():
44 def setup():
45 """Wraps user-facing mercurial commands with narrow-aware versions."""
45 """Wraps user-facing mercurial commands with narrow-aware versions."""
46
46
47 entry = extensions.wrapcommand(commands.table, b'clone', clonenarrowcmd)
47 entry = extensions.wrapcommand(commands.table, b'clone', clonenarrowcmd)
48 entry[1].append(
48 entry[1].append(
49 (b'', b'narrow', None, _(b"create a narrow clone of select files"))
49 (b'', b'narrow', None, _(b"create a narrow clone of select files"))
50 )
50 )
51 entry[1].append(
51 entry[1].append(
52 (
52 (
53 b'',
53 b'',
54 b'depth',
54 b'depth',
55 b'',
55 b'',
56 _(b"limit the history fetched by distance from heads"),
56 _(b"limit the history fetched by distance from heads"),
57 )
57 )
58 )
58 )
59 entry[1].append((b'', b'narrowspec', b'', _(b"read narrowspecs from file")))
59 entry[1].append((b'', b'narrowspec', b'', _(b"read narrowspecs from file")))
60 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
60 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
61 if b'sparse' not in extensions.enabled():
61 if b'sparse' not in extensions.enabled():
62 entry[1].append(
62 entry[1].append(
63 (b'', b'include', [], _(b"specifically fetch this file/directory"))
63 (b'', b'include', [], _(b"specifically fetch this file/directory"))
64 )
64 )
65 entry[1].append(
65 entry[1].append(
66 (
66 (
67 b'',
67 b'',
68 b'exclude',
68 b'exclude',
69 [],
69 [],
70 _(b"do not fetch this file/directory, even if included"),
70 _(b"do not fetch this file/directory, even if included"),
71 )
71 )
72 )
72 )
73
73
74 entry = extensions.wrapcommand(commands.table, b'pull', pullnarrowcmd)
74 entry = extensions.wrapcommand(commands.table, b'pull', pullnarrowcmd)
75 entry[1].append(
75 entry[1].append(
76 (
76 (
77 b'',
77 b'',
78 b'depth',
78 b'depth',
79 b'',
79 b'',
80 _(b"limit the history fetched by distance from heads"),
80 _(b"limit the history fetched by distance from heads"),
81 )
81 )
82 )
82 )
83
83
84 extensions.wrapcommand(commands.table, b'archive', archivenarrowcmd)
84 extensions.wrapcommand(commands.table, b'archive', archivenarrowcmd)
85
85
86
86
87 def clonenarrowcmd(orig, ui, repo, *args, **opts):
87 def clonenarrowcmd(orig, ui, repo, *args, **opts):
88 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
88 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
89 opts = pycompat.byteskwargs(opts)
89 opts = pycompat.byteskwargs(opts)
90 wrappedextraprepare = util.nullcontextmanager()
90 wrappedextraprepare = util.nullcontextmanager()
91 narrowspecfile = opts[b'narrowspec']
91 narrowspecfile = opts[b'narrowspec']
92
92
93 if narrowspecfile:
93 if narrowspecfile:
94 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
94 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
95 ui.status(_(b"reading narrowspec from '%s'\n") % filepath)
95 ui.status(_(b"reading narrowspec from '%s'\n") % filepath)
96 try:
96 try:
97 fdata = util.readfile(filepath)
97 fdata = util.readfile(filepath)
98 except IOError as inst:
98 except IOError as inst:
99 raise error.Abort(
99 raise error.Abort(
100 _(b"cannot read narrowspecs from '%s': %s")
100 _(b"cannot read narrowspecs from '%s': %s")
101 % (filepath, encoding.strtolocal(inst.strerror))
101 % (filepath, encoding.strtolocal(inst.strerror))
102 )
102 )
103
103
104 includes, excludes, profiles = sparse.parseconfig(ui, fdata, b'narrow')
104 includes, excludes, profiles = sparse.parseconfig(ui, fdata, b'narrow')
105 if profiles:
105 if profiles:
106 raise error.ConfigError(
106 raise error.ConfigError(
107 _(
107 _(
108 b"cannot specify other files using '%include' in"
108 b"cannot specify other files using '%include' in"
109 b" narrowspec"
109 b" narrowspec"
110 )
110 )
111 )
111 )
112
112
113 narrowspec.validatepatterns(includes)
113 narrowspec.validatepatterns(includes)
114 narrowspec.validatepatterns(excludes)
114 narrowspec.validatepatterns(excludes)
115
115
116 # narrowspec is passed so we should assume that user wants narrow clone
116 # narrowspec is passed so we should assume that user wants narrow clone
117 opts[b'narrow'] = True
117 opts[b'narrow'] = True
118 opts[b'include'].extend(includes)
118 opts[b'include'].extend(includes)
119 opts[b'exclude'].extend(excludes)
119 opts[b'exclude'].extend(excludes)
120
120
121 if opts[b'narrow']:
121 if opts[b'narrow']:
122
122
123 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
123 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
124 orig(pullop, kwargs)
124 orig(pullop, kwargs)
125
125
126 if opts.get(b'depth'):
126 if opts.get(b'depth'):
127 kwargs[b'depth'] = opts[b'depth']
127 kwargs[b'depth'] = opts[b'depth']
128
128
129 wrappedextraprepare = extensions.wrappedfunction(
129 wrappedextraprepare = extensions.wrappedfunction(
130 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
130 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
131 )
131 )
132
132
133 with wrappedextraprepare:
133 with wrappedextraprepare:
134 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
134 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
135
135
136
136
137 def pullnarrowcmd(orig, ui, repo, *args, **opts):
137 def pullnarrowcmd(orig, ui, repo, *args, **opts):
138 """Wraps pull command to allow modifying narrow spec."""
138 """Wraps pull command to allow modifying narrow spec."""
139 wrappedextraprepare = util.nullcontextmanager()
139 wrappedextraprepare = util.nullcontextmanager()
140 if requirements.NARROW_REQUIREMENT in repo.requirements:
140 if requirements.NARROW_REQUIREMENT in repo.requirements:
141
141
142 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
142 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
143 orig(pullop, kwargs)
143 orig(pullop, kwargs)
144 if opts.get('depth'):
144 if opts.get('depth'):
145 kwargs[b'depth'] = opts['depth']
145 kwargs[b'depth'] = opts['depth']
146
146
147 wrappedextraprepare = extensions.wrappedfunction(
147 wrappedextraprepare = extensions.wrappedfunction(
148 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
148 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
149 )
149 )
150
150
151 with wrappedextraprepare:
151 with wrappedextraprepare:
152 return orig(ui, repo, *args, **opts)
152 return orig(ui, repo, *args, **opts)
153
153
154
154
155 def archivenarrowcmd(orig, ui, repo, *args, **opts):
155 def archivenarrowcmd(orig, ui, repo, *args, **opts):
156 """Wraps archive command to narrow the default includes."""
156 """Wraps archive command to narrow the default includes."""
157 if requirements.NARROW_REQUIREMENT in repo.requirements:
157 if requirements.NARROW_REQUIREMENT in repo.requirements:
158 repo_includes, repo_excludes = repo.narrowpats
158 repo_includes, repo_excludes = repo.narrowpats
159 includes = set(opts.get('include', []))
159 includes = set(opts.get('include', []))
160 excludes = set(opts.get('exclude', []))
160 excludes = set(opts.get('exclude', []))
161 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
161 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
162 includes, excludes, repo_includes, repo_excludes
162 includes, excludes, repo_includes, repo_excludes
163 )
163 )
164 if includes:
164 if includes:
165 opts['include'] = includes
165 opts['include'] = includes
166 if excludes:
166 if excludes:
167 opts['exclude'] = excludes
167 opts['exclude'] = excludes
168 return orig(ui, repo, *args, **opts)
168 return orig(ui, repo, *args, **opts)
169
169
170
170
171 def pullbundle2extraprepare(orig, pullop, kwargs):
171 def pullbundle2extraprepare(orig, pullop, kwargs):
172 repo = pullop.repo
172 repo = pullop.repo
173 if requirements.NARROW_REQUIREMENT not in repo.requirements:
173 if requirements.NARROW_REQUIREMENT not in repo.requirements:
174 return orig(pullop, kwargs)
174 return orig(pullop, kwargs)
175
175
176 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
176 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
177 raise error.Abort(_(b"server does not support narrow clones"))
177 raise error.Abort(_(b"server does not support narrow clones"))
178 orig(pullop, kwargs)
178 orig(pullop, kwargs)
179 kwargs[b'narrow'] = True
179 kwargs[b'narrow'] = True
180 include, exclude = repo.narrowpats
180 include, exclude = repo.narrowpats
181 kwargs[b'oldincludepats'] = include
181 kwargs[b'oldincludepats'] = include
182 kwargs[b'oldexcludepats'] = exclude
182 kwargs[b'oldexcludepats'] = exclude
183 if include:
183 if include:
184 kwargs[b'includepats'] = include
184 kwargs[b'includepats'] = include
185 if exclude:
185 if exclude:
186 kwargs[b'excludepats'] = exclude
186 kwargs[b'excludepats'] = exclude
187 # calculate known nodes only in ellipses cases because in non-ellipses cases
187 # calculate known nodes only in ellipses cases because in non-ellipses cases
188 # we have all the nodes
188 # we have all the nodes
189 if wireprototypes.ELLIPSESCAP1 in pullop.remote.capabilities():
189 if wireprototypes.ELLIPSESCAP1 in pullop.remote.capabilities():
190 kwargs[b'known'] = [
190 kwargs[b'known'] = [
191 hex(ctx.node())
191 hex(ctx.node())
192 for ctx in repo.set(b'::%ln', pullop.common)
192 for ctx in repo.set(b'::%ln', pullop.common)
193 if ctx.node() != nullid
193 if ctx.node() != nullid
194 ]
194 ]
195 if not kwargs[b'known']:
195 if not kwargs[b'known']:
196 # Mercurial serializes an empty list as '' and deserializes it as
196 # Mercurial serializes an empty list as '' and deserializes it as
197 # [''], so delete it instead to avoid handling the empty string on
197 # [''], so delete it instead to avoid handling the empty string on
198 # the server.
198 # the server.
199 del kwargs[b'known']
199 del kwargs[b'known']
200
200
201
201
202 extensions.wrapfunction(
202 extensions.wrapfunction(
203 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare
203 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare
204 )
204 )
205
205
206
206
207 def _narrow(
207 def _narrow(
208 ui,
208 ui,
209 repo,
209 repo,
210 remote,
210 remote,
211 commoninc,
211 commoninc,
212 oldincludes,
212 oldincludes,
213 oldexcludes,
213 oldexcludes,
214 newincludes,
214 newincludes,
215 newexcludes,
215 newexcludes,
216 force,
216 force,
217 backup,
217 backup,
218 ):
218 ):
219 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
219 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
220 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
220 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
221
221
222 # This is essentially doing "hg outgoing" to find all local-only
222 # This is essentially doing "hg outgoing" to find all local-only
223 # commits. We will then check that the local-only commits don't
223 # commits. We will then check that the local-only commits don't
224 # have any changes to files that will be untracked.
224 # have any changes to files that will be untracked.
225 unfi = repo.unfiltered()
225 unfi = repo.unfiltered()
226 outgoing = discovery.findcommonoutgoing(unfi, remote, commoninc=commoninc)
226 outgoing = discovery.findcommonoutgoing(unfi, remote, commoninc=commoninc)
227 ui.status(_(b'looking for local changes to affected paths\n'))
227 ui.status(_(b'looking for local changes to affected paths\n'))
228 localnodes = []
228 localnodes = []
229 for n in itertools.chain(outgoing.missing, outgoing.excluded):
229 for n in itertools.chain(outgoing.missing, outgoing.excluded):
230 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
230 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
231 localnodes.append(n)
231 localnodes.append(n)
232 revstostrip = unfi.revs(b'descendants(%ln)', localnodes)
232 revstostrip = unfi.revs(b'descendants(%ln)', localnodes)
233 hiddenrevs = repoview.filterrevs(repo, b'visible')
233 hiddenrevs = repoview.filterrevs(repo, b'visible')
234 visibletostrip = list(
234 visibletostrip = list(
235 repo.changelog.node(r) for r in (revstostrip - hiddenrevs)
235 repo.changelog.node(r) for r in (revstostrip - hiddenrevs)
236 )
236 )
237 if visibletostrip:
237 if visibletostrip:
238 ui.status(
238 ui.status(
239 _(
239 _(
240 b'The following changeset(s) or their ancestors have '
240 b'The following changeset(s) or their ancestors have '
241 b'local changes not on the remote:\n'
241 b'local changes not on the remote:\n'
242 )
242 )
243 )
243 )
244 maxnodes = 10
244 maxnodes = 10
245 if ui.verbose or len(visibletostrip) <= maxnodes:
245 if ui.verbose or len(visibletostrip) <= maxnodes:
246 for n in visibletostrip:
246 for n in visibletostrip:
247 ui.status(b'%s\n' % short(n))
247 ui.status(b'%s\n' % short(n))
248 else:
248 else:
249 for n in visibletostrip[:maxnodes]:
249 for n in visibletostrip[:maxnodes]:
250 ui.status(b'%s\n' % short(n))
250 ui.status(b'%s\n' % short(n))
251 ui.status(
251 ui.status(
252 _(b'...and %d more, use --verbose to list all\n')
252 _(b'...and %d more, use --verbose to list all\n')
253 % (len(visibletostrip) - maxnodes)
253 % (len(visibletostrip) - maxnodes)
254 )
254 )
255 if not force:
255 if not force:
256 raise error.StateError(
256 raise error.StateError(
257 _(b'local changes found'),
257 _(b'local changes found'),
258 hint=_(b'use --force-delete-local-changes to ignore'),
258 hint=_(b'use --force-delete-local-changes to ignore'),
259 )
259 )
260
260
261 with ui.uninterruptible():
261 with ui.uninterruptible():
262 if revstostrip:
262 if revstostrip:
263 tostrip = [unfi.changelog.node(r) for r in revstostrip]
263 tostrip = [unfi.changelog.node(r) for r in revstostrip]
264 if repo[b'.'].node() in tostrip:
264 if repo[b'.'].node() in tostrip:
265 # stripping working copy, so move to a different commit first
265 # stripping working copy, so move to a different commit first
266 urev = max(
266 urev = max(
267 repo.revs(
267 repo.revs(
268 b'(::%n) - %ln + null',
268 b'(::%n) - %ln + null',
269 repo[b'.'].node(),
269 repo[b'.'].node(),
270 visibletostrip,
270 visibletostrip,
271 )
271 )
272 )
272 )
273 hg.clean(repo, urev)
273 hg.clean(repo, urev)
274 overrides = {(b'devel', b'strip-obsmarkers'): False}
274 overrides = {(b'devel', b'strip-obsmarkers'): False}
275 with ui.configoverride(overrides, b'narrow'):
275 with ui.configoverride(overrides, b'narrow'):
276 repair.strip(ui, unfi, tostrip, topic=b'narrow', backup=backup)
276 repair.strip(ui, unfi, tostrip, topic=b'narrow', backup=backup)
277
277
278 todelete = []
278 todelete = []
279 for f, f2, size in repo.store.datafiles():
279 for f, f2, size in repo.store.datafiles():
280 if f.startswith(b'data/'):
280 if f.startswith(b'data/'):
281 file = f[5:-2]
281 file = f[5:-2]
282 if not newmatch(file):
282 if not newmatch(file):
283 todelete.append(f)
283 todelete.append(f)
284 elif f.startswith(b'meta/'):
284 elif f.startswith(b'meta/'):
285 dir = f[5:-13]
285 dir = f[5:-13]
286 dirs = sorted(pathutil.dirs({dir})) + [dir]
286 dirs = sorted(pathutil.dirs({dir})) + [dir]
287 include = True
287 include = True
288 for d in dirs:
288 for d in dirs:
289 visit = newmatch.visitdir(d)
289 visit = newmatch.visitdir(d)
290 if not visit:
290 if not visit:
291 include = False
291 include = False
292 break
292 break
293 if visit == b'all':
293 if visit == b'all':
294 break
294 break
295 if not include:
295 if not include:
296 todelete.append(f)
296 todelete.append(f)
297
297
298 repo.destroying()
298 repo.destroying()
299
299
300 with repo.transaction(b'narrowing'):
300 with repo.transaction(b'narrowing'):
301 # Update narrowspec before removing revlogs, so repo won't be
301 # Update narrowspec before removing revlogs, so repo won't be
302 # corrupt in case of crash
302 # corrupt in case of crash
303 repo.setnarrowpats(newincludes, newexcludes)
303 repo.setnarrowpats(newincludes, newexcludes)
304
304
305 for f in todelete:
305 for f in todelete:
306 ui.status(_(b'deleting %s\n') % f)
306 ui.status(_(b'deleting %s\n') % f)
307 util.unlinkpath(repo.svfs.join(f))
307 util.unlinkpath(repo.svfs.join(f))
308 repo.store.markremoved(f)
308 repo.store.markremoved(f)
309
309
310 narrowspec.updateworkingcopy(repo, assumeclean=True)
310 narrowspec.updateworkingcopy(repo, assumeclean=True)
311 narrowspec.copytoworkingcopy(repo)
311 narrowspec.copytoworkingcopy(repo)
312
312
313 repo.destroyed()
313 repo.destroyed()
314
314
315
315
316 def _widen(
316 def _widen(
317 ui,
317 ui,
318 repo,
318 repo,
319 remote,
319 remote,
320 commoninc,
320 commoninc,
321 oldincludes,
321 oldincludes,
322 oldexcludes,
322 oldexcludes,
323 newincludes,
323 newincludes,
324 newexcludes,
324 newexcludes,
325 ):
325 ):
326 # for now we assume that if a server has ellipses enabled, we will be
326 # for now we assume that if a server has ellipses enabled, we will be
327 # exchanging ellipses nodes. In future we should add ellipses as a client
327 # exchanging ellipses nodes. In future we should add ellipses as a client
328 # side requirement (maybe) to distinguish a client is shallow or not and
328 # side requirement (maybe) to distinguish a client is shallow or not and
329 # then send that information to server whether we want ellipses or not.
329 # then send that information to server whether we want ellipses or not.
330 # Theoretically a non-ellipses repo should be able to use narrow
330 # Theoretically a non-ellipses repo should be able to use narrow
331 # functionality from an ellipses enabled server
331 # functionality from an ellipses enabled server
332 remotecap = remote.capabilities()
332 remotecap = remote.capabilities()
333 ellipsesremote = any(
333 ellipsesremote = any(
334 cap in remotecap for cap in wireprototypes.SUPPORTED_ELLIPSESCAP
334 cap in remotecap for cap in wireprototypes.SUPPORTED_ELLIPSESCAP
335 )
335 )
336
336
337 # check whether we are talking to a server which supports old version of
337 # check whether we are talking to a server which supports old version of
338 # ellipses capabilities
338 # ellipses capabilities
339 isoldellipses = (
339 isoldellipses = (
340 ellipsesremote
340 ellipsesremote
341 and wireprototypes.ELLIPSESCAP1 in remotecap
341 and wireprototypes.ELLIPSESCAP1 in remotecap
342 and wireprototypes.ELLIPSESCAP not in remotecap
342 and wireprototypes.ELLIPSESCAP not in remotecap
343 )
343 )
344
344
345 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
345 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
346 orig(pullop, kwargs)
346 orig(pullop, kwargs)
347 # The old{in,ex}cludepats have already been set by orig()
347 # The old{in,ex}cludepats have already been set by orig()
348 kwargs[b'includepats'] = newincludes
348 kwargs[b'includepats'] = newincludes
349 kwargs[b'excludepats'] = newexcludes
349 kwargs[b'excludepats'] = newexcludes
350
350
351 wrappedextraprepare = extensions.wrappedfunction(
351 wrappedextraprepare = extensions.wrappedfunction(
352 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
352 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
353 )
353 )
354
354
355 # define a function that narrowbundle2 can call after creating the
355 # define a function that narrowbundle2 can call after creating the
356 # backup bundle, but before applying the bundle from the server
356 # backup bundle, but before applying the bundle from the server
357 def setnewnarrowpats():
357 def setnewnarrowpats():
358 repo.setnarrowpats(newincludes, newexcludes)
358 repo.setnarrowpats(newincludes, newexcludes)
359
359
360 repo.setnewnarrowpats = setnewnarrowpats
360 repo.setnewnarrowpats = setnewnarrowpats
361 # silence the devel-warning of applying an empty changegroup
361 # silence the devel-warning of applying an empty changegroup
362 overrides = {(b'devel', b'all-warnings'): False}
362 overrides = {(b'devel', b'all-warnings'): False}
363
363
364 common = commoninc[0]
364 common = commoninc[0]
365 with ui.uninterruptible():
365 with ui.uninterruptible():
366 if ellipsesremote:
366 if ellipsesremote:
367 ds = repo.dirstate
367 ds = repo.dirstate
368 p1, p2 = ds.p1(), ds.p2()
368 p1, p2 = ds.p1(), ds.p2()
369 with ds.parentchange():
369 with ds.parentchange():
370 ds.setparents(nullid, nullid)
370 ds.setparents(nullid, nullid)
371 if isoldellipses:
371 if isoldellipses:
372 with wrappedextraprepare:
372 with wrappedextraprepare:
373 exchange.pull(repo, remote, heads=common)
373 exchange.pull(repo, remote, heads=common)
374 else:
374 else:
375 known = []
375 known = []
376 if ellipsesremote:
376 if ellipsesremote:
377 known = [
377 known = [
378 ctx.node()
378 ctx.node()
379 for ctx in repo.set(b'::%ln', common)
379 for ctx in repo.set(b'::%ln', common)
380 if ctx.node() != nullid
380 if ctx.node() != nullid
381 ]
381 ]
382 with remote.commandexecutor() as e:
382 with remote.commandexecutor() as e:
383 bundle = e.callcommand(
383 bundle = e.callcommand(
384 b'narrow_widen',
384 b'narrow_widen',
385 {
385 {
386 b'oldincludes': oldincludes,
386 b'oldincludes': oldincludes,
387 b'oldexcludes': oldexcludes,
387 b'oldexcludes': oldexcludes,
388 b'newincludes': newincludes,
388 b'newincludes': newincludes,
389 b'newexcludes': newexcludes,
389 b'newexcludes': newexcludes,
390 b'cgversion': b'03',
390 b'cgversion': b'03',
391 b'commonheads': common,
391 b'commonheads': common,
392 b'known': known,
392 b'known': known,
393 b'ellipses': ellipsesremote,
393 b'ellipses': ellipsesremote,
394 },
394 },
395 ).result()
395 ).result()
396
396
397 trmanager = exchange.transactionmanager(
397 trmanager = exchange.transactionmanager(
398 repo, b'widen', remote.url()
398 repo, b'widen', remote.url()
399 )
399 )
400 with trmanager, repo.ui.configoverride(overrides, b'widen'):
400 with trmanager, repo.ui.configoverride(overrides, b'widen'):
401 op = bundle2.bundleoperation(
401 op = bundle2.bundleoperation(
402 repo, trmanager.transaction, source=b'widen'
402 repo, trmanager.transaction, source=b'widen'
403 )
403 )
404 # TODO: we should catch error.Abort here
404 # TODO: we should catch error.Abort here
405 bundle2.processbundle(repo, bundle, op=op)
405 bundle2.processbundle(repo, bundle, op=op)
406
406
407 if ellipsesremote:
407 if ellipsesremote:
408 with ds.parentchange():
408 with ds.parentchange():
409 ds.setparents(p1, p2)
409 ds.setparents(p1, p2)
410
410
411 with repo.transaction(b'widening'):
411 with repo.transaction(b'widening'):
412 repo.setnewnarrowpats()
412 repo.setnewnarrowpats()
413 narrowspec.updateworkingcopy(repo)
413 narrowspec.updateworkingcopy(repo)
414 narrowspec.copytoworkingcopy(repo)
414 narrowspec.copytoworkingcopy(repo)
415
415
416
416
417 # TODO(rdamazio): Make new matcher format and update description
417 # TODO(rdamazio): Make new matcher format and update description
418 @command(
418 @command(
419 b'tracked',
419 b'tracked',
420 [
420 [
421 (b'', b'addinclude', [], _(b'new paths to include')),
421 (b'', b'addinclude', [], _(b'new paths to include')),
422 (b'', b'removeinclude', [], _(b'old paths to no longer include')),
422 (b'', b'removeinclude', [], _(b'old paths to no longer include')),
423 (
423 (
424 b'',
424 b'',
425 b'auto-remove-includes',
425 b'auto-remove-includes',
426 False,
426 False,
427 _(b'automatically choose unused includes to remove'),
427 _(b'automatically choose unused includes to remove'),
428 ),
428 ),
429 (b'', b'addexclude', [], _(b'new paths to exclude')),
429 (b'', b'addexclude', [], _(b'new paths to exclude')),
430 (b'', b'import-rules', b'', _(b'import narrowspecs from a file')),
430 (b'', b'import-rules', b'', _(b'import narrowspecs from a file')),
431 (b'', b'removeexclude', [], _(b'old paths to no longer exclude')),
431 (b'', b'removeexclude', [], _(b'old paths to no longer exclude')),
432 (
432 (
433 b'',
433 b'',
434 b'clear',
434 b'clear',
435 False,
435 False,
436 _(b'whether to replace the existing narrowspec'),
436 _(b'whether to replace the existing narrowspec'),
437 ),
437 ),
438 (
438 (
439 b'',
439 b'',
440 b'force-delete-local-changes',
440 b'force-delete-local-changes',
441 False,
441 False,
442 _(b'forces deletion of local changes when narrowing'),
442 _(b'forces deletion of local changes when narrowing'),
443 ),
443 ),
444 (
444 (
445 b'',
445 b'',
446 b'backup',
446 b'backup',
447 True,
447 True,
448 _(b'back up local changes when narrowing'),
448 _(b'back up local changes when narrowing'),
449 ),
449 ),
450 (
450 (
451 b'',
451 b'',
452 b'update-working-copy',
452 b'update-working-copy',
453 False,
453 False,
454 _(b'update working copy when the store has changed'),
454 _(b'update working copy when the store has changed'),
455 ),
455 ),
456 ]
456 ]
457 + commands.remoteopts,
457 + commands.remoteopts,
458 _(b'[OPTIONS]... [REMOTE]'),
458 _(b'[OPTIONS]... [REMOTE]'),
459 inferrepo=True,
459 inferrepo=True,
460 helpcategory=command.CATEGORY_MAINTENANCE,
460 helpcategory=command.CATEGORY_MAINTENANCE,
461 )
461 )
462 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
462 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
463 """show or change the current narrowspec
463 """show or change the current narrowspec
464
464
465 With no argument, shows the current narrowspec entries, one per line. Each
465 With no argument, shows the current narrowspec entries, one per line. Each
466 line will be prefixed with 'I' or 'X' for included or excluded patterns,
466 line will be prefixed with 'I' or 'X' for included or excluded patterns,
467 respectively.
467 respectively.
468
468
469 The narrowspec is comprised of expressions to match remote files and/or
469 The narrowspec is comprised of expressions to match remote files and/or
470 directories that should be pulled into your client.
470 directories that should be pulled into your client.
471 The narrowspec has *include* and *exclude* expressions, with excludes always
471 The narrowspec has *include* and *exclude* expressions, with excludes always
472 trumping includes: that is, if a file matches an exclude expression, it will
472 trumping includes: that is, if a file matches an exclude expression, it will
473 be excluded even if it also matches an include expression.
473 be excluded even if it also matches an include expression.
474 Excluding files that were never included has no effect.
474 Excluding files that were never included has no effect.
475
475
476 Each included or excluded entry is in the format described by
476 Each included or excluded entry is in the format described by
477 'hg help patterns'.
477 'hg help patterns'.
478
478
479 The options allow you to add or remove included and excluded expressions.
479 The options allow you to add or remove included and excluded expressions.
480
480
481 If --clear is specified, then all previous includes and excludes are DROPPED
481 If --clear is specified, then all previous includes and excludes are DROPPED
482 and replaced by the new ones specified to --addinclude and --addexclude.
482 and replaced by the new ones specified to --addinclude and --addexclude.
483 If --clear is specified without any further options, the narrowspec will be
483 If --clear is specified without any further options, the narrowspec will be
484 empty and will not match any files.
484 empty and will not match any files.
485
485
486 If --auto-remove-includes is specified, then those includes that don't match
486 If --auto-remove-includes is specified, then those includes that don't match
487 any files modified by currently visible local commits (those not shared by
487 any files modified by currently visible local commits (those not shared by
488 the remote) will be added to the set of explicitly specified includes to
488 the remote) will be added to the set of explicitly specified includes to
489 remove.
489 remove.
490
490
491 --import-rules accepts a path to a file containing rules, allowing you to
491 --import-rules accepts a path to a file containing rules, allowing you to
492 add --addinclude, --addexclude rules in bulk. Like the other include and
492 add --addinclude, --addexclude rules in bulk. Like the other include and
493 exclude switches, the changes are applied immediately.
493 exclude switches, the changes are applied immediately.
494 """
494 """
495 opts = pycompat.byteskwargs(opts)
495 opts = pycompat.byteskwargs(opts)
496 if requirements.NARROW_REQUIREMENT not in repo.requirements:
496 if requirements.NARROW_REQUIREMENT not in repo.requirements:
497 raise error.InputError(
497 raise error.InputError(
498 _(
498 _(
499 b'the tracked command is only supported on '
499 b'the tracked command is only supported on '
500 b'repositories cloned with --narrow'
500 b'repositories cloned with --narrow'
501 )
501 )
502 )
502 )
503
503
504 # Before supporting, decide whether it "hg tracked --clear" should mean
504 # Before supporting, decide whether it "hg tracked --clear" should mean
505 # tracking no paths or all paths.
505 # tracking no paths or all paths.
506 if opts[b'clear']:
506 if opts[b'clear']:
507 raise error.InputError(_(b'the --clear option is not yet supported'))
507 raise error.InputError(_(b'the --clear option is not yet supported'))
508
508
509 # import rules from a file
509 # import rules from a file
510 newrules = opts.get(b'import_rules')
510 newrules = opts.get(b'import_rules')
511 if newrules:
511 if newrules:
512 try:
512 try:
513 filepath = os.path.join(encoding.getcwd(), newrules)
513 filepath = os.path.join(encoding.getcwd(), newrules)
514 fdata = util.readfile(filepath)
514 fdata = util.readfile(filepath)
515 except IOError as inst:
515 except IOError as inst:
516 raise error.StorageError(
516 raise error.StorageError(
517 _(b"cannot read narrowspecs from '%s': %s")
517 _(b"cannot read narrowspecs from '%s': %s")
518 % (filepath, encoding.strtolocal(inst.strerror))
518 % (filepath, encoding.strtolocal(inst.strerror))
519 )
519 )
520 includepats, excludepats, profiles = sparse.parseconfig(
520 includepats, excludepats, profiles = sparse.parseconfig(
521 ui, fdata, b'narrow'
521 ui, fdata, b'narrow'
522 )
522 )
523 if profiles:
523 if profiles:
524 raise error.InputError(
524 raise error.InputError(
525 _(
525 _(
526 b"including other spec files using '%include' "
526 b"including other spec files using '%include' "
527 b"is not supported in narrowspec"
527 b"is not supported in narrowspec"
528 )
528 )
529 )
529 )
530 opts[b'addinclude'].extend(includepats)
530 opts[b'addinclude'].extend(includepats)
531 opts[b'addexclude'].extend(excludepats)
531 opts[b'addexclude'].extend(excludepats)
532
532
533 addedincludes = narrowspec.parsepatterns(opts[b'addinclude'])
533 addedincludes = narrowspec.parsepatterns(opts[b'addinclude'])
534 removedincludes = narrowspec.parsepatterns(opts[b'removeinclude'])
534 removedincludes = narrowspec.parsepatterns(opts[b'removeinclude'])
535 addedexcludes = narrowspec.parsepatterns(opts[b'addexclude'])
535 addedexcludes = narrowspec.parsepatterns(opts[b'addexclude'])
536 removedexcludes = narrowspec.parsepatterns(opts[b'removeexclude'])
536 removedexcludes = narrowspec.parsepatterns(opts[b'removeexclude'])
537 autoremoveincludes = opts[b'auto_remove_includes']
537 autoremoveincludes = opts[b'auto_remove_includes']
538
538
539 update_working_copy = opts[b'update_working_copy']
539 update_working_copy = opts[b'update_working_copy']
540 only_show = not (
540 only_show = not (
541 addedincludes
541 addedincludes
542 or removedincludes
542 or removedincludes
543 or addedexcludes
543 or addedexcludes
544 or removedexcludes
544 or removedexcludes
545 or newrules
545 or newrules
546 or autoremoveincludes
546 or autoremoveincludes
547 or update_working_copy
547 or update_working_copy
548 )
548 )
549
549
550 oldincludes, oldexcludes = repo.narrowpats
550 oldincludes, oldexcludes = repo.narrowpats
551
551
552 # filter the user passed additions and deletions into actual additions and
552 # filter the user passed additions and deletions into actual additions and
553 # deletions of excludes and includes
553 # deletions of excludes and includes
554 addedincludes -= oldincludes
554 addedincludes -= oldincludes
555 removedincludes &= oldincludes
555 removedincludes &= oldincludes
556 addedexcludes -= oldexcludes
556 addedexcludes -= oldexcludes
557 removedexcludes &= oldexcludes
557 removedexcludes &= oldexcludes
558
558
559 widening = addedincludes or removedexcludes
559 widening = addedincludes or removedexcludes
560 narrowing = removedincludes or addedexcludes
560 narrowing = removedincludes or addedexcludes
561
561
562 # Only print the current narrowspec.
562 # Only print the current narrowspec.
563 if only_show:
563 if only_show:
564 ui.pager(b'tracked')
564 ui.pager(b'tracked')
565 fm = ui.formatter(b'narrow', opts)
565 fm = ui.formatter(b'narrow', opts)
566 for i in sorted(oldincludes):
566 for i in sorted(oldincludes):
567 fm.startitem()
567 fm.startitem()
568 fm.write(b'status', b'%s ', b'I', label=b'narrow.included')
568 fm.write(b'status', b'%s ', b'I', label=b'narrow.included')
569 fm.write(b'pat', b'%s\n', i, label=b'narrow.included')
569 fm.write(b'pat', b'%s\n', i, label=b'narrow.included')
570 for i in sorted(oldexcludes):
570 for i in sorted(oldexcludes):
571 fm.startitem()
571 fm.startitem()
572 fm.write(b'status', b'%s ', b'X', label=b'narrow.excluded')
572 fm.write(b'status', b'%s ', b'X', label=b'narrow.excluded')
573 fm.write(b'pat', b'%s\n', i, label=b'narrow.excluded')
573 fm.write(b'pat', b'%s\n', i, label=b'narrow.excluded')
574 fm.end()
574 fm.end()
575 return 0
575 return 0
576
576
577 if update_working_copy:
577 if update_working_copy:
578 with repo.wlock(), repo.lock(), repo.transaction(b'narrow-wc'):
578 with repo.wlock(), repo.lock(), repo.transaction(b'narrow-wc'):
579 narrowspec.updateworkingcopy(repo)
579 narrowspec.updateworkingcopy(repo)
580 narrowspec.copytoworkingcopy(repo)
580 narrowspec.copytoworkingcopy(repo)
581 return 0
581 return 0
582
582
583 if not (widening or narrowing or autoremoveincludes):
583 if not (widening or narrowing or autoremoveincludes):
584 ui.status(_(b"nothing to widen or narrow\n"))
584 ui.status(_(b"nothing to widen or narrow\n"))
585 return 0
585 return 0
586
586
587 with repo.wlock(), repo.lock():
587 with repo.wlock(), repo.lock():
588 cmdutil.bailifchanged(repo)
588 cmdutil.bailifchanged(repo)
589
589
590 # Find the revisions we have in common with the remote. These will
590 # Find the revisions we have in common with the remote. These will
591 # be used for finding local-only changes for narrowing. They will
591 # be used for finding local-only changes for narrowing. They will
592 # also define the set of revisions to update for widening.
592 # also define the set of revisions to update for widening.
593 remotepath = ui.expandpath(remotepath or b'default')
593 remotepath = ui.expandpath(remotepath or b'default')
594 url, branches = hg.parseurl(remotepath)
594 url, branches = hg.parseurl(remotepath)
595 ui.status(_(b'comparing with %s\n') % util.hidepassword(url))
595 ui.status(_(b'comparing with %s\n') % util.hidepassword(url))
596 remote = hg.peer(repo, opts, url)
596 remote = hg.peer(repo, opts, url)
597
597
598 # check narrow support before doing anything if widening needs to be
598 try:
599 # performed. In future we should also abort if client is ellipses and
599 # check narrow support before doing anything if widening needs to be
600 # server does not support ellipses
600 # performed. In future we should also abort if client is ellipses and
601 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
601 # server does not support ellipses
602 raise error.Abort(_(b"server does not support narrow clones"))
602 if (
603 widening
604 and wireprototypes.NARROWCAP not in remote.capabilities()
605 ):
606 raise error.Abort(_(b"server does not support narrow clones"))
603
607
604 commoninc = discovery.findcommonincoming(repo, remote)
608 commoninc = discovery.findcommonincoming(repo, remote)
605
609
606 if autoremoveincludes:
610 if autoremoveincludes:
607 outgoing = discovery.findcommonoutgoing(
611 outgoing = discovery.findcommonoutgoing(
608 repo, remote, commoninc=commoninc
612 repo, remote, commoninc=commoninc
609 )
613 )
610 ui.status(_(b'looking for unused includes to remove\n'))
614 ui.status(_(b'looking for unused includes to remove\n'))
611 localfiles = set()
615 localfiles = set()
612 for n in itertools.chain(outgoing.missing, outgoing.excluded):
616 for n in itertools.chain(outgoing.missing, outgoing.excluded):
613 localfiles.update(repo[n].files())
617 localfiles.update(repo[n].files())
614 suggestedremovals = []
618 suggestedremovals = []
615 for include in sorted(oldincludes):
619 for include in sorted(oldincludes):
616 match = narrowspec.match(repo.root, [include], oldexcludes)
620 match = narrowspec.match(repo.root, [include], oldexcludes)
617 if not any(match(f) for f in localfiles):
621 if not any(match(f) for f in localfiles):
618 suggestedremovals.append(include)
622 suggestedremovals.append(include)
619 if suggestedremovals:
623 if suggestedremovals:
620 for s in suggestedremovals:
624 for s in suggestedremovals:
621 ui.status(b'%s\n' % s)
625 ui.status(b'%s\n' % s)
622 if (
626 if (
623 ui.promptchoice(
627 ui.promptchoice(
624 _(
628 _(
625 b'remove these unused includes (yn)?'
629 b'remove these unused includes (yn)?'
626 b'$$ &Yes $$ &No'
630 b'$$ &Yes $$ &No'
631 )
627 )
632 )
628 )
633 == 0
629 == 0
634 ):
630 ):
635 removedincludes.update(suggestedremovals)
631 removedincludes.update(suggestedremovals)
636 narrowing = True
632 narrowing = True
637 else:
633 else:
638 ui.status(_(b'found no unused includes\n'))
634 ui.status(_(b'found no unused includes\n'))
635
639
636 if narrowing:
640 if narrowing:
637 newincludes = oldincludes - removedincludes
641 newincludes = oldincludes - removedincludes
638 newexcludes = oldexcludes | addedexcludes
642 newexcludes = oldexcludes | addedexcludes
639 _narrow(
643 _narrow(
640 ui,
644 ui,
641 repo,
645 repo,
642 remote,
646 remote,
643 commoninc,
647 commoninc,
644 oldincludes,
648 oldincludes,
645 oldexcludes,
649 oldexcludes,
646 newincludes,
650 newincludes,
647 newexcludes,
651 newexcludes,
648 opts[b'force_delete_local_changes'],
652 opts[b'force_delete_local_changes'],
649 opts[b'backup'],
653 opts[b'backup'],
650 )
654 )
651 # _narrow() updated the narrowspec and _widen() below needs to
655 # _narrow() updated the narrowspec and _widen() below needs to
652 # use the updated values as its base (otherwise removed includes
656 # use the updated values as its base (otherwise removed includes
653 # and addedexcludes will be lost in the resulting narrowspec)
657 # and addedexcludes will be lost in the resulting narrowspec)
654 oldincludes = newincludes
658 oldincludes = newincludes
655 oldexcludes = newexcludes
659 oldexcludes = newexcludes
656
660
657 if widening:
661 if widening:
658 newincludes = oldincludes | addedincludes
662 newincludes = oldincludes | addedincludes
659 newexcludes = oldexcludes - removedexcludes
663 newexcludes = oldexcludes - removedexcludes
660 _widen(
664 _widen(
661 ui,
665 ui,
662 repo,
666 repo,
663 remote,
667 remote,
664 commoninc,
668 commoninc,
665 oldincludes,
669 oldincludes,
666 oldexcludes,
670 oldexcludes,
667 newincludes,
671 newincludes,
668 newexcludes,
672 newexcludes,
669 )
673 )
674 finally:
675 remote.close()
670
676
671 return 0
677 return 0
@@ -1,7855 +1,7872 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 errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 wdirhex,
21 wdirhex,
22 wdirrev,
22 wdirrev,
23 )
23 )
24 from .pycompat import open
24 from .pycompat import open
25 from . import (
25 from . import (
26 archival,
26 archival,
27 bookmarks,
27 bookmarks,
28 bundle2,
28 bundle2,
29 bundlecaches,
29 bundlecaches,
30 changegroup,
30 changegroup,
31 cmdutil,
31 cmdutil,
32 copies,
32 copies,
33 debugcommands as debugcommandsmod,
33 debugcommands as debugcommandsmod,
34 destutil,
34 destutil,
35 dirstateguard,
35 dirstateguard,
36 discovery,
36 discovery,
37 encoding,
37 encoding,
38 error,
38 error,
39 exchange,
39 exchange,
40 extensions,
40 extensions,
41 filemerge,
41 filemerge,
42 formatter,
42 formatter,
43 graphmod,
43 graphmod,
44 grep as grepmod,
44 grep as grepmod,
45 hbisect,
45 hbisect,
46 help,
46 help,
47 hg,
47 hg,
48 logcmdutil,
48 logcmdutil,
49 merge as mergemod,
49 merge as mergemod,
50 mergestate as mergestatemod,
50 mergestate as mergestatemod,
51 narrowspec,
51 narrowspec,
52 obsolete,
52 obsolete,
53 obsutil,
53 obsutil,
54 patch,
54 patch,
55 phases,
55 phases,
56 pycompat,
56 pycompat,
57 rcutil,
57 rcutil,
58 registrar,
58 registrar,
59 requirements,
59 requirements,
60 revsetlang,
60 revsetlang,
61 rewriteutil,
61 rewriteutil,
62 scmutil,
62 scmutil,
63 server,
63 server,
64 shelve as shelvemod,
64 shelve as shelvemod,
65 state as statemod,
65 state as statemod,
66 streamclone,
66 streamclone,
67 tags as tagsmod,
67 tags as tagsmod,
68 ui as uimod,
68 ui as uimod,
69 util,
69 util,
70 verify as verifymod,
70 verify as verifymod,
71 vfs as vfsmod,
71 vfs as vfsmod,
72 wireprotoserver,
72 wireprotoserver,
73 )
73 )
74 from .utils import (
74 from .utils import (
75 dateutil,
75 dateutil,
76 stringutil,
76 stringutil,
77 )
77 )
78
78
79 table = {}
79 table = {}
80 table.update(debugcommandsmod.command._table)
80 table.update(debugcommandsmod.command._table)
81
81
82 command = registrar.command(table)
82 command = registrar.command(table)
83 INTENT_READONLY = registrar.INTENT_READONLY
83 INTENT_READONLY = registrar.INTENT_READONLY
84
84
85 # common command options
85 # common command options
86
86
87 globalopts = [
87 globalopts = [
88 (
88 (
89 b'R',
89 b'R',
90 b'repository',
90 b'repository',
91 b'',
91 b'',
92 _(b'repository root directory or name of overlay bundle file'),
92 _(b'repository root directory or name of overlay bundle file'),
93 _(b'REPO'),
93 _(b'REPO'),
94 ),
94 ),
95 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
95 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
96 (
96 (
97 b'y',
97 b'y',
98 b'noninteractive',
98 b'noninteractive',
99 None,
99 None,
100 _(
100 _(
101 b'do not prompt, automatically pick the first choice for all prompts'
101 b'do not prompt, automatically pick the first choice for all prompts'
102 ),
102 ),
103 ),
103 ),
104 (b'q', b'quiet', None, _(b'suppress output')),
104 (b'q', b'quiet', None, _(b'suppress output')),
105 (b'v', b'verbose', None, _(b'enable additional output')),
105 (b'v', b'verbose', None, _(b'enable additional output')),
106 (
106 (
107 b'',
107 b'',
108 b'color',
108 b'color',
109 b'',
109 b'',
110 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
110 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
111 # and should not be translated
111 # and should not be translated
112 _(b"when to colorize (boolean, always, auto, never, or debug)"),
112 _(b"when to colorize (boolean, always, auto, never, or debug)"),
113 _(b'TYPE'),
113 _(b'TYPE'),
114 ),
114 ),
115 (
115 (
116 b'',
116 b'',
117 b'config',
117 b'config',
118 [],
118 [],
119 _(b'set/override config option (use \'section.name=value\')'),
119 _(b'set/override config option (use \'section.name=value\')'),
120 _(b'CONFIG'),
120 _(b'CONFIG'),
121 ),
121 ),
122 (b'', b'debug', None, _(b'enable debugging output')),
122 (b'', b'debug', None, _(b'enable debugging output')),
123 (b'', b'debugger', None, _(b'start debugger')),
123 (b'', b'debugger', None, _(b'start debugger')),
124 (
124 (
125 b'',
125 b'',
126 b'encoding',
126 b'encoding',
127 encoding.encoding,
127 encoding.encoding,
128 _(b'set the charset encoding'),
128 _(b'set the charset encoding'),
129 _(b'ENCODE'),
129 _(b'ENCODE'),
130 ),
130 ),
131 (
131 (
132 b'',
132 b'',
133 b'encodingmode',
133 b'encodingmode',
134 encoding.encodingmode,
134 encoding.encodingmode,
135 _(b'set the charset encoding mode'),
135 _(b'set the charset encoding mode'),
136 _(b'MODE'),
136 _(b'MODE'),
137 ),
137 ),
138 (b'', b'traceback', None, _(b'always print a traceback on exception')),
138 (b'', b'traceback', None, _(b'always print a traceback on exception')),
139 (b'', b'time', None, _(b'time how long the command takes')),
139 (b'', b'time', None, _(b'time how long the command takes')),
140 (b'', b'profile', None, _(b'print command execution profile')),
140 (b'', b'profile', None, _(b'print command execution profile')),
141 (b'', b'version', None, _(b'output version information and exit')),
141 (b'', b'version', None, _(b'output version information and exit')),
142 (b'h', b'help', None, _(b'display help and exit')),
142 (b'h', b'help', None, _(b'display help and exit')),
143 (b'', b'hidden', False, _(b'consider hidden changesets')),
143 (b'', b'hidden', False, _(b'consider hidden changesets')),
144 (
144 (
145 b'',
145 b'',
146 b'pager',
146 b'pager',
147 b'auto',
147 b'auto',
148 _(b"when to paginate (boolean, always, auto, or never)"),
148 _(b"when to paginate (boolean, always, auto, or never)"),
149 _(b'TYPE'),
149 _(b'TYPE'),
150 ),
150 ),
151 ]
151 ]
152
152
153 dryrunopts = cmdutil.dryrunopts
153 dryrunopts = cmdutil.dryrunopts
154 remoteopts = cmdutil.remoteopts
154 remoteopts = cmdutil.remoteopts
155 walkopts = cmdutil.walkopts
155 walkopts = cmdutil.walkopts
156 commitopts = cmdutil.commitopts
156 commitopts = cmdutil.commitopts
157 commitopts2 = cmdutil.commitopts2
157 commitopts2 = cmdutil.commitopts2
158 commitopts3 = cmdutil.commitopts3
158 commitopts3 = cmdutil.commitopts3
159 formatteropts = cmdutil.formatteropts
159 formatteropts = cmdutil.formatteropts
160 templateopts = cmdutil.templateopts
160 templateopts = cmdutil.templateopts
161 logopts = cmdutil.logopts
161 logopts = cmdutil.logopts
162 diffopts = cmdutil.diffopts
162 diffopts = cmdutil.diffopts
163 diffwsopts = cmdutil.diffwsopts
163 diffwsopts = cmdutil.diffwsopts
164 diffopts2 = cmdutil.diffopts2
164 diffopts2 = cmdutil.diffopts2
165 mergetoolopts = cmdutil.mergetoolopts
165 mergetoolopts = cmdutil.mergetoolopts
166 similarityopts = cmdutil.similarityopts
166 similarityopts = cmdutil.similarityopts
167 subrepoopts = cmdutil.subrepoopts
167 subrepoopts = cmdutil.subrepoopts
168 debugrevlogopts = cmdutil.debugrevlogopts
168 debugrevlogopts = cmdutil.debugrevlogopts
169
169
170 # Commands start here, listed alphabetically
170 # Commands start here, listed alphabetically
171
171
172
172
173 @command(
173 @command(
174 b'abort',
174 b'abort',
175 dryrunopts,
175 dryrunopts,
176 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
176 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
177 helpbasic=True,
177 helpbasic=True,
178 )
178 )
179 def abort(ui, repo, **opts):
179 def abort(ui, repo, **opts):
180 """abort an unfinished operation (EXPERIMENTAL)
180 """abort an unfinished operation (EXPERIMENTAL)
181
181
182 Aborts a multistep operation like graft, histedit, rebase, merge,
182 Aborts a multistep operation like graft, histedit, rebase, merge,
183 and unshelve if they are in an unfinished state.
183 and unshelve if they are in an unfinished state.
184
184
185 use --dry-run/-n to dry run the command.
185 use --dry-run/-n to dry run the command.
186 """
186 """
187 dryrun = opts.get('dry_run')
187 dryrun = opts.get('dry_run')
188 abortstate = cmdutil.getunfinishedstate(repo)
188 abortstate = cmdutil.getunfinishedstate(repo)
189 if not abortstate:
189 if not abortstate:
190 raise error.StateError(_(b'no operation in progress'))
190 raise error.StateError(_(b'no operation in progress'))
191 if not abortstate.abortfunc:
191 if not abortstate.abortfunc:
192 raise error.InputError(
192 raise error.InputError(
193 (
193 (
194 _(b"%s in progress but does not support 'hg abort'")
194 _(b"%s in progress but does not support 'hg abort'")
195 % (abortstate._opname)
195 % (abortstate._opname)
196 ),
196 ),
197 hint=abortstate.hint(),
197 hint=abortstate.hint(),
198 )
198 )
199 if dryrun:
199 if dryrun:
200 ui.status(
200 ui.status(
201 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
201 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
202 )
202 )
203 return
203 return
204 return abortstate.abortfunc(ui, repo)
204 return abortstate.abortfunc(ui, repo)
205
205
206
206
207 @command(
207 @command(
208 b'add',
208 b'add',
209 walkopts + subrepoopts + dryrunopts,
209 walkopts + subrepoopts + dryrunopts,
210 _(b'[OPTION]... [FILE]...'),
210 _(b'[OPTION]... [FILE]...'),
211 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
211 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
212 helpbasic=True,
212 helpbasic=True,
213 inferrepo=True,
213 inferrepo=True,
214 )
214 )
215 def add(ui, repo, *pats, **opts):
215 def add(ui, repo, *pats, **opts):
216 """add the specified files on the next commit
216 """add the specified files on the next commit
217
217
218 Schedule files to be version controlled and added to the
218 Schedule files to be version controlled and added to the
219 repository.
219 repository.
220
220
221 The files will be added to the repository at the next commit. To
221 The files will be added to the repository at the next commit. To
222 undo an add before that, see :hg:`forget`.
222 undo an add before that, see :hg:`forget`.
223
223
224 If no names are given, add all files to the repository (except
224 If no names are given, add all files to the repository (except
225 files matching ``.hgignore``).
225 files matching ``.hgignore``).
226
226
227 .. container:: verbose
227 .. container:: verbose
228
228
229 Examples:
229 Examples:
230
230
231 - New (unknown) files are added
231 - New (unknown) files are added
232 automatically by :hg:`add`::
232 automatically by :hg:`add`::
233
233
234 $ ls
234 $ ls
235 foo.c
235 foo.c
236 $ hg status
236 $ hg status
237 ? foo.c
237 ? foo.c
238 $ hg add
238 $ hg add
239 adding foo.c
239 adding foo.c
240 $ hg status
240 $ hg status
241 A foo.c
241 A foo.c
242
242
243 - Specific files to be added can be specified::
243 - Specific files to be added can be specified::
244
244
245 $ ls
245 $ ls
246 bar.c foo.c
246 bar.c foo.c
247 $ hg status
247 $ hg status
248 ? bar.c
248 ? bar.c
249 ? foo.c
249 ? foo.c
250 $ hg add bar.c
250 $ hg add bar.c
251 $ hg status
251 $ hg status
252 A bar.c
252 A bar.c
253 ? foo.c
253 ? foo.c
254
254
255 Returns 0 if all files are successfully added.
255 Returns 0 if all files are successfully added.
256 """
256 """
257
257
258 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
258 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
260 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
260 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
261 return rejected and 1 or 0
261 return rejected and 1 or 0
262
262
263
263
264 @command(
264 @command(
265 b'addremove',
265 b'addremove',
266 similarityopts + subrepoopts + walkopts + dryrunopts,
266 similarityopts + subrepoopts + walkopts + dryrunopts,
267 _(b'[OPTION]... [FILE]...'),
267 _(b'[OPTION]... [FILE]...'),
268 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
268 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
269 inferrepo=True,
269 inferrepo=True,
270 )
270 )
271 def addremove(ui, repo, *pats, **opts):
271 def addremove(ui, repo, *pats, **opts):
272 """add all new files, delete all missing files
272 """add all new files, delete all missing files
273
273
274 Add all new files and remove all missing files from the
274 Add all new files and remove all missing files from the
275 repository.
275 repository.
276
276
277 Unless names are given, new files are ignored if they match any of
277 Unless names are given, new files are ignored if they match any of
278 the patterns in ``.hgignore``. As with add, these changes take
278 the patterns in ``.hgignore``. As with add, these changes take
279 effect at the next commit.
279 effect at the next commit.
280
280
281 Use the -s/--similarity option to detect renamed files. This
281 Use the -s/--similarity option to detect renamed files. This
282 option takes a percentage between 0 (disabled) and 100 (files must
282 option takes a percentage between 0 (disabled) and 100 (files must
283 be identical) as its parameter. With a parameter greater than 0,
283 be identical) as its parameter. With a parameter greater than 0,
284 this compares every removed file with every added file and records
284 this compares every removed file with every added file and records
285 those similar enough as renames. Detecting renamed files this way
285 those similar enough as renames. Detecting renamed files this way
286 can be expensive. After using this option, :hg:`status -C` can be
286 can be expensive. After using this option, :hg:`status -C` can be
287 used to check which files were identified as moved or renamed. If
287 used to check which files were identified as moved or renamed. If
288 not specified, -s/--similarity defaults to 100 and only renames of
288 not specified, -s/--similarity defaults to 100 and only renames of
289 identical files are detected.
289 identical files are detected.
290
290
291 .. container:: verbose
291 .. container:: verbose
292
292
293 Examples:
293 Examples:
294
294
295 - A number of files (bar.c and foo.c) are new,
295 - A number of files (bar.c and foo.c) are new,
296 while foobar.c has been removed (without using :hg:`remove`)
296 while foobar.c has been removed (without using :hg:`remove`)
297 from the repository::
297 from the repository::
298
298
299 $ ls
299 $ ls
300 bar.c foo.c
300 bar.c foo.c
301 $ hg status
301 $ hg status
302 ! foobar.c
302 ! foobar.c
303 ? bar.c
303 ? bar.c
304 ? foo.c
304 ? foo.c
305 $ hg addremove
305 $ hg addremove
306 adding bar.c
306 adding bar.c
307 adding foo.c
307 adding foo.c
308 removing foobar.c
308 removing foobar.c
309 $ hg status
309 $ hg status
310 A bar.c
310 A bar.c
311 A foo.c
311 A foo.c
312 R foobar.c
312 R foobar.c
313
313
314 - A file foobar.c was moved to foo.c without using :hg:`rename`.
314 - A file foobar.c was moved to foo.c without using :hg:`rename`.
315 Afterwards, it was edited slightly::
315 Afterwards, it was edited slightly::
316
316
317 $ ls
317 $ ls
318 foo.c
318 foo.c
319 $ hg status
319 $ hg status
320 ! foobar.c
320 ! foobar.c
321 ? foo.c
321 ? foo.c
322 $ hg addremove --similarity 90
322 $ hg addremove --similarity 90
323 removing foobar.c
323 removing foobar.c
324 adding foo.c
324 adding foo.c
325 recording removal of foobar.c as rename to foo.c (94% similar)
325 recording removal of foobar.c as rename to foo.c (94% similar)
326 $ hg status -C
326 $ hg status -C
327 A foo.c
327 A foo.c
328 foobar.c
328 foobar.c
329 R foobar.c
329 R foobar.c
330
330
331 Returns 0 if all files are successfully added.
331 Returns 0 if all files are successfully added.
332 """
332 """
333 opts = pycompat.byteskwargs(opts)
333 opts = pycompat.byteskwargs(opts)
334 if not opts.get(b'similarity'):
334 if not opts.get(b'similarity'):
335 opts[b'similarity'] = b'100'
335 opts[b'similarity'] = b'100'
336 matcher = scmutil.match(repo[None], pats, opts)
336 matcher = scmutil.match(repo[None], pats, opts)
337 relative = scmutil.anypats(pats, opts)
337 relative = scmutil.anypats(pats, opts)
338 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
339 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
340
340
341
341
342 @command(
342 @command(
343 b'annotate|blame',
343 b'annotate|blame',
344 [
344 [
345 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
346 (
346 (
347 b'',
347 b'',
348 b'follow',
348 b'follow',
349 None,
349 None,
350 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 _(b'follow copies/renames and list the filename (DEPRECATED)'),
351 ),
351 ),
352 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
353 (b'a', b'text', None, _(b'treat all files as text')),
353 (b'a', b'text', None, _(b'treat all files as text')),
354 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 (b'u', b'user', None, _(b'list the author (long with -v)')),
355 (b'f', b'file', None, _(b'list the filename')),
355 (b'f', b'file', None, _(b'list the filename')),
356 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 (b'd', b'date', None, _(b'list the date (short with -q)')),
357 (b'n', b'number', None, _(b'list the revision number (default)')),
357 (b'n', b'number', None, _(b'list the revision number (default)')),
358 (b'c', b'changeset', None, _(b'list the changeset')),
358 (b'c', b'changeset', None, _(b'list the changeset')),
359 (
359 (
360 b'l',
360 b'l',
361 b'line-number',
361 b'line-number',
362 None,
362 None,
363 _(b'show line number at the first appearance'),
363 _(b'show line number at the first appearance'),
364 ),
364 ),
365 (
365 (
366 b'',
366 b'',
367 b'skip',
367 b'skip',
368 [],
368 [],
369 _(b'revset to not display (EXPERIMENTAL)'),
369 _(b'revset to not display (EXPERIMENTAL)'),
370 _(b'REV'),
370 _(b'REV'),
371 ),
371 ),
372 ]
372 ]
373 + diffwsopts
373 + diffwsopts
374 + walkopts
374 + walkopts
375 + formatteropts,
375 + formatteropts,
376 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
377 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 helpcategory=command.CATEGORY_FILE_CONTENTS,
378 helpbasic=True,
378 helpbasic=True,
379 inferrepo=True,
379 inferrepo=True,
380 )
380 )
381 def annotate(ui, repo, *pats, **opts):
381 def annotate(ui, repo, *pats, **opts):
382 """show changeset information by line for each file
382 """show changeset information by line for each file
383
383
384 List changes in files, showing the revision id responsible for
384 List changes in files, showing the revision id responsible for
385 each line.
385 each line.
386
386
387 This command is useful for discovering when a change was made and
387 This command is useful for discovering when a change was made and
388 by whom.
388 by whom.
389
389
390 If you include --file, --user, or --date, the revision number is
390 If you include --file, --user, or --date, the revision number is
391 suppressed unless you also include --number.
391 suppressed unless you also include --number.
392
392
393 Without the -a/--text option, annotate will avoid processing files
393 Without the -a/--text option, annotate will avoid processing files
394 it detects as binary. With -a, annotate will annotate the file
394 it detects as binary. With -a, annotate will annotate the file
395 anyway, although the results will probably be neither useful
395 anyway, although the results will probably be neither useful
396 nor desirable.
396 nor desirable.
397
397
398 .. container:: verbose
398 .. container:: verbose
399
399
400 Template:
400 Template:
401
401
402 The following keywords are supported in addition to the common template
402 The following keywords are supported in addition to the common template
403 keywords and functions. See also :hg:`help templates`.
403 keywords and functions. See also :hg:`help templates`.
404
404
405 :lines: List of lines with annotation data.
405 :lines: List of lines with annotation data.
406 :path: String. Repository-absolute path of the specified file.
406 :path: String. Repository-absolute path of the specified file.
407
407
408 And each entry of ``{lines}`` provides the following sub-keywords in
408 And each entry of ``{lines}`` provides the following sub-keywords in
409 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
410
410
411 :line: String. Line content.
411 :line: String. Line content.
412 :lineno: Integer. Line number at that revision.
412 :lineno: Integer. Line number at that revision.
413 :path: String. Repository-absolute path of the file at that revision.
413 :path: String. Repository-absolute path of the file at that revision.
414
414
415 See :hg:`help templates.operators` for the list expansion syntax.
415 See :hg:`help templates.operators` for the list expansion syntax.
416
416
417 Returns 0 on success.
417 Returns 0 on success.
418 """
418 """
419 opts = pycompat.byteskwargs(opts)
419 opts = pycompat.byteskwargs(opts)
420 if not pats:
420 if not pats:
421 raise error.InputError(
421 raise error.InputError(
422 _(b'at least one filename or pattern is required')
422 _(b'at least one filename or pattern is required')
423 )
423 )
424
424
425 if opts.get(b'follow'):
425 if opts.get(b'follow'):
426 # --follow is deprecated and now just an alias for -f/--file
426 # --follow is deprecated and now just an alias for -f/--file
427 # to mimic the behavior of Mercurial before version 1.5
427 # to mimic the behavior of Mercurial before version 1.5
428 opts[b'file'] = True
428 opts[b'file'] = True
429
429
430 if (
430 if (
431 not opts.get(b'user')
431 not opts.get(b'user')
432 and not opts.get(b'changeset')
432 and not opts.get(b'changeset')
433 and not opts.get(b'date')
433 and not opts.get(b'date')
434 and not opts.get(b'file')
434 and not opts.get(b'file')
435 ):
435 ):
436 opts[b'number'] = True
436 opts[b'number'] = True
437
437
438 linenumber = opts.get(b'line_number') is not None
438 linenumber = opts.get(b'line_number') is not None
439 if (
439 if (
440 linenumber
440 linenumber
441 and (not opts.get(b'changeset'))
441 and (not opts.get(b'changeset'))
442 and (not opts.get(b'number'))
442 and (not opts.get(b'number'))
443 ):
443 ):
444 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
444 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
445
445
446 rev = opts.get(b'rev')
446 rev = opts.get(b'rev')
447 if rev:
447 if rev:
448 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
448 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
449 ctx = scmutil.revsingle(repo, rev)
449 ctx = scmutil.revsingle(repo, rev)
450
450
451 ui.pager(b'annotate')
451 ui.pager(b'annotate')
452 rootfm = ui.formatter(b'annotate', opts)
452 rootfm = ui.formatter(b'annotate', opts)
453 if ui.debugflag:
453 if ui.debugflag:
454 shorthex = pycompat.identity
454 shorthex = pycompat.identity
455 else:
455 else:
456
456
457 def shorthex(h):
457 def shorthex(h):
458 return h[:12]
458 return h[:12]
459
459
460 if ui.quiet:
460 if ui.quiet:
461 datefunc = dateutil.shortdate
461 datefunc = dateutil.shortdate
462 else:
462 else:
463 datefunc = dateutil.datestr
463 datefunc = dateutil.datestr
464 if ctx.rev() is None:
464 if ctx.rev() is None:
465 if opts.get(b'changeset'):
465 if opts.get(b'changeset'):
466 # omit "+" suffix which is appended to node hex
466 # omit "+" suffix which is appended to node hex
467 def formatrev(rev):
467 def formatrev(rev):
468 if rev == wdirrev:
468 if rev == wdirrev:
469 return b'%d' % ctx.p1().rev()
469 return b'%d' % ctx.p1().rev()
470 else:
470 else:
471 return b'%d' % rev
471 return b'%d' % rev
472
472
473 else:
473 else:
474
474
475 def formatrev(rev):
475 def formatrev(rev):
476 if rev == wdirrev:
476 if rev == wdirrev:
477 return b'%d+' % ctx.p1().rev()
477 return b'%d+' % ctx.p1().rev()
478 else:
478 else:
479 return b'%d ' % rev
479 return b'%d ' % rev
480
480
481 def formathex(h):
481 def formathex(h):
482 if h == wdirhex:
482 if h == wdirhex:
483 return b'%s+' % shorthex(hex(ctx.p1().node()))
483 return b'%s+' % shorthex(hex(ctx.p1().node()))
484 else:
484 else:
485 return b'%s ' % shorthex(h)
485 return b'%s ' % shorthex(h)
486
486
487 else:
487 else:
488 formatrev = b'%d'.__mod__
488 formatrev = b'%d'.__mod__
489 formathex = shorthex
489 formathex = shorthex
490
490
491 opmap = [
491 opmap = [
492 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
492 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
493 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
493 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
494 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
494 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
495 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
495 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
496 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
496 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
497 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
497 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
498 ]
498 ]
499 opnamemap = {
499 opnamemap = {
500 b'rev': b'number',
500 b'rev': b'number',
501 b'node': b'changeset',
501 b'node': b'changeset',
502 b'path': b'file',
502 b'path': b'file',
503 b'lineno': b'line_number',
503 b'lineno': b'line_number',
504 }
504 }
505
505
506 if rootfm.isplain():
506 if rootfm.isplain():
507
507
508 def makefunc(get, fmt):
508 def makefunc(get, fmt):
509 return lambda x: fmt(get(x))
509 return lambda x: fmt(get(x))
510
510
511 else:
511 else:
512
512
513 def makefunc(get, fmt):
513 def makefunc(get, fmt):
514 return get
514 return get
515
515
516 datahint = rootfm.datahint()
516 datahint = rootfm.datahint()
517 funcmap = [
517 funcmap = [
518 (makefunc(get, fmt), sep)
518 (makefunc(get, fmt), sep)
519 for fn, sep, get, fmt in opmap
519 for fn, sep, get, fmt in opmap
520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
521 ]
521 ]
522 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
522 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
523 fields = b' '.join(
523 fields = b' '.join(
524 fn
524 fn
525 for fn, sep, get, fmt in opmap
525 for fn, sep, get, fmt in opmap
526 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
526 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
527 )
527 )
528
528
529 def bad(x, y):
529 def bad(x, y):
530 raise error.Abort(b"%s: %s" % (x, y))
530 raise error.Abort(b"%s: %s" % (x, y))
531
531
532 m = scmutil.match(ctx, pats, opts, badfn=bad)
532 m = scmutil.match(ctx, pats, opts, badfn=bad)
533
533
534 follow = not opts.get(b'no_follow')
534 follow = not opts.get(b'no_follow')
535 diffopts = patch.difffeatureopts(
535 diffopts = patch.difffeatureopts(
536 ui, opts, section=b'annotate', whitespace=True
536 ui, opts, section=b'annotate', whitespace=True
537 )
537 )
538 skiprevs = opts.get(b'skip')
538 skiprevs = opts.get(b'skip')
539 if skiprevs:
539 if skiprevs:
540 skiprevs = scmutil.revrange(repo, skiprevs)
540 skiprevs = scmutil.revrange(repo, skiprevs)
541
541
542 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
542 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
543 for abs in ctx.walk(m):
543 for abs in ctx.walk(m):
544 fctx = ctx[abs]
544 fctx = ctx[abs]
545 rootfm.startitem()
545 rootfm.startitem()
546 rootfm.data(path=abs)
546 rootfm.data(path=abs)
547 if not opts.get(b'text') and fctx.isbinary():
547 if not opts.get(b'text') and fctx.isbinary():
548 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
548 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
549 continue
549 continue
550
550
551 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
551 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
552 lines = fctx.annotate(
552 lines = fctx.annotate(
553 follow=follow, skiprevs=skiprevs, diffopts=diffopts
553 follow=follow, skiprevs=skiprevs, diffopts=diffopts
554 )
554 )
555 if not lines:
555 if not lines:
556 fm.end()
556 fm.end()
557 continue
557 continue
558 formats = []
558 formats = []
559 pieces = []
559 pieces = []
560
560
561 for f, sep in funcmap:
561 for f, sep in funcmap:
562 l = [f(n) for n in lines]
562 l = [f(n) for n in lines]
563 if fm.isplain():
563 if fm.isplain():
564 sizes = [encoding.colwidth(x) for x in l]
564 sizes = [encoding.colwidth(x) for x in l]
565 ml = max(sizes)
565 ml = max(sizes)
566 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
566 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
567 else:
567 else:
568 formats.append([b'%s'] * len(l))
568 formats.append([b'%s'] * len(l))
569 pieces.append(l)
569 pieces.append(l)
570
570
571 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
571 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
572 fm.startitem()
572 fm.startitem()
573 fm.context(fctx=n.fctx)
573 fm.context(fctx=n.fctx)
574 fm.write(fields, b"".join(f), *p)
574 fm.write(fields, b"".join(f), *p)
575 if n.skip:
575 if n.skip:
576 fmt = b"* %s"
576 fmt = b"* %s"
577 else:
577 else:
578 fmt = b": %s"
578 fmt = b": %s"
579 fm.write(b'line', fmt, n.text)
579 fm.write(b'line', fmt, n.text)
580
580
581 if not lines[-1].text.endswith(b'\n'):
581 if not lines[-1].text.endswith(b'\n'):
582 fm.plain(b'\n')
582 fm.plain(b'\n')
583 fm.end()
583 fm.end()
584
584
585 rootfm.end()
585 rootfm.end()
586
586
587
587
588 @command(
588 @command(
589 b'archive',
589 b'archive',
590 [
590 [
591 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
591 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
592 (
592 (
593 b'p',
593 b'p',
594 b'prefix',
594 b'prefix',
595 b'',
595 b'',
596 _(b'directory prefix for files in archive'),
596 _(b'directory prefix for files in archive'),
597 _(b'PREFIX'),
597 _(b'PREFIX'),
598 ),
598 ),
599 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
599 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
600 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
600 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
601 ]
601 ]
602 + subrepoopts
602 + subrepoopts
603 + walkopts,
603 + walkopts,
604 _(b'[OPTION]... DEST'),
604 _(b'[OPTION]... DEST'),
605 helpcategory=command.CATEGORY_IMPORT_EXPORT,
605 helpcategory=command.CATEGORY_IMPORT_EXPORT,
606 )
606 )
607 def archive(ui, repo, dest, **opts):
607 def archive(ui, repo, dest, **opts):
608 """create an unversioned archive of a repository revision
608 """create an unversioned archive of a repository revision
609
609
610 By default, the revision used is the parent of the working
610 By default, the revision used is the parent of the working
611 directory; use -r/--rev to specify a different revision.
611 directory; use -r/--rev to specify a different revision.
612
612
613 The archive type is automatically detected based on file
613 The archive type is automatically detected based on file
614 extension (to override, use -t/--type).
614 extension (to override, use -t/--type).
615
615
616 .. container:: verbose
616 .. container:: verbose
617
617
618 Examples:
618 Examples:
619
619
620 - create a zip file containing the 1.0 release::
620 - create a zip file containing the 1.0 release::
621
621
622 hg archive -r 1.0 project-1.0.zip
622 hg archive -r 1.0 project-1.0.zip
623
623
624 - create a tarball excluding .hg files::
624 - create a tarball excluding .hg files::
625
625
626 hg archive project.tar.gz -X ".hg*"
626 hg archive project.tar.gz -X ".hg*"
627
627
628 Valid types are:
628 Valid types are:
629
629
630 :``files``: a directory full of files (default)
630 :``files``: a directory full of files (default)
631 :``tar``: tar archive, uncompressed
631 :``tar``: tar archive, uncompressed
632 :``tbz2``: tar archive, compressed using bzip2
632 :``tbz2``: tar archive, compressed using bzip2
633 :``tgz``: tar archive, compressed using gzip
633 :``tgz``: tar archive, compressed using gzip
634 :``txz``: tar archive, compressed using lzma (only in Python 3)
634 :``txz``: tar archive, compressed using lzma (only in Python 3)
635 :``uzip``: zip archive, uncompressed
635 :``uzip``: zip archive, uncompressed
636 :``zip``: zip archive, compressed using deflate
636 :``zip``: zip archive, compressed using deflate
637
637
638 The exact name of the destination archive or directory is given
638 The exact name of the destination archive or directory is given
639 using a format string; see :hg:`help export` for details.
639 using a format string; see :hg:`help export` for details.
640
640
641 Each member added to an archive file has a directory prefix
641 Each member added to an archive file has a directory prefix
642 prepended. Use -p/--prefix to specify a format string for the
642 prepended. Use -p/--prefix to specify a format string for the
643 prefix. The default is the basename of the archive, with suffixes
643 prefix. The default is the basename of the archive, with suffixes
644 removed.
644 removed.
645
645
646 Returns 0 on success.
646 Returns 0 on success.
647 """
647 """
648
648
649 opts = pycompat.byteskwargs(opts)
649 opts = pycompat.byteskwargs(opts)
650 rev = opts.get(b'rev')
650 rev = opts.get(b'rev')
651 if rev:
651 if rev:
652 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
652 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
653 ctx = scmutil.revsingle(repo, rev)
653 ctx = scmutil.revsingle(repo, rev)
654 if not ctx:
654 if not ctx:
655 raise error.InputError(
655 raise error.InputError(
656 _(b'no working directory: please specify a revision')
656 _(b'no working directory: please specify a revision')
657 )
657 )
658 node = ctx.node()
658 node = ctx.node()
659 dest = cmdutil.makefilename(ctx, dest)
659 dest = cmdutil.makefilename(ctx, dest)
660 if os.path.realpath(dest) == repo.root:
660 if os.path.realpath(dest) == repo.root:
661 raise error.InputError(_(b'repository root cannot be destination'))
661 raise error.InputError(_(b'repository root cannot be destination'))
662
662
663 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
663 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
664 prefix = opts.get(b'prefix')
664 prefix = opts.get(b'prefix')
665
665
666 if dest == b'-':
666 if dest == b'-':
667 if kind == b'files':
667 if kind == b'files':
668 raise error.InputError(_(b'cannot archive plain files to stdout'))
668 raise error.InputError(_(b'cannot archive plain files to stdout'))
669 dest = cmdutil.makefileobj(ctx, dest)
669 dest = cmdutil.makefileobj(ctx, dest)
670 if not prefix:
670 if not prefix:
671 prefix = os.path.basename(repo.root) + b'-%h'
671 prefix = os.path.basename(repo.root) + b'-%h'
672
672
673 prefix = cmdutil.makefilename(ctx, prefix)
673 prefix = cmdutil.makefilename(ctx, prefix)
674 match = scmutil.match(ctx, [], opts)
674 match = scmutil.match(ctx, [], opts)
675 archival.archive(
675 archival.archive(
676 repo,
676 repo,
677 dest,
677 dest,
678 node,
678 node,
679 kind,
679 kind,
680 not opts.get(b'no_decode'),
680 not opts.get(b'no_decode'),
681 match,
681 match,
682 prefix,
682 prefix,
683 subrepos=opts.get(b'subrepos'),
683 subrepos=opts.get(b'subrepos'),
684 )
684 )
685
685
686
686
687 @command(
687 @command(
688 b'backout',
688 b'backout',
689 [
689 [
690 (
690 (
691 b'',
691 b'',
692 b'merge',
692 b'merge',
693 None,
693 None,
694 _(b'merge with old dirstate parent after backout'),
694 _(b'merge with old dirstate parent after backout'),
695 ),
695 ),
696 (
696 (
697 b'',
697 b'',
698 b'commit',
698 b'commit',
699 None,
699 None,
700 _(b'commit if no conflicts were encountered (DEPRECATED)'),
700 _(b'commit if no conflicts were encountered (DEPRECATED)'),
701 ),
701 ),
702 (b'', b'no-commit', None, _(b'do not commit')),
702 (b'', b'no-commit', None, _(b'do not commit')),
703 (
703 (
704 b'',
704 b'',
705 b'parent',
705 b'parent',
706 b'',
706 b'',
707 _(b'parent to choose when backing out merge (DEPRECATED)'),
707 _(b'parent to choose when backing out merge (DEPRECATED)'),
708 _(b'REV'),
708 _(b'REV'),
709 ),
709 ),
710 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
710 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
711 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
711 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
712 ]
712 ]
713 + mergetoolopts
713 + mergetoolopts
714 + walkopts
714 + walkopts
715 + commitopts
715 + commitopts
716 + commitopts2,
716 + commitopts2,
717 _(b'[OPTION]... [-r] REV'),
717 _(b'[OPTION]... [-r] REV'),
718 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
718 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
719 )
719 )
720 def backout(ui, repo, node=None, rev=None, **opts):
720 def backout(ui, repo, node=None, rev=None, **opts):
721 """reverse effect of earlier changeset
721 """reverse effect of earlier changeset
722
722
723 Prepare a new changeset with the effect of REV undone in the
723 Prepare a new changeset with the effect of REV undone in the
724 current working directory. If no conflicts were encountered,
724 current working directory. If no conflicts were encountered,
725 it will be committed immediately.
725 it will be committed immediately.
726
726
727 If REV is the parent of the working directory, then this new changeset
727 If REV is the parent of the working directory, then this new changeset
728 is committed automatically (unless --no-commit is specified).
728 is committed automatically (unless --no-commit is specified).
729
729
730 .. note::
730 .. note::
731
731
732 :hg:`backout` cannot be used to fix either an unwanted or
732 :hg:`backout` cannot be used to fix either an unwanted or
733 incorrect merge.
733 incorrect merge.
734
734
735 .. container:: verbose
735 .. container:: verbose
736
736
737 Examples:
737 Examples:
738
738
739 - Reverse the effect of the parent of the working directory.
739 - Reverse the effect of the parent of the working directory.
740 This backout will be committed immediately::
740 This backout will be committed immediately::
741
741
742 hg backout -r .
742 hg backout -r .
743
743
744 - Reverse the effect of previous bad revision 23::
744 - Reverse the effect of previous bad revision 23::
745
745
746 hg backout -r 23
746 hg backout -r 23
747
747
748 - Reverse the effect of previous bad revision 23 and
748 - Reverse the effect of previous bad revision 23 and
749 leave changes uncommitted::
749 leave changes uncommitted::
750
750
751 hg backout -r 23 --no-commit
751 hg backout -r 23 --no-commit
752 hg commit -m "Backout revision 23"
752 hg commit -m "Backout revision 23"
753
753
754 By default, the pending changeset will have one parent,
754 By default, the pending changeset will have one parent,
755 maintaining a linear history. With --merge, the pending
755 maintaining a linear history. With --merge, the pending
756 changeset will instead have two parents: the old parent of the
756 changeset will instead have two parents: the old parent of the
757 working directory and a new child of REV that simply undoes REV.
757 working directory and a new child of REV that simply undoes REV.
758
758
759 Before version 1.7, the behavior without --merge was equivalent
759 Before version 1.7, the behavior without --merge was equivalent
760 to specifying --merge followed by :hg:`update --clean .` to
760 to specifying --merge followed by :hg:`update --clean .` to
761 cancel the merge and leave the child of REV as a head to be
761 cancel the merge and leave the child of REV as a head to be
762 merged separately.
762 merged separately.
763
763
764 See :hg:`help dates` for a list of formats valid for -d/--date.
764 See :hg:`help dates` for a list of formats valid for -d/--date.
765
765
766 See :hg:`help revert` for a way to restore files to the state
766 See :hg:`help revert` for a way to restore files to the state
767 of another revision.
767 of another revision.
768
768
769 Returns 0 on success, 1 if nothing to backout or there are unresolved
769 Returns 0 on success, 1 if nothing to backout or there are unresolved
770 files.
770 files.
771 """
771 """
772 with repo.wlock(), repo.lock():
772 with repo.wlock(), repo.lock():
773 return _dobackout(ui, repo, node, rev, **opts)
773 return _dobackout(ui, repo, node, rev, **opts)
774
774
775
775
776 def _dobackout(ui, repo, node=None, rev=None, **opts):
776 def _dobackout(ui, repo, node=None, rev=None, **opts):
777 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
777 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
778 opts = pycompat.byteskwargs(opts)
778 opts = pycompat.byteskwargs(opts)
779
779
780 if rev and node:
780 if rev and node:
781 raise error.InputError(_(b"please specify just one revision"))
781 raise error.InputError(_(b"please specify just one revision"))
782
782
783 if not rev:
783 if not rev:
784 rev = node
784 rev = node
785
785
786 if not rev:
786 if not rev:
787 raise error.InputError(_(b"please specify a revision to backout"))
787 raise error.InputError(_(b"please specify a revision to backout"))
788
788
789 date = opts.get(b'date')
789 date = opts.get(b'date')
790 if date:
790 if date:
791 opts[b'date'] = dateutil.parsedate(date)
791 opts[b'date'] = dateutil.parsedate(date)
792
792
793 cmdutil.checkunfinished(repo)
793 cmdutil.checkunfinished(repo)
794 cmdutil.bailifchanged(repo)
794 cmdutil.bailifchanged(repo)
795 ctx = scmutil.revsingle(repo, rev)
795 ctx = scmutil.revsingle(repo, rev)
796 node = ctx.node()
796 node = ctx.node()
797
797
798 op1, op2 = repo.dirstate.parents()
798 op1, op2 = repo.dirstate.parents()
799 if not repo.changelog.isancestor(node, op1):
799 if not repo.changelog.isancestor(node, op1):
800 raise error.InputError(
800 raise error.InputError(
801 _(b'cannot backout change that is not an ancestor')
801 _(b'cannot backout change that is not an ancestor')
802 )
802 )
803
803
804 p1, p2 = repo.changelog.parents(node)
804 p1, p2 = repo.changelog.parents(node)
805 if p1 == nullid:
805 if p1 == nullid:
806 raise error.InputError(_(b'cannot backout a change with no parents'))
806 raise error.InputError(_(b'cannot backout a change with no parents'))
807 if p2 != nullid:
807 if p2 != nullid:
808 if not opts.get(b'parent'):
808 if not opts.get(b'parent'):
809 raise error.InputError(_(b'cannot backout a merge changeset'))
809 raise error.InputError(_(b'cannot backout a merge changeset'))
810 p = repo.lookup(opts[b'parent'])
810 p = repo.lookup(opts[b'parent'])
811 if p not in (p1, p2):
811 if p not in (p1, p2):
812 raise error.InputError(
812 raise error.InputError(
813 _(b'%s is not a parent of %s') % (short(p), short(node))
813 _(b'%s is not a parent of %s') % (short(p), short(node))
814 )
814 )
815 parent = p
815 parent = p
816 else:
816 else:
817 if opts.get(b'parent'):
817 if opts.get(b'parent'):
818 raise error.InputError(
818 raise error.InputError(
819 _(b'cannot use --parent on non-merge changeset')
819 _(b'cannot use --parent on non-merge changeset')
820 )
820 )
821 parent = p1
821 parent = p1
822
822
823 # the backout should appear on the same branch
823 # the backout should appear on the same branch
824 branch = repo.dirstate.branch()
824 branch = repo.dirstate.branch()
825 bheads = repo.branchheads(branch)
825 bheads = repo.branchheads(branch)
826 rctx = scmutil.revsingle(repo, hex(parent))
826 rctx = scmutil.revsingle(repo, hex(parent))
827 if not opts.get(b'merge') and op1 != node:
827 if not opts.get(b'merge') and op1 != node:
828 with dirstateguard.dirstateguard(repo, b'backout'):
828 with dirstateguard.dirstateguard(repo, b'backout'):
829 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
829 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
830 with ui.configoverride(overrides, b'backout'):
830 with ui.configoverride(overrides, b'backout'):
831 stats = mergemod.back_out(ctx, parent=repo[parent])
831 stats = mergemod.back_out(ctx, parent=repo[parent])
832 repo.setparents(op1, op2)
832 repo.setparents(op1, op2)
833 hg._showstats(repo, stats)
833 hg._showstats(repo, stats)
834 if stats.unresolvedcount:
834 if stats.unresolvedcount:
835 repo.ui.status(
835 repo.ui.status(
836 _(b"use 'hg resolve' to retry unresolved file merges\n")
836 _(b"use 'hg resolve' to retry unresolved file merges\n")
837 )
837 )
838 return 1
838 return 1
839 else:
839 else:
840 hg.clean(repo, node, show_stats=False)
840 hg.clean(repo, node, show_stats=False)
841 repo.dirstate.setbranch(branch)
841 repo.dirstate.setbranch(branch)
842 cmdutil.revert(ui, repo, rctx)
842 cmdutil.revert(ui, repo, rctx)
843
843
844 if opts.get(b'no_commit'):
844 if opts.get(b'no_commit'):
845 msg = _(b"changeset %s backed out, don't forget to commit.\n")
845 msg = _(b"changeset %s backed out, don't forget to commit.\n")
846 ui.status(msg % short(node))
846 ui.status(msg % short(node))
847 return 0
847 return 0
848
848
849 def commitfunc(ui, repo, message, match, opts):
849 def commitfunc(ui, repo, message, match, opts):
850 editform = b'backout'
850 editform = b'backout'
851 e = cmdutil.getcommiteditor(
851 e = cmdutil.getcommiteditor(
852 editform=editform, **pycompat.strkwargs(opts)
852 editform=editform, **pycompat.strkwargs(opts)
853 )
853 )
854 if not message:
854 if not message:
855 # we don't translate commit messages
855 # we don't translate commit messages
856 message = b"Backed out changeset %s" % short(node)
856 message = b"Backed out changeset %s" % short(node)
857 e = cmdutil.getcommiteditor(edit=True, editform=editform)
857 e = cmdutil.getcommiteditor(edit=True, editform=editform)
858 return repo.commit(
858 return repo.commit(
859 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
859 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
860 )
860 )
861
861
862 # save to detect changes
862 # save to detect changes
863 tip = repo.changelog.tip()
863 tip = repo.changelog.tip()
864
864
865 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
865 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
866 if not newnode:
866 if not newnode:
867 ui.status(_(b"nothing changed\n"))
867 ui.status(_(b"nothing changed\n"))
868 return 1
868 return 1
869 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
869 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
870
870
871 def nice(node):
871 def nice(node):
872 return b'%d:%s' % (repo.changelog.rev(node), short(node))
872 return b'%d:%s' % (repo.changelog.rev(node), short(node))
873
873
874 ui.status(
874 ui.status(
875 _(b'changeset %s backs out changeset %s\n')
875 _(b'changeset %s backs out changeset %s\n')
876 % (nice(newnode), nice(node))
876 % (nice(newnode), nice(node))
877 )
877 )
878 if opts.get(b'merge') and op1 != node:
878 if opts.get(b'merge') and op1 != node:
879 hg.clean(repo, op1, show_stats=False)
879 hg.clean(repo, op1, show_stats=False)
880 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
880 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
881 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
881 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
882 with ui.configoverride(overrides, b'backout'):
882 with ui.configoverride(overrides, b'backout'):
883 return hg.merge(repo[b'tip'])
883 return hg.merge(repo[b'tip'])
884 return 0
884 return 0
885
885
886
886
887 @command(
887 @command(
888 b'bisect',
888 b'bisect',
889 [
889 [
890 (b'r', b'reset', False, _(b'reset bisect state')),
890 (b'r', b'reset', False, _(b'reset bisect state')),
891 (b'g', b'good', False, _(b'mark changeset good')),
891 (b'g', b'good', False, _(b'mark changeset good')),
892 (b'b', b'bad', False, _(b'mark changeset bad')),
892 (b'b', b'bad', False, _(b'mark changeset bad')),
893 (b's', b'skip', False, _(b'skip testing changeset')),
893 (b's', b'skip', False, _(b'skip testing changeset')),
894 (b'e', b'extend', False, _(b'extend the bisect range')),
894 (b'e', b'extend', False, _(b'extend the bisect range')),
895 (
895 (
896 b'c',
896 b'c',
897 b'command',
897 b'command',
898 b'',
898 b'',
899 _(b'use command to check changeset state'),
899 _(b'use command to check changeset state'),
900 _(b'CMD'),
900 _(b'CMD'),
901 ),
901 ),
902 (b'U', b'noupdate', False, _(b'do not update to target')),
902 (b'U', b'noupdate', False, _(b'do not update to target')),
903 ],
903 ],
904 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
904 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
905 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
905 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
906 )
906 )
907 def bisect(
907 def bisect(
908 ui,
908 ui,
909 repo,
909 repo,
910 positional_1=None,
910 positional_1=None,
911 positional_2=None,
911 positional_2=None,
912 command=None,
912 command=None,
913 reset=None,
913 reset=None,
914 good=None,
914 good=None,
915 bad=None,
915 bad=None,
916 skip=None,
916 skip=None,
917 extend=None,
917 extend=None,
918 noupdate=None,
918 noupdate=None,
919 ):
919 ):
920 """subdivision search of changesets
920 """subdivision search of changesets
921
921
922 This command helps to find changesets which introduce problems. To
922 This command helps to find changesets which introduce problems. To
923 use, mark the earliest changeset you know exhibits the problem as
923 use, mark the earliest changeset you know exhibits the problem as
924 bad, then mark the latest changeset which is free from the problem
924 bad, then mark the latest changeset which is free from the problem
925 as good. Bisect will update your working directory to a revision
925 as good. Bisect will update your working directory to a revision
926 for testing (unless the -U/--noupdate option is specified). Once
926 for testing (unless the -U/--noupdate option is specified). Once
927 you have performed tests, mark the working directory as good or
927 you have performed tests, mark the working directory as good or
928 bad, and bisect will either update to another candidate changeset
928 bad, and bisect will either update to another candidate changeset
929 or announce that it has found the bad revision.
929 or announce that it has found the bad revision.
930
930
931 As a shortcut, you can also use the revision argument to mark a
931 As a shortcut, you can also use the revision argument to mark a
932 revision as good or bad without checking it out first.
932 revision as good or bad without checking it out first.
933
933
934 If you supply a command, it will be used for automatic bisection.
934 If you supply a command, it will be used for automatic bisection.
935 The environment variable HG_NODE will contain the ID of the
935 The environment variable HG_NODE will contain the ID of the
936 changeset being tested. The exit status of the command will be
936 changeset being tested. The exit status of the command will be
937 used to mark revisions as good or bad: status 0 means good, 125
937 used to mark revisions as good or bad: status 0 means good, 125
938 means to skip the revision, 127 (command not found) will abort the
938 means to skip the revision, 127 (command not found) will abort the
939 bisection, and any other non-zero exit status means the revision
939 bisection, and any other non-zero exit status means the revision
940 is bad.
940 is bad.
941
941
942 .. container:: verbose
942 .. container:: verbose
943
943
944 Some examples:
944 Some examples:
945
945
946 - start a bisection with known bad revision 34, and good revision 12::
946 - start a bisection with known bad revision 34, and good revision 12::
947
947
948 hg bisect --bad 34
948 hg bisect --bad 34
949 hg bisect --good 12
949 hg bisect --good 12
950
950
951 - advance the current bisection by marking current revision as good or
951 - advance the current bisection by marking current revision as good or
952 bad::
952 bad::
953
953
954 hg bisect --good
954 hg bisect --good
955 hg bisect --bad
955 hg bisect --bad
956
956
957 - mark the current revision, or a known revision, to be skipped (e.g. if
957 - mark the current revision, or a known revision, to be skipped (e.g. if
958 that revision is not usable because of another issue)::
958 that revision is not usable because of another issue)::
959
959
960 hg bisect --skip
960 hg bisect --skip
961 hg bisect --skip 23
961 hg bisect --skip 23
962
962
963 - skip all revisions that do not touch directories ``foo`` or ``bar``::
963 - skip all revisions that do not touch directories ``foo`` or ``bar``::
964
964
965 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
965 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
966
966
967 - forget the current bisection::
967 - forget the current bisection::
968
968
969 hg bisect --reset
969 hg bisect --reset
970
970
971 - use 'make && make tests' to automatically find the first broken
971 - use 'make && make tests' to automatically find the first broken
972 revision::
972 revision::
973
973
974 hg bisect --reset
974 hg bisect --reset
975 hg bisect --bad 34
975 hg bisect --bad 34
976 hg bisect --good 12
976 hg bisect --good 12
977 hg bisect --command "make && make tests"
977 hg bisect --command "make && make tests"
978
978
979 - see all changesets whose states are already known in the current
979 - see all changesets whose states are already known in the current
980 bisection::
980 bisection::
981
981
982 hg log -r "bisect(pruned)"
982 hg log -r "bisect(pruned)"
983
983
984 - see the changeset currently being bisected (especially useful
984 - see the changeset currently being bisected (especially useful
985 if running with -U/--noupdate)::
985 if running with -U/--noupdate)::
986
986
987 hg log -r "bisect(current)"
987 hg log -r "bisect(current)"
988
988
989 - see all changesets that took part in the current bisection::
989 - see all changesets that took part in the current bisection::
990
990
991 hg log -r "bisect(range)"
991 hg log -r "bisect(range)"
992
992
993 - you can even get a nice graph::
993 - you can even get a nice graph::
994
994
995 hg log --graph -r "bisect(range)"
995 hg log --graph -r "bisect(range)"
996
996
997 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
997 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
998
998
999 Returns 0 on success.
999 Returns 0 on success.
1000 """
1000 """
1001 rev = []
1001 rev = []
1002 # backward compatibility
1002 # backward compatibility
1003 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1003 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1004 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1004 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1005 cmd = positional_1
1005 cmd = positional_1
1006 rev.append(positional_2)
1006 rev.append(positional_2)
1007 if cmd == b"good":
1007 if cmd == b"good":
1008 good = True
1008 good = True
1009 elif cmd == b"bad":
1009 elif cmd == b"bad":
1010 bad = True
1010 bad = True
1011 else:
1011 else:
1012 reset = True
1012 reset = True
1013 elif positional_2:
1013 elif positional_2:
1014 raise error.InputError(_(b'incompatible arguments'))
1014 raise error.InputError(_(b'incompatible arguments'))
1015 elif positional_1 is not None:
1015 elif positional_1 is not None:
1016 rev.append(positional_1)
1016 rev.append(positional_1)
1017
1017
1018 incompatibles = {
1018 incompatibles = {
1019 b'--bad': bad,
1019 b'--bad': bad,
1020 b'--command': bool(command),
1020 b'--command': bool(command),
1021 b'--extend': extend,
1021 b'--extend': extend,
1022 b'--good': good,
1022 b'--good': good,
1023 b'--reset': reset,
1023 b'--reset': reset,
1024 b'--skip': skip,
1024 b'--skip': skip,
1025 }
1025 }
1026
1026
1027 enabled = [x for x in incompatibles if incompatibles[x]]
1027 enabled = [x for x in incompatibles if incompatibles[x]]
1028
1028
1029 if len(enabled) > 1:
1029 if len(enabled) > 1:
1030 raise error.InputError(
1030 raise error.InputError(
1031 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1031 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1032 )
1032 )
1033
1033
1034 if reset:
1034 if reset:
1035 hbisect.resetstate(repo)
1035 hbisect.resetstate(repo)
1036 return
1036 return
1037
1037
1038 state = hbisect.load_state(repo)
1038 state = hbisect.load_state(repo)
1039
1039
1040 if rev:
1040 if rev:
1041 nodes = [repo[i].node() for i in scmutil.revrange(repo, rev)]
1041 nodes = [repo[i].node() for i in scmutil.revrange(repo, rev)]
1042 else:
1042 else:
1043 nodes = [repo.lookup(b'.')]
1043 nodes = [repo.lookup(b'.')]
1044
1044
1045 # update state
1045 # update state
1046 if good or bad or skip:
1046 if good or bad or skip:
1047 if good:
1047 if good:
1048 state[b'good'] += nodes
1048 state[b'good'] += nodes
1049 elif bad:
1049 elif bad:
1050 state[b'bad'] += nodes
1050 state[b'bad'] += nodes
1051 elif skip:
1051 elif skip:
1052 state[b'skip'] += nodes
1052 state[b'skip'] += nodes
1053 hbisect.save_state(repo, state)
1053 hbisect.save_state(repo, state)
1054 if not (state[b'good'] and state[b'bad']):
1054 if not (state[b'good'] and state[b'bad']):
1055 return
1055 return
1056
1056
1057 def mayupdate(repo, node, show_stats=True):
1057 def mayupdate(repo, node, show_stats=True):
1058 """common used update sequence"""
1058 """common used update sequence"""
1059 if noupdate:
1059 if noupdate:
1060 return
1060 return
1061 cmdutil.checkunfinished(repo)
1061 cmdutil.checkunfinished(repo)
1062 cmdutil.bailifchanged(repo)
1062 cmdutil.bailifchanged(repo)
1063 return hg.clean(repo, node, show_stats=show_stats)
1063 return hg.clean(repo, node, show_stats=show_stats)
1064
1064
1065 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1065 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1066
1066
1067 if command:
1067 if command:
1068 changesets = 1
1068 changesets = 1
1069 if noupdate:
1069 if noupdate:
1070 try:
1070 try:
1071 node = state[b'current'][0]
1071 node = state[b'current'][0]
1072 except LookupError:
1072 except LookupError:
1073 raise error.StateError(
1073 raise error.StateError(
1074 _(
1074 _(
1075 b'current bisect revision is unknown - '
1075 b'current bisect revision is unknown - '
1076 b'start a new bisect to fix'
1076 b'start a new bisect to fix'
1077 )
1077 )
1078 )
1078 )
1079 else:
1079 else:
1080 node, p2 = repo.dirstate.parents()
1080 node, p2 = repo.dirstate.parents()
1081 if p2 != nullid:
1081 if p2 != nullid:
1082 raise error.StateError(_(b'current bisect revision is a merge'))
1082 raise error.StateError(_(b'current bisect revision is a merge'))
1083 if rev:
1083 if rev:
1084 if not nodes:
1084 if not nodes:
1085 raise error.Abort(_(b'empty revision set'))
1085 raise error.Abort(_(b'empty revision set'))
1086 node = repo[nodes.last()].node()
1086 node = repo[nodes.last()].node()
1087 with hbisect.restore_state(repo, state, node):
1087 with hbisect.restore_state(repo, state, node):
1088 while changesets:
1088 while changesets:
1089 # update state
1089 # update state
1090 state[b'current'] = [node]
1090 state[b'current'] = [node]
1091 hbisect.save_state(repo, state)
1091 hbisect.save_state(repo, state)
1092 status = ui.system(
1092 status = ui.system(
1093 command,
1093 command,
1094 environ={b'HG_NODE': hex(node)},
1094 environ={b'HG_NODE': hex(node)},
1095 blockedtag=b'bisect_check',
1095 blockedtag=b'bisect_check',
1096 )
1096 )
1097 if status == 125:
1097 if status == 125:
1098 transition = b"skip"
1098 transition = b"skip"
1099 elif status == 0:
1099 elif status == 0:
1100 transition = b"good"
1100 transition = b"good"
1101 # status < 0 means process was killed
1101 # status < 0 means process was killed
1102 elif status == 127:
1102 elif status == 127:
1103 raise error.Abort(_(b"failed to execute %s") % command)
1103 raise error.Abort(_(b"failed to execute %s") % command)
1104 elif status < 0:
1104 elif status < 0:
1105 raise error.Abort(_(b"%s killed") % command)
1105 raise error.Abort(_(b"%s killed") % command)
1106 else:
1106 else:
1107 transition = b"bad"
1107 transition = b"bad"
1108 state[transition].append(node)
1108 state[transition].append(node)
1109 ctx = repo[node]
1109 ctx = repo[node]
1110 ui.status(
1110 ui.status(
1111 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1111 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1112 )
1112 )
1113 hbisect.checkstate(state)
1113 hbisect.checkstate(state)
1114 # bisect
1114 # bisect
1115 nodes, changesets, bgood = hbisect.bisect(repo, state)
1115 nodes, changesets, bgood = hbisect.bisect(repo, state)
1116 # update to next check
1116 # update to next check
1117 node = nodes[0]
1117 node = nodes[0]
1118 mayupdate(repo, node, show_stats=False)
1118 mayupdate(repo, node, show_stats=False)
1119 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1119 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1120 return
1120 return
1121
1121
1122 hbisect.checkstate(state)
1122 hbisect.checkstate(state)
1123
1123
1124 # actually bisect
1124 # actually bisect
1125 nodes, changesets, good = hbisect.bisect(repo, state)
1125 nodes, changesets, good = hbisect.bisect(repo, state)
1126 if extend:
1126 if extend:
1127 if not changesets:
1127 if not changesets:
1128 extendnode = hbisect.extendrange(repo, state, nodes, good)
1128 extendnode = hbisect.extendrange(repo, state, nodes, good)
1129 if extendnode is not None:
1129 if extendnode is not None:
1130 ui.write(
1130 ui.write(
1131 _(b"Extending search to changeset %d:%s\n")
1131 _(b"Extending search to changeset %d:%s\n")
1132 % (extendnode.rev(), extendnode)
1132 % (extendnode.rev(), extendnode)
1133 )
1133 )
1134 state[b'current'] = [extendnode.node()]
1134 state[b'current'] = [extendnode.node()]
1135 hbisect.save_state(repo, state)
1135 hbisect.save_state(repo, state)
1136 return mayupdate(repo, extendnode.node())
1136 return mayupdate(repo, extendnode.node())
1137 raise error.StateError(_(b"nothing to extend"))
1137 raise error.StateError(_(b"nothing to extend"))
1138
1138
1139 if changesets == 0:
1139 if changesets == 0:
1140 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1140 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1141 else:
1141 else:
1142 assert len(nodes) == 1 # only a single node can be tested next
1142 assert len(nodes) == 1 # only a single node can be tested next
1143 node = nodes[0]
1143 node = nodes[0]
1144 # compute the approximate number of remaining tests
1144 # compute the approximate number of remaining tests
1145 tests, size = 0, 2
1145 tests, size = 0, 2
1146 while size <= changesets:
1146 while size <= changesets:
1147 tests, size = tests + 1, size * 2
1147 tests, size = tests + 1, size * 2
1148 rev = repo.changelog.rev(node)
1148 rev = repo.changelog.rev(node)
1149 ui.write(
1149 ui.write(
1150 _(
1150 _(
1151 b"Testing changeset %d:%s "
1151 b"Testing changeset %d:%s "
1152 b"(%d changesets remaining, ~%d tests)\n"
1152 b"(%d changesets remaining, ~%d tests)\n"
1153 )
1153 )
1154 % (rev, short(node), changesets, tests)
1154 % (rev, short(node), changesets, tests)
1155 )
1155 )
1156 state[b'current'] = [node]
1156 state[b'current'] = [node]
1157 hbisect.save_state(repo, state)
1157 hbisect.save_state(repo, state)
1158 return mayupdate(repo, node)
1158 return mayupdate(repo, node)
1159
1159
1160
1160
1161 @command(
1161 @command(
1162 b'bookmarks|bookmark',
1162 b'bookmarks|bookmark',
1163 [
1163 [
1164 (b'f', b'force', False, _(b'force')),
1164 (b'f', b'force', False, _(b'force')),
1165 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1165 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1166 (b'd', b'delete', False, _(b'delete a given bookmark')),
1166 (b'd', b'delete', False, _(b'delete a given bookmark')),
1167 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1167 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1168 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1168 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1169 (b'l', b'list', False, _(b'list existing bookmarks')),
1169 (b'l', b'list', False, _(b'list existing bookmarks')),
1170 ]
1170 ]
1171 + formatteropts,
1171 + formatteropts,
1172 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1172 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1173 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1173 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1174 )
1174 )
1175 def bookmark(ui, repo, *names, **opts):
1175 def bookmark(ui, repo, *names, **opts):
1176 """create a new bookmark or list existing bookmarks
1176 """create a new bookmark or list existing bookmarks
1177
1177
1178 Bookmarks are labels on changesets to help track lines of development.
1178 Bookmarks are labels on changesets to help track lines of development.
1179 Bookmarks are unversioned and can be moved, renamed and deleted.
1179 Bookmarks are unversioned and can be moved, renamed and deleted.
1180 Deleting or moving a bookmark has no effect on the associated changesets.
1180 Deleting or moving a bookmark has no effect on the associated changesets.
1181
1181
1182 Creating or updating to a bookmark causes it to be marked as 'active'.
1182 Creating or updating to a bookmark causes it to be marked as 'active'.
1183 The active bookmark is indicated with a '*'.
1183 The active bookmark is indicated with a '*'.
1184 When a commit is made, the active bookmark will advance to the new commit.
1184 When a commit is made, the active bookmark will advance to the new commit.
1185 A plain :hg:`update` will also advance an active bookmark, if possible.
1185 A plain :hg:`update` will also advance an active bookmark, if possible.
1186 Updating away from a bookmark will cause it to be deactivated.
1186 Updating away from a bookmark will cause it to be deactivated.
1187
1187
1188 Bookmarks can be pushed and pulled between repositories (see
1188 Bookmarks can be pushed and pulled between repositories (see
1189 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1189 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1190 diverged, a new 'divergent bookmark' of the form 'name@path' will
1190 diverged, a new 'divergent bookmark' of the form 'name@path' will
1191 be created. Using :hg:`merge` will resolve the divergence.
1191 be created. Using :hg:`merge` will resolve the divergence.
1192
1192
1193 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1193 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1194 the active bookmark's name.
1194 the active bookmark's name.
1195
1195
1196 A bookmark named '@' has the special property that :hg:`clone` will
1196 A bookmark named '@' has the special property that :hg:`clone` will
1197 check it out by default if it exists.
1197 check it out by default if it exists.
1198
1198
1199 .. container:: verbose
1199 .. container:: verbose
1200
1200
1201 Template:
1201 Template:
1202
1202
1203 The following keywords are supported in addition to the common template
1203 The following keywords are supported in addition to the common template
1204 keywords and functions such as ``{bookmark}``. See also
1204 keywords and functions such as ``{bookmark}``. See also
1205 :hg:`help templates`.
1205 :hg:`help templates`.
1206
1206
1207 :active: Boolean. True if the bookmark is active.
1207 :active: Boolean. True if the bookmark is active.
1208
1208
1209 Examples:
1209 Examples:
1210
1210
1211 - create an active bookmark for a new line of development::
1211 - create an active bookmark for a new line of development::
1212
1212
1213 hg book new-feature
1213 hg book new-feature
1214
1214
1215 - create an inactive bookmark as a place marker::
1215 - create an inactive bookmark as a place marker::
1216
1216
1217 hg book -i reviewed
1217 hg book -i reviewed
1218
1218
1219 - create an inactive bookmark on another changeset::
1219 - create an inactive bookmark on another changeset::
1220
1220
1221 hg book -r .^ tested
1221 hg book -r .^ tested
1222
1222
1223 - rename bookmark turkey to dinner::
1223 - rename bookmark turkey to dinner::
1224
1224
1225 hg book -m turkey dinner
1225 hg book -m turkey dinner
1226
1226
1227 - move the '@' bookmark from another branch::
1227 - move the '@' bookmark from another branch::
1228
1228
1229 hg book -f @
1229 hg book -f @
1230
1230
1231 - print only the active bookmark name::
1231 - print only the active bookmark name::
1232
1232
1233 hg book -ql .
1233 hg book -ql .
1234 """
1234 """
1235 opts = pycompat.byteskwargs(opts)
1235 opts = pycompat.byteskwargs(opts)
1236 force = opts.get(b'force')
1236 force = opts.get(b'force')
1237 rev = opts.get(b'rev')
1237 rev = opts.get(b'rev')
1238 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1238 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1239
1239
1240 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1240 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1241 if action:
1241 if action:
1242 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1242 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1243 elif names or rev:
1243 elif names or rev:
1244 action = b'add'
1244 action = b'add'
1245 elif inactive:
1245 elif inactive:
1246 action = b'inactive' # meaning deactivate
1246 action = b'inactive' # meaning deactivate
1247 else:
1247 else:
1248 action = b'list'
1248 action = b'list'
1249
1249
1250 cmdutil.check_incompatible_arguments(
1250 cmdutil.check_incompatible_arguments(
1251 opts, b'inactive', [b'delete', b'list']
1251 opts, b'inactive', [b'delete', b'list']
1252 )
1252 )
1253 if not names and action in {b'add', b'delete'}:
1253 if not names and action in {b'add', b'delete'}:
1254 raise error.InputError(_(b"bookmark name required"))
1254 raise error.InputError(_(b"bookmark name required"))
1255
1255
1256 if action in {b'add', b'delete', b'rename', b'inactive'}:
1256 if action in {b'add', b'delete', b'rename', b'inactive'}:
1257 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1257 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1258 if action == b'delete':
1258 if action == b'delete':
1259 names = pycompat.maplist(repo._bookmarks.expandname, names)
1259 names = pycompat.maplist(repo._bookmarks.expandname, names)
1260 bookmarks.delete(repo, tr, names)
1260 bookmarks.delete(repo, tr, names)
1261 elif action == b'rename':
1261 elif action == b'rename':
1262 if not names:
1262 if not names:
1263 raise error.InputError(_(b"new bookmark name required"))
1263 raise error.InputError(_(b"new bookmark name required"))
1264 elif len(names) > 1:
1264 elif len(names) > 1:
1265 raise error.InputError(
1265 raise error.InputError(
1266 _(b"only one new bookmark name allowed")
1266 _(b"only one new bookmark name allowed")
1267 )
1267 )
1268 oldname = repo._bookmarks.expandname(opts[b'rename'])
1268 oldname = repo._bookmarks.expandname(opts[b'rename'])
1269 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1269 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1270 elif action == b'add':
1270 elif action == b'add':
1271 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1271 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1272 elif action == b'inactive':
1272 elif action == b'inactive':
1273 if len(repo._bookmarks) == 0:
1273 if len(repo._bookmarks) == 0:
1274 ui.status(_(b"no bookmarks set\n"))
1274 ui.status(_(b"no bookmarks set\n"))
1275 elif not repo._activebookmark:
1275 elif not repo._activebookmark:
1276 ui.status(_(b"no active bookmark\n"))
1276 ui.status(_(b"no active bookmark\n"))
1277 else:
1277 else:
1278 bookmarks.deactivate(repo)
1278 bookmarks.deactivate(repo)
1279 elif action == b'list':
1279 elif action == b'list':
1280 names = pycompat.maplist(repo._bookmarks.expandname, names)
1280 names = pycompat.maplist(repo._bookmarks.expandname, names)
1281 with ui.formatter(b'bookmarks', opts) as fm:
1281 with ui.formatter(b'bookmarks', opts) as fm:
1282 bookmarks.printbookmarks(ui, repo, fm, names)
1282 bookmarks.printbookmarks(ui, repo, fm, names)
1283 else:
1283 else:
1284 raise error.ProgrammingError(b'invalid action: %s' % action)
1284 raise error.ProgrammingError(b'invalid action: %s' % action)
1285
1285
1286
1286
1287 @command(
1287 @command(
1288 b'branch',
1288 b'branch',
1289 [
1289 [
1290 (
1290 (
1291 b'f',
1291 b'f',
1292 b'force',
1292 b'force',
1293 None,
1293 None,
1294 _(b'set branch name even if it shadows an existing branch'),
1294 _(b'set branch name even if it shadows an existing branch'),
1295 ),
1295 ),
1296 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1296 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1297 (
1297 (
1298 b'r',
1298 b'r',
1299 b'rev',
1299 b'rev',
1300 [],
1300 [],
1301 _(b'change branches of the given revs (EXPERIMENTAL)'),
1301 _(b'change branches of the given revs (EXPERIMENTAL)'),
1302 ),
1302 ),
1303 ],
1303 ],
1304 _(b'[-fC] [NAME]'),
1304 _(b'[-fC] [NAME]'),
1305 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1305 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1306 )
1306 )
1307 def branch(ui, repo, label=None, **opts):
1307 def branch(ui, repo, label=None, **opts):
1308 """set or show the current branch name
1308 """set or show the current branch name
1309
1309
1310 .. note::
1310 .. note::
1311
1311
1312 Branch names are permanent and global. Use :hg:`bookmark` to create a
1312 Branch names are permanent and global. Use :hg:`bookmark` to create a
1313 light-weight bookmark instead. See :hg:`help glossary` for more
1313 light-weight bookmark instead. See :hg:`help glossary` for more
1314 information about named branches and bookmarks.
1314 information about named branches and bookmarks.
1315
1315
1316 With no argument, show the current branch name. With one argument,
1316 With no argument, show the current branch name. With one argument,
1317 set the working directory branch name (the branch will not exist
1317 set the working directory branch name (the branch will not exist
1318 in the repository until the next commit). Standard practice
1318 in the repository until the next commit). Standard practice
1319 recommends that primary development take place on the 'default'
1319 recommends that primary development take place on the 'default'
1320 branch.
1320 branch.
1321
1321
1322 Unless -f/--force is specified, branch will not let you set a
1322 Unless -f/--force is specified, branch will not let you set a
1323 branch name that already exists.
1323 branch name that already exists.
1324
1324
1325 Use -C/--clean to reset the working directory branch to that of
1325 Use -C/--clean to reset the working directory branch to that of
1326 the parent of the working directory, negating a previous branch
1326 the parent of the working directory, negating a previous branch
1327 change.
1327 change.
1328
1328
1329 Use the command :hg:`update` to switch to an existing branch. Use
1329 Use the command :hg:`update` to switch to an existing branch. Use
1330 :hg:`commit --close-branch` to mark this branch head as closed.
1330 :hg:`commit --close-branch` to mark this branch head as closed.
1331 When all heads of a branch are closed, the branch will be
1331 When all heads of a branch are closed, the branch will be
1332 considered closed.
1332 considered closed.
1333
1333
1334 Returns 0 on success.
1334 Returns 0 on success.
1335 """
1335 """
1336 opts = pycompat.byteskwargs(opts)
1336 opts = pycompat.byteskwargs(opts)
1337 revs = opts.get(b'rev')
1337 revs = opts.get(b'rev')
1338 if label:
1338 if label:
1339 label = label.strip()
1339 label = label.strip()
1340
1340
1341 if not opts.get(b'clean') and not label:
1341 if not opts.get(b'clean') and not label:
1342 if revs:
1342 if revs:
1343 raise error.InputError(
1343 raise error.InputError(
1344 _(b"no branch name specified for the revisions")
1344 _(b"no branch name specified for the revisions")
1345 )
1345 )
1346 ui.write(b"%s\n" % repo.dirstate.branch())
1346 ui.write(b"%s\n" % repo.dirstate.branch())
1347 return
1347 return
1348
1348
1349 with repo.wlock():
1349 with repo.wlock():
1350 if opts.get(b'clean'):
1350 if opts.get(b'clean'):
1351 label = repo[b'.'].branch()
1351 label = repo[b'.'].branch()
1352 repo.dirstate.setbranch(label)
1352 repo.dirstate.setbranch(label)
1353 ui.status(_(b'reset working directory to branch %s\n') % label)
1353 ui.status(_(b'reset working directory to branch %s\n') % label)
1354 elif label:
1354 elif label:
1355
1355
1356 scmutil.checknewlabel(repo, label, b'branch')
1356 scmutil.checknewlabel(repo, label, b'branch')
1357 if revs:
1357 if revs:
1358 return cmdutil.changebranch(ui, repo, revs, label, opts)
1358 return cmdutil.changebranch(ui, repo, revs, label, opts)
1359
1359
1360 if not opts.get(b'force') and label in repo.branchmap():
1360 if not opts.get(b'force') and label in repo.branchmap():
1361 if label not in [p.branch() for p in repo[None].parents()]:
1361 if label not in [p.branch() for p in repo[None].parents()]:
1362 raise error.InputError(
1362 raise error.InputError(
1363 _(b'a branch of the same name already exists'),
1363 _(b'a branch of the same name already exists'),
1364 # i18n: "it" refers to an existing branch
1364 # i18n: "it" refers to an existing branch
1365 hint=_(b"use 'hg update' to switch to it"),
1365 hint=_(b"use 'hg update' to switch to it"),
1366 )
1366 )
1367
1367
1368 repo.dirstate.setbranch(label)
1368 repo.dirstate.setbranch(label)
1369 ui.status(_(b'marked working directory as branch %s\n') % label)
1369 ui.status(_(b'marked working directory as branch %s\n') % label)
1370
1370
1371 # find any open named branches aside from default
1371 # find any open named branches aside from default
1372 for n, h, t, c in repo.branchmap().iterbranches():
1372 for n, h, t, c in repo.branchmap().iterbranches():
1373 if n != b"default" and not c:
1373 if n != b"default" and not c:
1374 return 0
1374 return 0
1375 ui.status(
1375 ui.status(
1376 _(
1376 _(
1377 b'(branches are permanent and global, '
1377 b'(branches are permanent and global, '
1378 b'did you want a bookmark?)\n'
1378 b'did you want a bookmark?)\n'
1379 )
1379 )
1380 )
1380 )
1381
1381
1382
1382
1383 @command(
1383 @command(
1384 b'branches',
1384 b'branches',
1385 [
1385 [
1386 (
1386 (
1387 b'a',
1387 b'a',
1388 b'active',
1388 b'active',
1389 False,
1389 False,
1390 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1390 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1391 ),
1391 ),
1392 (b'c', b'closed', False, _(b'show normal and closed branches')),
1392 (b'c', b'closed', False, _(b'show normal and closed branches')),
1393 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1393 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1394 ]
1394 ]
1395 + formatteropts,
1395 + formatteropts,
1396 _(b'[-c]'),
1396 _(b'[-c]'),
1397 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1397 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1398 intents={INTENT_READONLY},
1398 intents={INTENT_READONLY},
1399 )
1399 )
1400 def branches(ui, repo, active=False, closed=False, **opts):
1400 def branches(ui, repo, active=False, closed=False, **opts):
1401 """list repository named branches
1401 """list repository named branches
1402
1402
1403 List the repository's named branches, indicating which ones are
1403 List the repository's named branches, indicating which ones are
1404 inactive. If -c/--closed is specified, also list branches which have
1404 inactive. If -c/--closed is specified, also list branches which have
1405 been marked closed (see :hg:`commit --close-branch`).
1405 been marked closed (see :hg:`commit --close-branch`).
1406
1406
1407 Use the command :hg:`update` to switch to an existing branch.
1407 Use the command :hg:`update` to switch to an existing branch.
1408
1408
1409 .. container:: verbose
1409 .. container:: verbose
1410
1410
1411 Template:
1411 Template:
1412
1412
1413 The following keywords are supported in addition to the common template
1413 The following keywords are supported in addition to the common template
1414 keywords and functions such as ``{branch}``. See also
1414 keywords and functions such as ``{branch}``. See also
1415 :hg:`help templates`.
1415 :hg:`help templates`.
1416
1416
1417 :active: Boolean. True if the branch is active.
1417 :active: Boolean. True if the branch is active.
1418 :closed: Boolean. True if the branch is closed.
1418 :closed: Boolean. True if the branch is closed.
1419 :current: Boolean. True if it is the current branch.
1419 :current: Boolean. True if it is the current branch.
1420
1420
1421 Returns 0.
1421 Returns 0.
1422 """
1422 """
1423
1423
1424 opts = pycompat.byteskwargs(opts)
1424 opts = pycompat.byteskwargs(opts)
1425 revs = opts.get(b'rev')
1425 revs = opts.get(b'rev')
1426 selectedbranches = None
1426 selectedbranches = None
1427 if revs:
1427 if revs:
1428 revs = scmutil.revrange(repo, revs)
1428 revs = scmutil.revrange(repo, revs)
1429 getbi = repo.revbranchcache().branchinfo
1429 getbi = repo.revbranchcache().branchinfo
1430 selectedbranches = {getbi(r)[0] for r in revs}
1430 selectedbranches = {getbi(r)[0] for r in revs}
1431
1431
1432 ui.pager(b'branches')
1432 ui.pager(b'branches')
1433 fm = ui.formatter(b'branches', opts)
1433 fm = ui.formatter(b'branches', opts)
1434 hexfunc = fm.hexfunc
1434 hexfunc = fm.hexfunc
1435
1435
1436 allheads = set(repo.heads())
1436 allheads = set(repo.heads())
1437 branches = []
1437 branches = []
1438 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1438 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1439 if selectedbranches is not None and tag not in selectedbranches:
1439 if selectedbranches is not None and tag not in selectedbranches:
1440 continue
1440 continue
1441 isactive = False
1441 isactive = False
1442 if not isclosed:
1442 if not isclosed:
1443 openheads = set(repo.branchmap().iteropen(heads))
1443 openheads = set(repo.branchmap().iteropen(heads))
1444 isactive = bool(openheads & allheads)
1444 isactive = bool(openheads & allheads)
1445 branches.append((tag, repo[tip], isactive, not isclosed))
1445 branches.append((tag, repo[tip], isactive, not isclosed))
1446 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1446 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1447
1447
1448 for tag, ctx, isactive, isopen in branches:
1448 for tag, ctx, isactive, isopen in branches:
1449 if active and not isactive:
1449 if active and not isactive:
1450 continue
1450 continue
1451 if isactive:
1451 if isactive:
1452 label = b'branches.active'
1452 label = b'branches.active'
1453 notice = b''
1453 notice = b''
1454 elif not isopen:
1454 elif not isopen:
1455 if not closed:
1455 if not closed:
1456 continue
1456 continue
1457 label = b'branches.closed'
1457 label = b'branches.closed'
1458 notice = _(b' (closed)')
1458 notice = _(b' (closed)')
1459 else:
1459 else:
1460 label = b'branches.inactive'
1460 label = b'branches.inactive'
1461 notice = _(b' (inactive)')
1461 notice = _(b' (inactive)')
1462 current = tag == repo.dirstate.branch()
1462 current = tag == repo.dirstate.branch()
1463 if current:
1463 if current:
1464 label = b'branches.current'
1464 label = b'branches.current'
1465
1465
1466 fm.startitem()
1466 fm.startitem()
1467 fm.write(b'branch', b'%s', tag, label=label)
1467 fm.write(b'branch', b'%s', tag, label=label)
1468 rev = ctx.rev()
1468 rev = ctx.rev()
1469 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1469 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1470 fmt = b' ' * padsize + b' %d:%s'
1470 fmt = b' ' * padsize + b' %d:%s'
1471 fm.condwrite(
1471 fm.condwrite(
1472 not ui.quiet,
1472 not ui.quiet,
1473 b'rev node',
1473 b'rev node',
1474 fmt,
1474 fmt,
1475 rev,
1475 rev,
1476 hexfunc(ctx.node()),
1476 hexfunc(ctx.node()),
1477 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1477 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1478 )
1478 )
1479 fm.context(ctx=ctx)
1479 fm.context(ctx=ctx)
1480 fm.data(active=isactive, closed=not isopen, current=current)
1480 fm.data(active=isactive, closed=not isopen, current=current)
1481 if not ui.quiet:
1481 if not ui.quiet:
1482 fm.plain(notice)
1482 fm.plain(notice)
1483 fm.plain(b'\n')
1483 fm.plain(b'\n')
1484 fm.end()
1484 fm.end()
1485
1485
1486
1486
1487 @command(
1487 @command(
1488 b'bundle',
1488 b'bundle',
1489 [
1489 [
1490 (
1490 (
1491 b'f',
1491 b'f',
1492 b'force',
1492 b'force',
1493 None,
1493 None,
1494 _(b'run even when the destination is unrelated'),
1494 _(b'run even when the destination is unrelated'),
1495 ),
1495 ),
1496 (
1496 (
1497 b'r',
1497 b'r',
1498 b'rev',
1498 b'rev',
1499 [],
1499 [],
1500 _(b'a changeset intended to be added to the destination'),
1500 _(b'a changeset intended to be added to the destination'),
1501 _(b'REV'),
1501 _(b'REV'),
1502 ),
1502 ),
1503 (
1503 (
1504 b'b',
1504 b'b',
1505 b'branch',
1505 b'branch',
1506 [],
1506 [],
1507 _(b'a specific branch you would like to bundle'),
1507 _(b'a specific branch you would like to bundle'),
1508 _(b'BRANCH'),
1508 _(b'BRANCH'),
1509 ),
1509 ),
1510 (
1510 (
1511 b'',
1511 b'',
1512 b'base',
1512 b'base',
1513 [],
1513 [],
1514 _(b'a base changeset assumed to be available at the destination'),
1514 _(b'a base changeset assumed to be available at the destination'),
1515 _(b'REV'),
1515 _(b'REV'),
1516 ),
1516 ),
1517 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1517 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1518 (
1518 (
1519 b't',
1519 b't',
1520 b'type',
1520 b'type',
1521 b'bzip2',
1521 b'bzip2',
1522 _(b'bundle compression type to use'),
1522 _(b'bundle compression type to use'),
1523 _(b'TYPE'),
1523 _(b'TYPE'),
1524 ),
1524 ),
1525 ]
1525 ]
1526 + remoteopts,
1526 + remoteopts,
1527 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1527 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1528 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1528 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1529 )
1529 )
1530 def bundle(ui, repo, fname, dest=None, **opts):
1530 def bundle(ui, repo, fname, dest=None, **opts):
1531 """create a bundle file
1531 """create a bundle file
1532
1532
1533 Generate a bundle file containing data to be transferred to another
1533 Generate a bundle file containing data to be transferred to another
1534 repository.
1534 repository.
1535
1535
1536 To create a bundle containing all changesets, use -a/--all
1536 To create a bundle containing all changesets, use -a/--all
1537 (or --base null). Otherwise, hg assumes the destination will have
1537 (or --base null). Otherwise, hg assumes the destination will have
1538 all the nodes you specify with --base parameters. Otherwise, hg
1538 all the nodes you specify with --base parameters. Otherwise, hg
1539 will assume the repository has all the nodes in destination, or
1539 will assume the repository has all the nodes in destination, or
1540 default-push/default if no destination is specified, where destination
1540 default-push/default if no destination is specified, where destination
1541 is the repository you provide through DEST option.
1541 is the repository you provide through DEST option.
1542
1542
1543 You can change bundle format with the -t/--type option. See
1543 You can change bundle format with the -t/--type option. See
1544 :hg:`help bundlespec` for documentation on this format. By default,
1544 :hg:`help bundlespec` for documentation on this format. By default,
1545 the most appropriate format is used and compression defaults to
1545 the most appropriate format is used and compression defaults to
1546 bzip2.
1546 bzip2.
1547
1547
1548 The bundle file can then be transferred using conventional means
1548 The bundle file can then be transferred using conventional means
1549 and applied to another repository with the unbundle or pull
1549 and applied to another repository with the unbundle or pull
1550 command. This is useful when direct push and pull are not
1550 command. This is useful when direct push and pull are not
1551 available or when exporting an entire repository is undesirable.
1551 available or when exporting an entire repository is undesirable.
1552
1552
1553 Applying bundles preserves all changeset contents including
1553 Applying bundles preserves all changeset contents including
1554 permissions, copy/rename information, and revision history.
1554 permissions, copy/rename information, and revision history.
1555
1555
1556 Returns 0 on success, 1 if no changes found.
1556 Returns 0 on success, 1 if no changes found.
1557 """
1557 """
1558 opts = pycompat.byteskwargs(opts)
1558 opts = pycompat.byteskwargs(opts)
1559 revs = None
1559 revs = None
1560 if b'rev' in opts:
1560 if b'rev' in opts:
1561 revstrings = opts[b'rev']
1561 revstrings = opts[b'rev']
1562 revs = scmutil.revrange(repo, revstrings)
1562 revs = scmutil.revrange(repo, revstrings)
1563 if revstrings and not revs:
1563 if revstrings and not revs:
1564 raise error.InputError(_(b'no commits to bundle'))
1564 raise error.InputError(_(b'no commits to bundle'))
1565
1565
1566 bundletype = opts.get(b'type', b'bzip2').lower()
1566 bundletype = opts.get(b'type', b'bzip2').lower()
1567 try:
1567 try:
1568 bundlespec = bundlecaches.parsebundlespec(
1568 bundlespec = bundlecaches.parsebundlespec(
1569 repo, bundletype, strict=False
1569 repo, bundletype, strict=False
1570 )
1570 )
1571 except error.UnsupportedBundleSpecification as e:
1571 except error.UnsupportedBundleSpecification as e:
1572 raise error.InputError(
1572 raise error.InputError(
1573 pycompat.bytestr(e),
1573 pycompat.bytestr(e),
1574 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1574 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1575 )
1575 )
1576 cgversion = bundlespec.contentopts[b"cg.version"]
1576 cgversion = bundlespec.contentopts[b"cg.version"]
1577
1577
1578 # Packed bundles are a pseudo bundle format for now.
1578 # Packed bundles are a pseudo bundle format for now.
1579 if cgversion == b's1':
1579 if cgversion == b's1':
1580 raise error.InputError(
1580 raise error.InputError(
1581 _(b'packed bundles cannot be produced by "hg bundle"'),
1581 _(b'packed bundles cannot be produced by "hg bundle"'),
1582 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1582 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1583 )
1583 )
1584
1584
1585 if opts.get(b'all'):
1585 if opts.get(b'all'):
1586 if dest:
1586 if dest:
1587 raise error.InputError(
1587 raise error.InputError(
1588 _(b"--all is incompatible with specifying a destination")
1588 _(b"--all is incompatible with specifying a destination")
1589 )
1589 )
1590 if opts.get(b'base'):
1590 if opts.get(b'base'):
1591 ui.warn(_(b"ignoring --base because --all was specified\n"))
1591 ui.warn(_(b"ignoring --base because --all was specified\n"))
1592 base = [nullrev]
1592 base = [nullrev]
1593 else:
1593 else:
1594 base = scmutil.revrange(repo, opts.get(b'base'))
1594 base = scmutil.revrange(repo, opts.get(b'base'))
1595 if cgversion not in changegroup.supportedoutgoingversions(repo):
1595 if cgversion not in changegroup.supportedoutgoingversions(repo):
1596 raise error.Abort(
1596 raise error.Abort(
1597 _(b"repository does not support bundle version %s") % cgversion
1597 _(b"repository does not support bundle version %s") % cgversion
1598 )
1598 )
1599
1599
1600 if base:
1600 if base:
1601 if dest:
1601 if dest:
1602 raise error.InputError(
1602 raise error.InputError(
1603 _(b"--base is incompatible with specifying a destination")
1603 _(b"--base is incompatible with specifying a destination")
1604 )
1604 )
1605 common = [repo[rev].node() for rev in base]
1605 common = [repo[rev].node() for rev in base]
1606 heads = [repo[r].node() for r in revs] if revs else None
1606 heads = [repo[r].node() for r in revs] if revs else None
1607 outgoing = discovery.outgoing(repo, common, heads)
1607 outgoing = discovery.outgoing(repo, common, heads)
1608 else:
1608 else:
1609 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1609 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1610 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1610 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1611 other = hg.peer(repo, opts, dest)
1611 other = hg.peer(repo, opts, dest)
1612 revs = [repo[r].hex() for r in revs]
1612 revs = [repo[r].hex() for r in revs]
1613 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1613 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1614 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1614 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1615 outgoing = discovery.findcommonoutgoing(
1615 outgoing = discovery.findcommonoutgoing(
1616 repo,
1616 repo,
1617 other,
1617 other,
1618 onlyheads=heads,
1618 onlyheads=heads,
1619 force=opts.get(b'force'),
1619 force=opts.get(b'force'),
1620 portable=True,
1620 portable=True,
1621 )
1621 )
1622
1622
1623 if not outgoing.missing:
1623 if not outgoing.missing:
1624 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1624 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1625 return 1
1625 return 1
1626
1626
1627 if cgversion == b'01': # bundle1
1627 if cgversion == b'01': # bundle1
1628 bversion = b'HG10' + bundlespec.wirecompression
1628 bversion = b'HG10' + bundlespec.wirecompression
1629 bcompression = None
1629 bcompression = None
1630 elif cgversion in (b'02', b'03'):
1630 elif cgversion in (b'02', b'03'):
1631 bversion = b'HG20'
1631 bversion = b'HG20'
1632 bcompression = bundlespec.wirecompression
1632 bcompression = bundlespec.wirecompression
1633 else:
1633 else:
1634 raise error.ProgrammingError(
1634 raise error.ProgrammingError(
1635 b'bundle: unexpected changegroup version %s' % cgversion
1635 b'bundle: unexpected changegroup version %s' % cgversion
1636 )
1636 )
1637
1637
1638 # TODO compression options should be derived from bundlespec parsing.
1638 # TODO compression options should be derived from bundlespec parsing.
1639 # This is a temporary hack to allow adjusting bundle compression
1639 # This is a temporary hack to allow adjusting bundle compression
1640 # level without a) formalizing the bundlespec changes to declare it
1640 # level without a) formalizing the bundlespec changes to declare it
1641 # b) introducing a command flag.
1641 # b) introducing a command flag.
1642 compopts = {}
1642 compopts = {}
1643 complevel = ui.configint(
1643 complevel = ui.configint(
1644 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1644 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1645 )
1645 )
1646 if complevel is None:
1646 if complevel is None:
1647 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1647 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1648 if complevel is not None:
1648 if complevel is not None:
1649 compopts[b'level'] = complevel
1649 compopts[b'level'] = complevel
1650
1650
1651 # Bundling of obsmarker and phases is optional as not all clients
1651 # Bundling of obsmarker and phases is optional as not all clients
1652 # support the necessary features.
1652 # support the necessary features.
1653 cfg = ui.configbool
1653 cfg = ui.configbool
1654 contentopts = {
1654 contentopts = {
1655 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1655 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1656 b'obsolescence-mandatory': cfg(
1656 b'obsolescence-mandatory': cfg(
1657 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1657 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1658 ),
1658 ),
1659 b'phases': cfg(b'experimental', b'bundle-phases'),
1659 b'phases': cfg(b'experimental', b'bundle-phases'),
1660 }
1660 }
1661 bundlespec.contentopts.update(contentopts)
1661 bundlespec.contentopts.update(contentopts)
1662
1662
1663 bundle2.writenewbundle(
1663 bundle2.writenewbundle(
1664 ui,
1664 ui,
1665 repo,
1665 repo,
1666 b'bundle',
1666 b'bundle',
1667 fname,
1667 fname,
1668 bversion,
1668 bversion,
1669 outgoing,
1669 outgoing,
1670 bundlespec.contentopts,
1670 bundlespec.contentopts,
1671 compression=bcompression,
1671 compression=bcompression,
1672 compopts=compopts,
1672 compopts=compopts,
1673 )
1673 )
1674
1674
1675
1675
1676 @command(
1676 @command(
1677 b'cat',
1677 b'cat',
1678 [
1678 [
1679 (
1679 (
1680 b'o',
1680 b'o',
1681 b'output',
1681 b'output',
1682 b'',
1682 b'',
1683 _(b'print output to file with formatted name'),
1683 _(b'print output to file with formatted name'),
1684 _(b'FORMAT'),
1684 _(b'FORMAT'),
1685 ),
1685 ),
1686 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1686 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1687 (b'', b'decode', None, _(b'apply any matching decode filter')),
1687 (b'', b'decode', None, _(b'apply any matching decode filter')),
1688 ]
1688 ]
1689 + walkopts
1689 + walkopts
1690 + formatteropts,
1690 + formatteropts,
1691 _(b'[OPTION]... FILE...'),
1691 _(b'[OPTION]... FILE...'),
1692 helpcategory=command.CATEGORY_FILE_CONTENTS,
1692 helpcategory=command.CATEGORY_FILE_CONTENTS,
1693 inferrepo=True,
1693 inferrepo=True,
1694 intents={INTENT_READONLY},
1694 intents={INTENT_READONLY},
1695 )
1695 )
1696 def cat(ui, repo, file1, *pats, **opts):
1696 def cat(ui, repo, file1, *pats, **opts):
1697 """output the current or given revision of files
1697 """output the current or given revision of files
1698
1698
1699 Print the specified files as they were at the given revision. If
1699 Print the specified files as they were at the given revision. If
1700 no revision is given, the parent of the working directory is used.
1700 no revision is given, the parent of the working directory is used.
1701
1701
1702 Output may be to a file, in which case the name of the file is
1702 Output may be to a file, in which case the name of the file is
1703 given using a template string. See :hg:`help templates`. In addition
1703 given using a template string. See :hg:`help templates`. In addition
1704 to the common template keywords, the following formatting rules are
1704 to the common template keywords, the following formatting rules are
1705 supported:
1705 supported:
1706
1706
1707 :``%%``: literal "%" character
1707 :``%%``: literal "%" character
1708 :``%s``: basename of file being printed
1708 :``%s``: basename of file being printed
1709 :``%d``: dirname of file being printed, or '.' if in repository root
1709 :``%d``: dirname of file being printed, or '.' if in repository root
1710 :``%p``: root-relative path name of file being printed
1710 :``%p``: root-relative path name of file being printed
1711 :``%H``: changeset hash (40 hexadecimal digits)
1711 :``%H``: changeset hash (40 hexadecimal digits)
1712 :``%R``: changeset revision number
1712 :``%R``: changeset revision number
1713 :``%h``: short-form changeset hash (12 hexadecimal digits)
1713 :``%h``: short-form changeset hash (12 hexadecimal digits)
1714 :``%r``: zero-padded changeset revision number
1714 :``%r``: zero-padded changeset revision number
1715 :``%b``: basename of the exporting repository
1715 :``%b``: basename of the exporting repository
1716 :``\\``: literal "\\" character
1716 :``\\``: literal "\\" character
1717
1717
1718 .. container:: verbose
1718 .. container:: verbose
1719
1719
1720 Template:
1720 Template:
1721
1721
1722 The following keywords are supported in addition to the common template
1722 The following keywords are supported in addition to the common template
1723 keywords and functions. See also :hg:`help templates`.
1723 keywords and functions. See also :hg:`help templates`.
1724
1724
1725 :data: String. File content.
1725 :data: String. File content.
1726 :path: String. Repository-absolute path of the file.
1726 :path: String. Repository-absolute path of the file.
1727
1727
1728 Returns 0 on success.
1728 Returns 0 on success.
1729 """
1729 """
1730 opts = pycompat.byteskwargs(opts)
1730 opts = pycompat.byteskwargs(opts)
1731 rev = opts.get(b'rev')
1731 rev = opts.get(b'rev')
1732 if rev:
1732 if rev:
1733 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1733 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1734 ctx = scmutil.revsingle(repo, rev)
1734 ctx = scmutil.revsingle(repo, rev)
1735 m = scmutil.match(ctx, (file1,) + pats, opts)
1735 m = scmutil.match(ctx, (file1,) + pats, opts)
1736 fntemplate = opts.pop(b'output', b'')
1736 fntemplate = opts.pop(b'output', b'')
1737 if cmdutil.isstdiofilename(fntemplate):
1737 if cmdutil.isstdiofilename(fntemplate):
1738 fntemplate = b''
1738 fntemplate = b''
1739
1739
1740 if fntemplate:
1740 if fntemplate:
1741 fm = formatter.nullformatter(ui, b'cat', opts)
1741 fm = formatter.nullformatter(ui, b'cat', opts)
1742 else:
1742 else:
1743 ui.pager(b'cat')
1743 ui.pager(b'cat')
1744 fm = ui.formatter(b'cat', opts)
1744 fm = ui.formatter(b'cat', opts)
1745 with fm:
1745 with fm:
1746 return cmdutil.cat(
1746 return cmdutil.cat(
1747 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1747 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1748 )
1748 )
1749
1749
1750
1750
1751 @command(
1751 @command(
1752 b'clone',
1752 b'clone',
1753 [
1753 [
1754 (
1754 (
1755 b'U',
1755 b'U',
1756 b'noupdate',
1756 b'noupdate',
1757 None,
1757 None,
1758 _(
1758 _(
1759 b'the clone will include an empty working '
1759 b'the clone will include an empty working '
1760 b'directory (only a repository)'
1760 b'directory (only a repository)'
1761 ),
1761 ),
1762 ),
1762 ),
1763 (
1763 (
1764 b'u',
1764 b'u',
1765 b'updaterev',
1765 b'updaterev',
1766 b'',
1766 b'',
1767 _(b'revision, tag, or branch to check out'),
1767 _(b'revision, tag, or branch to check out'),
1768 _(b'REV'),
1768 _(b'REV'),
1769 ),
1769 ),
1770 (
1770 (
1771 b'r',
1771 b'r',
1772 b'rev',
1772 b'rev',
1773 [],
1773 [],
1774 _(
1774 _(
1775 b'do not clone everything, but include this changeset'
1775 b'do not clone everything, but include this changeset'
1776 b' and its ancestors'
1776 b' and its ancestors'
1777 ),
1777 ),
1778 _(b'REV'),
1778 _(b'REV'),
1779 ),
1779 ),
1780 (
1780 (
1781 b'b',
1781 b'b',
1782 b'branch',
1782 b'branch',
1783 [],
1783 [],
1784 _(
1784 _(
1785 b'do not clone everything, but include this branch\'s'
1785 b'do not clone everything, but include this branch\'s'
1786 b' changesets and their ancestors'
1786 b' changesets and their ancestors'
1787 ),
1787 ),
1788 _(b'BRANCH'),
1788 _(b'BRANCH'),
1789 ),
1789 ),
1790 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1790 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1791 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1791 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1792 (b'', b'stream', None, _(b'clone with minimal data processing')),
1792 (b'', b'stream', None, _(b'clone with minimal data processing')),
1793 ]
1793 ]
1794 + remoteopts,
1794 + remoteopts,
1795 _(b'[OPTION]... SOURCE [DEST]'),
1795 _(b'[OPTION]... SOURCE [DEST]'),
1796 helpcategory=command.CATEGORY_REPO_CREATION,
1796 helpcategory=command.CATEGORY_REPO_CREATION,
1797 helpbasic=True,
1797 helpbasic=True,
1798 norepo=True,
1798 norepo=True,
1799 )
1799 )
1800 def clone(ui, source, dest=None, **opts):
1800 def clone(ui, source, dest=None, **opts):
1801 """make a copy of an existing repository
1801 """make a copy of an existing repository
1802
1802
1803 Create a copy of an existing repository in a new directory.
1803 Create a copy of an existing repository in a new directory.
1804
1804
1805 If no destination directory name is specified, it defaults to the
1805 If no destination directory name is specified, it defaults to the
1806 basename of the source.
1806 basename of the source.
1807
1807
1808 The location of the source is added to the new repository's
1808 The location of the source is added to the new repository's
1809 ``.hg/hgrc`` file, as the default to be used for future pulls.
1809 ``.hg/hgrc`` file, as the default to be used for future pulls.
1810
1810
1811 Only local paths and ``ssh://`` URLs are supported as
1811 Only local paths and ``ssh://`` URLs are supported as
1812 destinations. For ``ssh://`` destinations, no working directory or
1812 destinations. For ``ssh://`` destinations, no working directory or
1813 ``.hg/hgrc`` will be created on the remote side.
1813 ``.hg/hgrc`` will be created on the remote side.
1814
1814
1815 If the source repository has a bookmark called '@' set, that
1815 If the source repository has a bookmark called '@' set, that
1816 revision will be checked out in the new repository by default.
1816 revision will be checked out in the new repository by default.
1817
1817
1818 To check out a particular version, use -u/--update, or
1818 To check out a particular version, use -u/--update, or
1819 -U/--noupdate to create a clone with no working directory.
1819 -U/--noupdate to create a clone with no working directory.
1820
1820
1821 To pull only a subset of changesets, specify one or more revisions
1821 To pull only a subset of changesets, specify one or more revisions
1822 identifiers with -r/--rev or branches with -b/--branch. The
1822 identifiers with -r/--rev or branches with -b/--branch. The
1823 resulting clone will contain only the specified changesets and
1823 resulting clone will contain only the specified changesets and
1824 their ancestors. These options (or 'clone src#rev dest') imply
1824 their ancestors. These options (or 'clone src#rev dest') imply
1825 --pull, even for local source repositories.
1825 --pull, even for local source repositories.
1826
1826
1827 In normal clone mode, the remote normalizes repository data into a common
1827 In normal clone mode, the remote normalizes repository data into a common
1828 exchange format and the receiving end translates this data into its local
1828 exchange format and the receiving end translates this data into its local
1829 storage format. --stream activates a different clone mode that essentially
1829 storage format. --stream activates a different clone mode that essentially
1830 copies repository files from the remote with minimal data processing. This
1830 copies repository files from the remote with minimal data processing. This
1831 significantly reduces the CPU cost of a clone both remotely and locally.
1831 significantly reduces the CPU cost of a clone both remotely and locally.
1832 However, it often increases the transferred data size by 30-40%. This can
1832 However, it often increases the transferred data size by 30-40%. This can
1833 result in substantially faster clones where I/O throughput is plentiful,
1833 result in substantially faster clones where I/O throughput is plentiful,
1834 especially for larger repositories. A side-effect of --stream clones is
1834 especially for larger repositories. A side-effect of --stream clones is
1835 that storage settings and requirements on the remote are applied locally:
1835 that storage settings and requirements on the remote are applied locally:
1836 a modern client may inherit legacy or inefficient storage used by the
1836 a modern client may inherit legacy or inefficient storage used by the
1837 remote or a legacy Mercurial client may not be able to clone from a
1837 remote or a legacy Mercurial client may not be able to clone from a
1838 modern Mercurial remote.
1838 modern Mercurial remote.
1839
1839
1840 .. note::
1840 .. note::
1841
1841
1842 Specifying a tag will include the tagged changeset but not the
1842 Specifying a tag will include the tagged changeset but not the
1843 changeset containing the tag.
1843 changeset containing the tag.
1844
1844
1845 .. container:: verbose
1845 .. container:: verbose
1846
1846
1847 For efficiency, hardlinks are used for cloning whenever the
1847 For efficiency, hardlinks are used for cloning whenever the
1848 source and destination are on the same filesystem (note this
1848 source and destination are on the same filesystem (note this
1849 applies only to the repository data, not to the working
1849 applies only to the repository data, not to the working
1850 directory). Some filesystems, such as AFS, implement hardlinking
1850 directory). Some filesystems, such as AFS, implement hardlinking
1851 incorrectly, but do not report errors. In these cases, use the
1851 incorrectly, but do not report errors. In these cases, use the
1852 --pull option to avoid hardlinking.
1852 --pull option to avoid hardlinking.
1853
1853
1854 Mercurial will update the working directory to the first applicable
1854 Mercurial will update the working directory to the first applicable
1855 revision from this list:
1855 revision from this list:
1856
1856
1857 a) null if -U or the source repository has no changesets
1857 a) null if -U or the source repository has no changesets
1858 b) if -u . and the source repository is local, the first parent of
1858 b) if -u . and the source repository is local, the first parent of
1859 the source repository's working directory
1859 the source repository's working directory
1860 c) the changeset specified with -u (if a branch name, this means the
1860 c) the changeset specified with -u (if a branch name, this means the
1861 latest head of that branch)
1861 latest head of that branch)
1862 d) the changeset specified with -r
1862 d) the changeset specified with -r
1863 e) the tipmost head specified with -b
1863 e) the tipmost head specified with -b
1864 f) the tipmost head specified with the url#branch source syntax
1864 f) the tipmost head specified with the url#branch source syntax
1865 g) the revision marked with the '@' bookmark, if present
1865 g) the revision marked with the '@' bookmark, if present
1866 h) the tipmost head of the default branch
1866 h) the tipmost head of the default branch
1867 i) tip
1867 i) tip
1868
1868
1869 When cloning from servers that support it, Mercurial may fetch
1869 When cloning from servers that support it, Mercurial may fetch
1870 pre-generated data from a server-advertised URL or inline from the
1870 pre-generated data from a server-advertised URL or inline from the
1871 same stream. When this is done, hooks operating on incoming changesets
1871 same stream. When this is done, hooks operating on incoming changesets
1872 and changegroups may fire more than once, once for each pre-generated
1872 and changegroups may fire more than once, once for each pre-generated
1873 bundle and as well as for any additional remaining data. In addition,
1873 bundle and as well as for any additional remaining data. In addition,
1874 if an error occurs, the repository may be rolled back to a partial
1874 if an error occurs, the repository may be rolled back to a partial
1875 clone. This behavior may change in future releases.
1875 clone. This behavior may change in future releases.
1876 See :hg:`help -e clonebundles` for more.
1876 See :hg:`help -e clonebundles` for more.
1877
1877
1878 Examples:
1878 Examples:
1879
1879
1880 - clone a remote repository to a new directory named hg/::
1880 - clone a remote repository to a new directory named hg/::
1881
1881
1882 hg clone https://www.mercurial-scm.org/repo/hg/
1882 hg clone https://www.mercurial-scm.org/repo/hg/
1883
1883
1884 - create a lightweight local clone::
1884 - create a lightweight local clone::
1885
1885
1886 hg clone project/ project-feature/
1886 hg clone project/ project-feature/
1887
1887
1888 - clone from an absolute path on an ssh server (note double-slash)::
1888 - clone from an absolute path on an ssh server (note double-slash)::
1889
1889
1890 hg clone ssh://user@server//home/projects/alpha/
1890 hg clone ssh://user@server//home/projects/alpha/
1891
1891
1892 - do a streaming clone while checking out a specified version::
1892 - do a streaming clone while checking out a specified version::
1893
1893
1894 hg clone --stream http://server/repo -u 1.5
1894 hg clone --stream http://server/repo -u 1.5
1895
1895
1896 - create a repository without changesets after a particular revision::
1896 - create a repository without changesets after a particular revision::
1897
1897
1898 hg clone -r 04e544 experimental/ good/
1898 hg clone -r 04e544 experimental/ good/
1899
1899
1900 - clone (and track) a particular named branch::
1900 - clone (and track) a particular named branch::
1901
1901
1902 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1902 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1903
1903
1904 See :hg:`help urls` for details on specifying URLs.
1904 See :hg:`help urls` for details on specifying URLs.
1905
1905
1906 Returns 0 on success.
1906 Returns 0 on success.
1907 """
1907 """
1908 opts = pycompat.byteskwargs(opts)
1908 opts = pycompat.byteskwargs(opts)
1909 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1909 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1910
1910
1911 # --include/--exclude can come from narrow or sparse.
1911 # --include/--exclude can come from narrow or sparse.
1912 includepats, excludepats = None, None
1912 includepats, excludepats = None, None
1913
1913
1914 # hg.clone() differentiates between None and an empty set. So make sure
1914 # hg.clone() differentiates between None and an empty set. So make sure
1915 # patterns are sets if narrow is requested without patterns.
1915 # patterns are sets if narrow is requested without patterns.
1916 if opts.get(b'narrow'):
1916 if opts.get(b'narrow'):
1917 includepats = set()
1917 includepats = set()
1918 excludepats = set()
1918 excludepats = set()
1919
1919
1920 if opts.get(b'include'):
1920 if opts.get(b'include'):
1921 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1921 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1922 if opts.get(b'exclude'):
1922 if opts.get(b'exclude'):
1923 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1923 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1924
1924
1925 r = hg.clone(
1925 r = hg.clone(
1926 ui,
1926 ui,
1927 opts,
1927 opts,
1928 source,
1928 source,
1929 dest,
1929 dest,
1930 pull=opts.get(b'pull'),
1930 pull=opts.get(b'pull'),
1931 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1931 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1932 revs=opts.get(b'rev'),
1932 revs=opts.get(b'rev'),
1933 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1933 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1934 branch=opts.get(b'branch'),
1934 branch=opts.get(b'branch'),
1935 shareopts=opts.get(b'shareopts'),
1935 shareopts=opts.get(b'shareopts'),
1936 storeincludepats=includepats,
1936 storeincludepats=includepats,
1937 storeexcludepats=excludepats,
1937 storeexcludepats=excludepats,
1938 depth=opts.get(b'depth') or None,
1938 depth=opts.get(b'depth') or None,
1939 )
1939 )
1940
1940
1941 return r is None
1941 return r is None
1942
1942
1943
1943
1944 @command(
1944 @command(
1945 b'commit|ci',
1945 b'commit|ci',
1946 [
1946 [
1947 (
1947 (
1948 b'A',
1948 b'A',
1949 b'addremove',
1949 b'addremove',
1950 None,
1950 None,
1951 _(b'mark new/missing files as added/removed before committing'),
1951 _(b'mark new/missing files as added/removed before committing'),
1952 ),
1952 ),
1953 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1953 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1954 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1954 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1955 (b's', b'secret', None, _(b'use the secret phase for committing')),
1955 (b's', b'secret', None, _(b'use the secret phase for committing')),
1956 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1956 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1957 (
1957 (
1958 b'',
1958 b'',
1959 b'force-close-branch',
1959 b'force-close-branch',
1960 None,
1960 None,
1961 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1961 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1962 ),
1962 ),
1963 (b'i', b'interactive', None, _(b'use interactive mode')),
1963 (b'i', b'interactive', None, _(b'use interactive mode')),
1964 ]
1964 ]
1965 + walkopts
1965 + walkopts
1966 + commitopts
1966 + commitopts
1967 + commitopts2
1967 + commitopts2
1968 + subrepoopts,
1968 + subrepoopts,
1969 _(b'[OPTION]... [FILE]...'),
1969 _(b'[OPTION]... [FILE]...'),
1970 helpcategory=command.CATEGORY_COMMITTING,
1970 helpcategory=command.CATEGORY_COMMITTING,
1971 helpbasic=True,
1971 helpbasic=True,
1972 inferrepo=True,
1972 inferrepo=True,
1973 )
1973 )
1974 def commit(ui, repo, *pats, **opts):
1974 def commit(ui, repo, *pats, **opts):
1975 """commit the specified files or all outstanding changes
1975 """commit the specified files or all outstanding changes
1976
1976
1977 Commit changes to the given files into the repository. Unlike a
1977 Commit changes to the given files into the repository. Unlike a
1978 centralized SCM, this operation is a local operation. See
1978 centralized SCM, this operation is a local operation. See
1979 :hg:`push` for a way to actively distribute your changes.
1979 :hg:`push` for a way to actively distribute your changes.
1980
1980
1981 If a list of files is omitted, all changes reported by :hg:`status`
1981 If a list of files is omitted, all changes reported by :hg:`status`
1982 will be committed.
1982 will be committed.
1983
1983
1984 If you are committing the result of a merge, do not provide any
1984 If you are committing the result of a merge, do not provide any
1985 filenames or -I/-X filters.
1985 filenames or -I/-X filters.
1986
1986
1987 If no commit message is specified, Mercurial starts your
1987 If no commit message is specified, Mercurial starts your
1988 configured editor where you can enter a message. In case your
1988 configured editor where you can enter a message. In case your
1989 commit fails, you will find a backup of your message in
1989 commit fails, you will find a backup of your message in
1990 ``.hg/last-message.txt``.
1990 ``.hg/last-message.txt``.
1991
1991
1992 The --close-branch flag can be used to mark the current branch
1992 The --close-branch flag can be used to mark the current branch
1993 head closed. When all heads of a branch are closed, the branch
1993 head closed. When all heads of a branch are closed, the branch
1994 will be considered closed and no longer listed.
1994 will be considered closed and no longer listed.
1995
1995
1996 The --amend flag can be used to amend the parent of the
1996 The --amend flag can be used to amend the parent of the
1997 working directory with a new commit that contains the changes
1997 working directory with a new commit that contains the changes
1998 in the parent in addition to those currently reported by :hg:`status`,
1998 in the parent in addition to those currently reported by :hg:`status`,
1999 if there are any. The old commit is stored in a backup bundle in
1999 if there are any. The old commit is stored in a backup bundle in
2000 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2000 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2001 on how to restore it).
2001 on how to restore it).
2002
2002
2003 Message, user and date are taken from the amended commit unless
2003 Message, user and date are taken from the amended commit unless
2004 specified. When a message isn't specified on the command line,
2004 specified. When a message isn't specified on the command line,
2005 the editor will open with the message of the amended commit.
2005 the editor will open with the message of the amended commit.
2006
2006
2007 It is not possible to amend public changesets (see :hg:`help phases`)
2007 It is not possible to amend public changesets (see :hg:`help phases`)
2008 or changesets that have children.
2008 or changesets that have children.
2009
2009
2010 See :hg:`help dates` for a list of formats valid for -d/--date.
2010 See :hg:`help dates` for a list of formats valid for -d/--date.
2011
2011
2012 Returns 0 on success, 1 if nothing changed.
2012 Returns 0 on success, 1 if nothing changed.
2013
2013
2014 .. container:: verbose
2014 .. container:: verbose
2015
2015
2016 Examples:
2016 Examples:
2017
2017
2018 - commit all files ending in .py::
2018 - commit all files ending in .py::
2019
2019
2020 hg commit --include "set:**.py"
2020 hg commit --include "set:**.py"
2021
2021
2022 - commit all non-binary files::
2022 - commit all non-binary files::
2023
2023
2024 hg commit --exclude "set:binary()"
2024 hg commit --exclude "set:binary()"
2025
2025
2026 - amend the current commit and set the date to now::
2026 - amend the current commit and set the date to now::
2027
2027
2028 hg commit --amend --date now
2028 hg commit --amend --date now
2029 """
2029 """
2030 with repo.wlock(), repo.lock():
2030 with repo.wlock(), repo.lock():
2031 return _docommit(ui, repo, *pats, **opts)
2031 return _docommit(ui, repo, *pats, **opts)
2032
2032
2033
2033
2034 def _docommit(ui, repo, *pats, **opts):
2034 def _docommit(ui, repo, *pats, **opts):
2035 if opts.get('interactive'):
2035 if opts.get('interactive'):
2036 opts.pop('interactive')
2036 opts.pop('interactive')
2037 ret = cmdutil.dorecord(
2037 ret = cmdutil.dorecord(
2038 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2038 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2039 )
2039 )
2040 # ret can be 0 (no changes to record) or the value returned by
2040 # ret can be 0 (no changes to record) or the value returned by
2041 # commit(), 1 if nothing changed or None on success.
2041 # commit(), 1 if nothing changed or None on success.
2042 return 1 if ret == 0 else ret
2042 return 1 if ret == 0 else ret
2043
2043
2044 opts = pycompat.byteskwargs(opts)
2044 opts = pycompat.byteskwargs(opts)
2045 if opts.get(b'subrepos'):
2045 if opts.get(b'subrepos'):
2046 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'amend'])
2046 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'amend'])
2047 # Let --subrepos on the command line override config setting.
2047 # Let --subrepos on the command line override config setting.
2048 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2048 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2049
2049
2050 cmdutil.checkunfinished(repo, commit=True)
2050 cmdutil.checkunfinished(repo, commit=True)
2051
2051
2052 branch = repo[None].branch()
2052 branch = repo[None].branch()
2053 bheads = repo.branchheads(branch)
2053 bheads = repo.branchheads(branch)
2054 tip = repo.changelog.tip()
2054 tip = repo.changelog.tip()
2055
2055
2056 extra = {}
2056 extra = {}
2057 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2057 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2058 extra[b'close'] = b'1'
2058 extra[b'close'] = b'1'
2059
2059
2060 if repo[b'.'].closesbranch():
2060 if repo[b'.'].closesbranch():
2061 raise error.InputError(
2061 raise error.InputError(
2062 _(b'current revision is already a branch closing head')
2062 _(b'current revision is already a branch closing head')
2063 )
2063 )
2064 elif not bheads:
2064 elif not bheads:
2065 raise error.InputError(
2065 raise error.InputError(
2066 _(b'branch "%s" has no heads to close') % branch
2066 _(b'branch "%s" has no heads to close') % branch
2067 )
2067 )
2068 elif (
2068 elif (
2069 branch == repo[b'.'].branch()
2069 branch == repo[b'.'].branch()
2070 and repo[b'.'].node() not in bheads
2070 and repo[b'.'].node() not in bheads
2071 and not opts.get(b'force_close_branch')
2071 and not opts.get(b'force_close_branch')
2072 ):
2072 ):
2073 hint = _(
2073 hint = _(
2074 b'use --force-close-branch to close branch from a non-head'
2074 b'use --force-close-branch to close branch from a non-head'
2075 b' changeset'
2075 b' changeset'
2076 )
2076 )
2077 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2077 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2078 elif opts.get(b'amend'):
2078 elif opts.get(b'amend'):
2079 if (
2079 if (
2080 repo[b'.'].p1().branch() != branch
2080 repo[b'.'].p1().branch() != branch
2081 and repo[b'.'].p2().branch() != branch
2081 and repo[b'.'].p2().branch() != branch
2082 ):
2082 ):
2083 raise error.InputError(_(b'can only close branch heads'))
2083 raise error.InputError(_(b'can only close branch heads'))
2084
2084
2085 if opts.get(b'amend'):
2085 if opts.get(b'amend'):
2086 if ui.configbool(b'ui', b'commitsubrepos'):
2086 if ui.configbool(b'ui', b'commitsubrepos'):
2087 raise error.InputError(
2087 raise error.InputError(
2088 _(b'cannot amend with ui.commitsubrepos enabled')
2088 _(b'cannot amend with ui.commitsubrepos enabled')
2089 )
2089 )
2090
2090
2091 old = repo[b'.']
2091 old = repo[b'.']
2092 rewriteutil.precheck(repo, [old.rev()], b'amend')
2092 rewriteutil.precheck(repo, [old.rev()], b'amend')
2093
2093
2094 # Currently histedit gets confused if an amend happens while histedit
2094 # Currently histedit gets confused if an amend happens while histedit
2095 # is in progress. Since we have a checkunfinished command, we are
2095 # is in progress. Since we have a checkunfinished command, we are
2096 # temporarily honoring it.
2096 # temporarily honoring it.
2097 #
2097 #
2098 # Note: eventually this guard will be removed. Please do not expect
2098 # Note: eventually this guard will be removed. Please do not expect
2099 # this behavior to remain.
2099 # this behavior to remain.
2100 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2100 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2101 cmdutil.checkunfinished(repo)
2101 cmdutil.checkunfinished(repo)
2102
2102
2103 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2103 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2104 if node == old.node():
2104 if node == old.node():
2105 ui.status(_(b"nothing changed\n"))
2105 ui.status(_(b"nothing changed\n"))
2106 return 1
2106 return 1
2107 else:
2107 else:
2108
2108
2109 def commitfunc(ui, repo, message, match, opts):
2109 def commitfunc(ui, repo, message, match, opts):
2110 overrides = {}
2110 overrides = {}
2111 if opts.get(b'secret'):
2111 if opts.get(b'secret'):
2112 overrides[(b'phases', b'new-commit')] = b'secret'
2112 overrides[(b'phases', b'new-commit')] = b'secret'
2113
2113
2114 baseui = repo.baseui
2114 baseui = repo.baseui
2115 with baseui.configoverride(overrides, b'commit'):
2115 with baseui.configoverride(overrides, b'commit'):
2116 with ui.configoverride(overrides, b'commit'):
2116 with ui.configoverride(overrides, b'commit'):
2117 editform = cmdutil.mergeeditform(
2117 editform = cmdutil.mergeeditform(
2118 repo[None], b'commit.normal'
2118 repo[None], b'commit.normal'
2119 )
2119 )
2120 editor = cmdutil.getcommiteditor(
2120 editor = cmdutil.getcommiteditor(
2121 editform=editform, **pycompat.strkwargs(opts)
2121 editform=editform, **pycompat.strkwargs(opts)
2122 )
2122 )
2123 return repo.commit(
2123 return repo.commit(
2124 message,
2124 message,
2125 opts.get(b'user'),
2125 opts.get(b'user'),
2126 opts.get(b'date'),
2126 opts.get(b'date'),
2127 match,
2127 match,
2128 editor=editor,
2128 editor=editor,
2129 extra=extra,
2129 extra=extra,
2130 )
2130 )
2131
2131
2132 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2132 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2133
2133
2134 if not node:
2134 if not node:
2135 stat = cmdutil.postcommitstatus(repo, pats, opts)
2135 stat = cmdutil.postcommitstatus(repo, pats, opts)
2136 if stat.deleted:
2136 if stat.deleted:
2137 ui.status(
2137 ui.status(
2138 _(
2138 _(
2139 b"nothing changed (%d missing files, see "
2139 b"nothing changed (%d missing files, see "
2140 b"'hg status')\n"
2140 b"'hg status')\n"
2141 )
2141 )
2142 % len(stat.deleted)
2142 % len(stat.deleted)
2143 )
2143 )
2144 else:
2144 else:
2145 ui.status(_(b"nothing changed\n"))
2145 ui.status(_(b"nothing changed\n"))
2146 return 1
2146 return 1
2147
2147
2148 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2148 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2149
2149
2150 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2150 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2151 status(
2151 status(
2152 ui,
2152 ui,
2153 repo,
2153 repo,
2154 modified=True,
2154 modified=True,
2155 added=True,
2155 added=True,
2156 removed=True,
2156 removed=True,
2157 deleted=True,
2157 deleted=True,
2158 unknown=True,
2158 unknown=True,
2159 subrepos=opts.get(b'subrepos'),
2159 subrepos=opts.get(b'subrepos'),
2160 )
2160 )
2161
2161
2162
2162
2163 @command(
2163 @command(
2164 b'config|showconfig|debugconfig',
2164 b'config|showconfig|debugconfig',
2165 [
2165 [
2166 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2166 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2167 (b'e', b'edit', None, _(b'edit user config')),
2167 (b'e', b'edit', None, _(b'edit user config')),
2168 (b'l', b'local', None, _(b'edit repository config')),
2168 (b'l', b'local', None, _(b'edit repository config')),
2169 (
2169 (
2170 b'',
2170 b'',
2171 b'shared',
2171 b'shared',
2172 None,
2172 None,
2173 _(b'edit shared source repository config (EXPERIMENTAL)'),
2173 _(b'edit shared source repository config (EXPERIMENTAL)'),
2174 ),
2174 ),
2175 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2175 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2176 (b'g', b'global', None, _(b'edit global config')),
2176 (b'g', b'global', None, _(b'edit global config')),
2177 ]
2177 ]
2178 + formatteropts,
2178 + formatteropts,
2179 _(b'[-u] [NAME]...'),
2179 _(b'[-u] [NAME]...'),
2180 helpcategory=command.CATEGORY_HELP,
2180 helpcategory=command.CATEGORY_HELP,
2181 optionalrepo=True,
2181 optionalrepo=True,
2182 intents={INTENT_READONLY},
2182 intents={INTENT_READONLY},
2183 )
2183 )
2184 def config(ui, repo, *values, **opts):
2184 def config(ui, repo, *values, **opts):
2185 """show combined config settings from all hgrc files
2185 """show combined config settings from all hgrc files
2186
2186
2187 With no arguments, print names and values of all config items.
2187 With no arguments, print names and values of all config items.
2188
2188
2189 With one argument of the form section.name, print just the value
2189 With one argument of the form section.name, print just the value
2190 of that config item.
2190 of that config item.
2191
2191
2192 With multiple arguments, print names and values of all config
2192 With multiple arguments, print names and values of all config
2193 items with matching section names or section.names.
2193 items with matching section names or section.names.
2194
2194
2195 With --edit, start an editor on the user-level config file. With
2195 With --edit, start an editor on the user-level config file. With
2196 --global, edit the system-wide config file. With --local, edit the
2196 --global, edit the system-wide config file. With --local, edit the
2197 repository-level config file.
2197 repository-level config file.
2198
2198
2199 With --debug, the source (filename and line number) is printed
2199 With --debug, the source (filename and line number) is printed
2200 for each config item.
2200 for each config item.
2201
2201
2202 See :hg:`help config` for more information about config files.
2202 See :hg:`help config` for more information about config files.
2203
2203
2204 .. container:: verbose
2204 .. container:: verbose
2205
2205
2206 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2206 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2207 This file is not shared across shares when in share-safe mode.
2207 This file is not shared across shares when in share-safe mode.
2208
2208
2209 Template:
2209 Template:
2210
2210
2211 The following keywords are supported. See also :hg:`help templates`.
2211 The following keywords are supported. See also :hg:`help templates`.
2212
2212
2213 :name: String. Config name.
2213 :name: String. Config name.
2214 :source: String. Filename and line number where the item is defined.
2214 :source: String. Filename and line number where the item is defined.
2215 :value: String. Config value.
2215 :value: String. Config value.
2216
2216
2217 The --shared flag can be used to edit the config file of shared source
2217 The --shared flag can be used to edit the config file of shared source
2218 repository. It only works when you have shared using the experimental
2218 repository. It only works when you have shared using the experimental
2219 share safe feature.
2219 share safe feature.
2220
2220
2221 Returns 0 on success, 1 if NAME does not exist.
2221 Returns 0 on success, 1 if NAME does not exist.
2222
2222
2223 """
2223 """
2224
2224
2225 opts = pycompat.byteskwargs(opts)
2225 opts = pycompat.byteskwargs(opts)
2226 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2226 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2227 if any(opts.get(o) for o in editopts):
2227 if any(opts.get(o) for o in editopts):
2228 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2228 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2229 if opts.get(b'local'):
2229 if opts.get(b'local'):
2230 if not repo:
2230 if not repo:
2231 raise error.InputError(
2231 raise error.InputError(
2232 _(b"can't use --local outside a repository")
2232 _(b"can't use --local outside a repository")
2233 )
2233 )
2234 paths = [repo.vfs.join(b'hgrc')]
2234 paths = [repo.vfs.join(b'hgrc')]
2235 elif opts.get(b'global'):
2235 elif opts.get(b'global'):
2236 paths = rcutil.systemrcpath()
2236 paths = rcutil.systemrcpath()
2237 elif opts.get(b'shared'):
2237 elif opts.get(b'shared'):
2238 if not repo.shared():
2238 if not repo.shared():
2239 raise error.InputError(
2239 raise error.InputError(
2240 _(b"repository is not shared; can't use --shared")
2240 _(b"repository is not shared; can't use --shared")
2241 )
2241 )
2242 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2242 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2243 raise error.InputError(
2243 raise error.InputError(
2244 _(
2244 _(
2245 b"share safe feature not enabled; "
2245 b"share safe feature not enabled; "
2246 b"unable to edit shared source repository config"
2246 b"unable to edit shared source repository config"
2247 )
2247 )
2248 )
2248 )
2249 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2249 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2250 elif opts.get(b'non_shared'):
2250 elif opts.get(b'non_shared'):
2251 paths = [repo.vfs.join(b'hgrc-not-shared')]
2251 paths = [repo.vfs.join(b'hgrc-not-shared')]
2252 else:
2252 else:
2253 paths = rcutil.userrcpath()
2253 paths = rcutil.userrcpath()
2254
2254
2255 for f in paths:
2255 for f in paths:
2256 if os.path.exists(f):
2256 if os.path.exists(f):
2257 break
2257 break
2258 else:
2258 else:
2259 if opts.get(b'global'):
2259 if opts.get(b'global'):
2260 samplehgrc = uimod.samplehgrcs[b'global']
2260 samplehgrc = uimod.samplehgrcs[b'global']
2261 elif opts.get(b'local'):
2261 elif opts.get(b'local'):
2262 samplehgrc = uimod.samplehgrcs[b'local']
2262 samplehgrc = uimod.samplehgrcs[b'local']
2263 else:
2263 else:
2264 samplehgrc = uimod.samplehgrcs[b'user']
2264 samplehgrc = uimod.samplehgrcs[b'user']
2265
2265
2266 f = paths[0]
2266 f = paths[0]
2267 fp = open(f, b"wb")
2267 fp = open(f, b"wb")
2268 fp.write(util.tonativeeol(samplehgrc))
2268 fp.write(util.tonativeeol(samplehgrc))
2269 fp.close()
2269 fp.close()
2270
2270
2271 editor = ui.geteditor()
2271 editor = ui.geteditor()
2272 ui.system(
2272 ui.system(
2273 b"%s \"%s\"" % (editor, f),
2273 b"%s \"%s\"" % (editor, f),
2274 onerr=error.InputError,
2274 onerr=error.InputError,
2275 errprefix=_(b"edit failed"),
2275 errprefix=_(b"edit failed"),
2276 blockedtag=b'config_edit',
2276 blockedtag=b'config_edit',
2277 )
2277 )
2278 return
2278 return
2279 ui.pager(b'config')
2279 ui.pager(b'config')
2280 fm = ui.formatter(b'config', opts)
2280 fm = ui.formatter(b'config', opts)
2281 for t, f in rcutil.rccomponents():
2281 for t, f in rcutil.rccomponents():
2282 if t == b'path':
2282 if t == b'path':
2283 ui.debug(b'read config from: %s\n' % f)
2283 ui.debug(b'read config from: %s\n' % f)
2284 elif t == b'resource':
2284 elif t == b'resource':
2285 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2285 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2286 elif t == b'items':
2286 elif t == b'items':
2287 # Don't print anything for 'items'.
2287 # Don't print anything for 'items'.
2288 pass
2288 pass
2289 else:
2289 else:
2290 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2290 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2291 untrusted = bool(opts.get(b'untrusted'))
2291 untrusted = bool(opts.get(b'untrusted'))
2292
2292
2293 selsections = selentries = []
2293 selsections = selentries = []
2294 if values:
2294 if values:
2295 selsections = [v for v in values if b'.' not in v]
2295 selsections = [v for v in values if b'.' not in v]
2296 selentries = [v for v in values if b'.' in v]
2296 selentries = [v for v in values if b'.' in v]
2297 uniquesel = len(selentries) == 1 and not selsections
2297 uniquesel = len(selentries) == 1 and not selsections
2298 selsections = set(selsections)
2298 selsections = set(selsections)
2299 selentries = set(selentries)
2299 selentries = set(selentries)
2300
2300
2301 matched = False
2301 matched = False
2302 for section, name, value in ui.walkconfig(untrusted=untrusted):
2302 for section, name, value in ui.walkconfig(untrusted=untrusted):
2303 source = ui.configsource(section, name, untrusted)
2303 source = ui.configsource(section, name, untrusted)
2304 value = pycompat.bytestr(value)
2304 value = pycompat.bytestr(value)
2305 defaultvalue = ui.configdefault(section, name)
2305 defaultvalue = ui.configdefault(section, name)
2306 if fm.isplain():
2306 if fm.isplain():
2307 source = source or b'none'
2307 source = source or b'none'
2308 value = value.replace(b'\n', b'\\n')
2308 value = value.replace(b'\n', b'\\n')
2309 entryname = section + b'.' + name
2309 entryname = section + b'.' + name
2310 if values and not (section in selsections or entryname in selentries):
2310 if values and not (section in selsections or entryname in selentries):
2311 continue
2311 continue
2312 fm.startitem()
2312 fm.startitem()
2313 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2313 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2314 if uniquesel:
2314 if uniquesel:
2315 fm.data(name=entryname)
2315 fm.data(name=entryname)
2316 fm.write(b'value', b'%s\n', value)
2316 fm.write(b'value', b'%s\n', value)
2317 else:
2317 else:
2318 fm.write(b'name value', b'%s=%s\n', entryname, value)
2318 fm.write(b'name value', b'%s=%s\n', entryname, value)
2319 if formatter.isprintable(defaultvalue):
2319 if formatter.isprintable(defaultvalue):
2320 fm.data(defaultvalue=defaultvalue)
2320 fm.data(defaultvalue=defaultvalue)
2321 elif isinstance(defaultvalue, list) and all(
2321 elif isinstance(defaultvalue, list) and all(
2322 formatter.isprintable(e) for e in defaultvalue
2322 formatter.isprintable(e) for e in defaultvalue
2323 ):
2323 ):
2324 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2324 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2325 # TODO: no idea how to process unsupported defaultvalue types
2325 # TODO: no idea how to process unsupported defaultvalue types
2326 matched = True
2326 matched = True
2327 fm.end()
2327 fm.end()
2328 if matched:
2328 if matched:
2329 return 0
2329 return 0
2330 return 1
2330 return 1
2331
2331
2332
2332
2333 @command(
2333 @command(
2334 b'continue',
2334 b'continue',
2335 dryrunopts,
2335 dryrunopts,
2336 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2336 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2337 helpbasic=True,
2337 helpbasic=True,
2338 )
2338 )
2339 def continuecmd(ui, repo, **opts):
2339 def continuecmd(ui, repo, **opts):
2340 """resumes an interrupted operation (EXPERIMENTAL)
2340 """resumes an interrupted operation (EXPERIMENTAL)
2341
2341
2342 Finishes a multistep operation like graft, histedit, rebase, merge,
2342 Finishes a multistep operation like graft, histedit, rebase, merge,
2343 and unshelve if they are in an interrupted state.
2343 and unshelve if they are in an interrupted state.
2344
2344
2345 use --dry-run/-n to dry run the command.
2345 use --dry-run/-n to dry run the command.
2346 """
2346 """
2347 dryrun = opts.get('dry_run')
2347 dryrun = opts.get('dry_run')
2348 contstate = cmdutil.getunfinishedstate(repo)
2348 contstate = cmdutil.getunfinishedstate(repo)
2349 if not contstate:
2349 if not contstate:
2350 raise error.StateError(_(b'no operation in progress'))
2350 raise error.StateError(_(b'no operation in progress'))
2351 if not contstate.continuefunc:
2351 if not contstate.continuefunc:
2352 raise error.StateError(
2352 raise error.StateError(
2353 (
2353 (
2354 _(b"%s in progress but does not support 'hg continue'")
2354 _(b"%s in progress but does not support 'hg continue'")
2355 % (contstate._opname)
2355 % (contstate._opname)
2356 ),
2356 ),
2357 hint=contstate.continuemsg(),
2357 hint=contstate.continuemsg(),
2358 )
2358 )
2359 if dryrun:
2359 if dryrun:
2360 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2360 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2361 return
2361 return
2362 return contstate.continuefunc(ui, repo)
2362 return contstate.continuefunc(ui, repo)
2363
2363
2364
2364
2365 @command(
2365 @command(
2366 b'copy|cp',
2366 b'copy|cp',
2367 [
2367 [
2368 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2368 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2369 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2369 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2370 (
2370 (
2371 b'',
2371 b'',
2372 b'at-rev',
2372 b'at-rev',
2373 b'',
2373 b'',
2374 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2374 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2375 _(b'REV'),
2375 _(b'REV'),
2376 ),
2376 ),
2377 (
2377 (
2378 b'f',
2378 b'f',
2379 b'force',
2379 b'force',
2380 None,
2380 None,
2381 _(b'forcibly copy over an existing managed file'),
2381 _(b'forcibly copy over an existing managed file'),
2382 ),
2382 ),
2383 ]
2383 ]
2384 + walkopts
2384 + walkopts
2385 + dryrunopts,
2385 + dryrunopts,
2386 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2386 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2387 helpcategory=command.CATEGORY_FILE_CONTENTS,
2387 helpcategory=command.CATEGORY_FILE_CONTENTS,
2388 )
2388 )
2389 def copy(ui, repo, *pats, **opts):
2389 def copy(ui, repo, *pats, **opts):
2390 """mark files as copied for the next commit
2390 """mark files as copied for the next commit
2391
2391
2392 Mark dest as having copies of source files. If dest is a
2392 Mark dest as having copies of source files. If dest is a
2393 directory, copies are put in that directory. If dest is a file,
2393 directory, copies are put in that directory. If dest is a file,
2394 the source must be a single file.
2394 the source must be a single file.
2395
2395
2396 By default, this command copies the contents of files as they
2396 By default, this command copies the contents of files as they
2397 exist in the working directory. If invoked with -A/--after, the
2397 exist in the working directory. If invoked with -A/--after, the
2398 operation is recorded, but no copying is performed.
2398 operation is recorded, but no copying is performed.
2399
2399
2400 To undo marking a destination file as copied, use --forget. With that
2400 To undo marking a destination file as copied, use --forget. With that
2401 option, all given (positional) arguments are unmarked as copies. The
2401 option, all given (positional) arguments are unmarked as copies. The
2402 destination file(s) will be left in place (still tracked).
2402 destination file(s) will be left in place (still tracked).
2403
2403
2404 This command takes effect with the next commit by default.
2404 This command takes effect with the next commit by default.
2405
2405
2406 Returns 0 on success, 1 if errors are encountered.
2406 Returns 0 on success, 1 if errors are encountered.
2407 """
2407 """
2408 opts = pycompat.byteskwargs(opts)
2408 opts = pycompat.byteskwargs(opts)
2409 with repo.wlock():
2409 with repo.wlock():
2410 return cmdutil.copy(ui, repo, pats, opts)
2410 return cmdutil.copy(ui, repo, pats, opts)
2411
2411
2412
2412
2413 @command(
2413 @command(
2414 b'debugcommands',
2414 b'debugcommands',
2415 [],
2415 [],
2416 _(b'[COMMAND]'),
2416 _(b'[COMMAND]'),
2417 helpcategory=command.CATEGORY_HELP,
2417 helpcategory=command.CATEGORY_HELP,
2418 norepo=True,
2418 norepo=True,
2419 )
2419 )
2420 def debugcommands(ui, cmd=b'', *args):
2420 def debugcommands(ui, cmd=b'', *args):
2421 """list all available commands and options"""
2421 """list all available commands and options"""
2422 for cmd, vals in sorted(pycompat.iteritems(table)):
2422 for cmd, vals in sorted(pycompat.iteritems(table)):
2423 cmd = cmd.split(b'|')[0]
2423 cmd = cmd.split(b'|')[0]
2424 opts = b', '.join([i[1] for i in vals[1]])
2424 opts = b', '.join([i[1] for i in vals[1]])
2425 ui.write(b'%s: %s\n' % (cmd, opts))
2425 ui.write(b'%s: %s\n' % (cmd, opts))
2426
2426
2427
2427
2428 @command(
2428 @command(
2429 b'debugcomplete',
2429 b'debugcomplete',
2430 [(b'o', b'options', None, _(b'show the command options'))],
2430 [(b'o', b'options', None, _(b'show the command options'))],
2431 _(b'[-o] CMD'),
2431 _(b'[-o] CMD'),
2432 helpcategory=command.CATEGORY_HELP,
2432 helpcategory=command.CATEGORY_HELP,
2433 norepo=True,
2433 norepo=True,
2434 )
2434 )
2435 def debugcomplete(ui, cmd=b'', **opts):
2435 def debugcomplete(ui, cmd=b'', **opts):
2436 """returns the completion list associated with the given command"""
2436 """returns the completion list associated with the given command"""
2437
2437
2438 if opts.get('options'):
2438 if opts.get('options'):
2439 options = []
2439 options = []
2440 otables = [globalopts]
2440 otables = [globalopts]
2441 if cmd:
2441 if cmd:
2442 aliases, entry = cmdutil.findcmd(cmd, table, False)
2442 aliases, entry = cmdutil.findcmd(cmd, table, False)
2443 otables.append(entry[1])
2443 otables.append(entry[1])
2444 for t in otables:
2444 for t in otables:
2445 for o in t:
2445 for o in t:
2446 if b"(DEPRECATED)" in o[3]:
2446 if b"(DEPRECATED)" in o[3]:
2447 continue
2447 continue
2448 if o[0]:
2448 if o[0]:
2449 options.append(b'-%s' % o[0])
2449 options.append(b'-%s' % o[0])
2450 options.append(b'--%s' % o[1])
2450 options.append(b'--%s' % o[1])
2451 ui.write(b"%s\n" % b"\n".join(options))
2451 ui.write(b"%s\n" % b"\n".join(options))
2452 return
2452 return
2453
2453
2454 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2454 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2455 if ui.verbose:
2455 if ui.verbose:
2456 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2456 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2457 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2457 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2458
2458
2459
2459
2460 @command(
2460 @command(
2461 b'diff',
2461 b'diff',
2462 [
2462 [
2463 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2463 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2464 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2464 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2465 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2465 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2466 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2466 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2467 ]
2467 ]
2468 + diffopts
2468 + diffopts
2469 + diffopts2
2469 + diffopts2
2470 + walkopts
2470 + walkopts
2471 + subrepoopts,
2471 + subrepoopts,
2472 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2472 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2473 helpcategory=command.CATEGORY_FILE_CONTENTS,
2473 helpcategory=command.CATEGORY_FILE_CONTENTS,
2474 helpbasic=True,
2474 helpbasic=True,
2475 inferrepo=True,
2475 inferrepo=True,
2476 intents={INTENT_READONLY},
2476 intents={INTENT_READONLY},
2477 )
2477 )
2478 def diff(ui, repo, *pats, **opts):
2478 def diff(ui, repo, *pats, **opts):
2479 """diff repository (or selected files)
2479 """diff repository (or selected files)
2480
2480
2481 Show differences between revisions for the specified files.
2481 Show differences between revisions for the specified files.
2482
2482
2483 Differences between files are shown using the unified diff format.
2483 Differences between files are shown using the unified diff format.
2484
2484
2485 .. note::
2485 .. note::
2486
2486
2487 :hg:`diff` may generate unexpected results for merges, as it will
2487 :hg:`diff` may generate unexpected results for merges, as it will
2488 default to comparing against the working directory's first
2488 default to comparing against the working directory's first
2489 parent changeset if no revisions are specified.
2489 parent changeset if no revisions are specified.
2490
2490
2491 By default, the working directory files are compared to its first parent. To
2491 By default, the working directory files are compared to its first parent. To
2492 see the differences from another revision, use --from. To see the difference
2492 see the differences from another revision, use --from. To see the difference
2493 to another revision, use --to. For example, :hg:`diff --from .^` will show
2493 to another revision, use --to. For example, :hg:`diff --from .^` will show
2494 the differences from the working copy's grandparent to the working copy,
2494 the differences from the working copy's grandparent to the working copy,
2495 :hg:`diff --to .` will show the diff from the working copy to its parent
2495 :hg:`diff --to .` will show the diff from the working copy to its parent
2496 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2496 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2497 show the diff between those two revisions.
2497 show the diff between those two revisions.
2498
2498
2499 Alternatively you can specify -c/--change with a revision to see the changes
2499 Alternatively you can specify -c/--change with a revision to see the changes
2500 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2500 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2501 equivalent to :hg:`diff --from 42^ --to 42`)
2501 equivalent to :hg:`diff --from 42^ --to 42`)
2502
2502
2503 Without the -a/--text option, diff will avoid generating diffs of
2503 Without the -a/--text option, diff will avoid generating diffs of
2504 files it detects as binary. With -a, diff will generate a diff
2504 files it detects as binary. With -a, diff will generate a diff
2505 anyway, probably with undesirable results.
2505 anyway, probably with undesirable results.
2506
2506
2507 Use the -g/--git option to generate diffs in the git extended diff
2507 Use the -g/--git option to generate diffs in the git extended diff
2508 format. For more information, read :hg:`help diffs`.
2508 format. For more information, read :hg:`help diffs`.
2509
2509
2510 .. container:: verbose
2510 .. container:: verbose
2511
2511
2512 Examples:
2512 Examples:
2513
2513
2514 - compare a file in the current working directory to its parent::
2514 - compare a file in the current working directory to its parent::
2515
2515
2516 hg diff foo.c
2516 hg diff foo.c
2517
2517
2518 - compare two historical versions of a directory, with rename info::
2518 - compare two historical versions of a directory, with rename info::
2519
2519
2520 hg diff --git --from 1.0 --to 1.2 lib/
2520 hg diff --git --from 1.0 --to 1.2 lib/
2521
2521
2522 - get change stats relative to the last change on some date::
2522 - get change stats relative to the last change on some date::
2523
2523
2524 hg diff --stat --from "date('may 2')"
2524 hg diff --stat --from "date('may 2')"
2525
2525
2526 - diff all newly-added files that contain a keyword::
2526 - diff all newly-added files that contain a keyword::
2527
2527
2528 hg diff "set:added() and grep(GNU)"
2528 hg diff "set:added() and grep(GNU)"
2529
2529
2530 - compare a revision and its parents::
2530 - compare a revision and its parents::
2531
2531
2532 hg diff -c 9353 # compare against first parent
2532 hg diff -c 9353 # compare against first parent
2533 hg diff --from 9353^ --to 9353 # same using revset syntax
2533 hg diff --from 9353^ --to 9353 # same using revset syntax
2534 hg diff --from 9353^2 --to 9353 # compare against the second parent
2534 hg diff --from 9353^2 --to 9353 # compare against the second parent
2535
2535
2536 Returns 0 on success.
2536 Returns 0 on success.
2537 """
2537 """
2538
2538
2539 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2539 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2540 opts = pycompat.byteskwargs(opts)
2540 opts = pycompat.byteskwargs(opts)
2541 revs = opts.get(b'rev')
2541 revs = opts.get(b'rev')
2542 change = opts.get(b'change')
2542 change = opts.get(b'change')
2543 from_rev = opts.get(b'from')
2543 from_rev = opts.get(b'from')
2544 to_rev = opts.get(b'to')
2544 to_rev = opts.get(b'to')
2545 stat = opts.get(b'stat')
2545 stat = opts.get(b'stat')
2546 reverse = opts.get(b'reverse')
2546 reverse = opts.get(b'reverse')
2547
2547
2548 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2548 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2549 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2549 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2550 if change:
2550 if change:
2551 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2551 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2552 ctx2 = scmutil.revsingle(repo, change, None)
2552 ctx2 = scmutil.revsingle(repo, change, None)
2553 ctx1 = logcmdutil.diff_parent(ctx2)
2553 ctx1 = logcmdutil.diff_parent(ctx2)
2554 elif from_rev or to_rev:
2554 elif from_rev or to_rev:
2555 repo = scmutil.unhidehashlikerevs(
2555 repo = scmutil.unhidehashlikerevs(
2556 repo, [from_rev] + [to_rev], b'nowarn'
2556 repo, [from_rev] + [to_rev], b'nowarn'
2557 )
2557 )
2558 ctx1 = scmutil.revsingle(repo, from_rev, None)
2558 ctx1 = scmutil.revsingle(repo, from_rev, None)
2559 ctx2 = scmutil.revsingle(repo, to_rev, None)
2559 ctx2 = scmutil.revsingle(repo, to_rev, None)
2560 else:
2560 else:
2561 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2561 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2562 ctx1, ctx2 = scmutil.revpair(repo, revs)
2562 ctx1, ctx2 = scmutil.revpair(repo, revs)
2563
2563
2564 if reverse:
2564 if reverse:
2565 ctxleft = ctx2
2565 ctxleft = ctx2
2566 ctxright = ctx1
2566 ctxright = ctx1
2567 else:
2567 else:
2568 ctxleft = ctx1
2568 ctxleft = ctx1
2569 ctxright = ctx2
2569 ctxright = ctx2
2570
2570
2571 diffopts = patch.diffallopts(ui, opts)
2571 diffopts = patch.diffallopts(ui, opts)
2572 m = scmutil.match(ctx2, pats, opts)
2572 m = scmutil.match(ctx2, pats, opts)
2573 m = repo.narrowmatch(m)
2573 m = repo.narrowmatch(m)
2574 ui.pager(b'diff')
2574 ui.pager(b'diff')
2575 logcmdutil.diffordiffstat(
2575 logcmdutil.diffordiffstat(
2576 ui,
2576 ui,
2577 repo,
2577 repo,
2578 diffopts,
2578 diffopts,
2579 ctxleft,
2579 ctxleft,
2580 ctxright,
2580 ctxright,
2581 m,
2581 m,
2582 stat=stat,
2582 stat=stat,
2583 listsubrepos=opts.get(b'subrepos'),
2583 listsubrepos=opts.get(b'subrepos'),
2584 root=opts.get(b'root'),
2584 root=opts.get(b'root'),
2585 )
2585 )
2586
2586
2587
2587
2588 @command(
2588 @command(
2589 b'export',
2589 b'export',
2590 [
2590 [
2591 (
2591 (
2592 b'B',
2592 b'B',
2593 b'bookmark',
2593 b'bookmark',
2594 b'',
2594 b'',
2595 _(b'export changes only reachable by given bookmark'),
2595 _(b'export changes only reachable by given bookmark'),
2596 _(b'BOOKMARK'),
2596 _(b'BOOKMARK'),
2597 ),
2597 ),
2598 (
2598 (
2599 b'o',
2599 b'o',
2600 b'output',
2600 b'output',
2601 b'',
2601 b'',
2602 _(b'print output to file with formatted name'),
2602 _(b'print output to file with formatted name'),
2603 _(b'FORMAT'),
2603 _(b'FORMAT'),
2604 ),
2604 ),
2605 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2605 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2606 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2606 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2607 ]
2607 ]
2608 + diffopts
2608 + diffopts
2609 + formatteropts,
2609 + formatteropts,
2610 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2610 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2611 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2611 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2612 helpbasic=True,
2612 helpbasic=True,
2613 intents={INTENT_READONLY},
2613 intents={INTENT_READONLY},
2614 )
2614 )
2615 def export(ui, repo, *changesets, **opts):
2615 def export(ui, repo, *changesets, **opts):
2616 """dump the header and diffs for one or more changesets
2616 """dump the header and diffs for one or more changesets
2617
2617
2618 Print the changeset header and diffs for one or more revisions.
2618 Print the changeset header and diffs for one or more revisions.
2619 If no revision is given, the parent of the working directory is used.
2619 If no revision is given, the parent of the working directory is used.
2620
2620
2621 The information shown in the changeset header is: author, date,
2621 The information shown in the changeset header is: author, date,
2622 branch name (if non-default), changeset hash, parent(s) and commit
2622 branch name (if non-default), changeset hash, parent(s) and commit
2623 comment.
2623 comment.
2624
2624
2625 .. note::
2625 .. note::
2626
2626
2627 :hg:`export` may generate unexpected diff output for merge
2627 :hg:`export` may generate unexpected diff output for merge
2628 changesets, as it will compare the merge changeset against its
2628 changesets, as it will compare the merge changeset against its
2629 first parent only.
2629 first parent only.
2630
2630
2631 Output may be to a file, in which case the name of the file is
2631 Output may be to a file, in which case the name of the file is
2632 given using a template string. See :hg:`help templates`. In addition
2632 given using a template string. See :hg:`help templates`. In addition
2633 to the common template keywords, the following formatting rules are
2633 to the common template keywords, the following formatting rules are
2634 supported:
2634 supported:
2635
2635
2636 :``%%``: literal "%" character
2636 :``%%``: literal "%" character
2637 :``%H``: changeset hash (40 hexadecimal digits)
2637 :``%H``: changeset hash (40 hexadecimal digits)
2638 :``%N``: number of patches being generated
2638 :``%N``: number of patches being generated
2639 :``%R``: changeset revision number
2639 :``%R``: changeset revision number
2640 :``%b``: basename of the exporting repository
2640 :``%b``: basename of the exporting repository
2641 :``%h``: short-form changeset hash (12 hexadecimal digits)
2641 :``%h``: short-form changeset hash (12 hexadecimal digits)
2642 :``%m``: first line of the commit message (only alphanumeric characters)
2642 :``%m``: first line of the commit message (only alphanumeric characters)
2643 :``%n``: zero-padded sequence number, starting at 1
2643 :``%n``: zero-padded sequence number, starting at 1
2644 :``%r``: zero-padded changeset revision number
2644 :``%r``: zero-padded changeset revision number
2645 :``\\``: literal "\\" character
2645 :``\\``: literal "\\" character
2646
2646
2647 Without the -a/--text option, export will avoid generating diffs
2647 Without the -a/--text option, export will avoid generating diffs
2648 of files it detects as binary. With -a, export will generate a
2648 of files it detects as binary. With -a, export will generate a
2649 diff anyway, probably with undesirable results.
2649 diff anyway, probably with undesirable results.
2650
2650
2651 With -B/--bookmark changesets reachable by the given bookmark are
2651 With -B/--bookmark changesets reachable by the given bookmark are
2652 selected.
2652 selected.
2653
2653
2654 Use the -g/--git option to generate diffs in the git extended diff
2654 Use the -g/--git option to generate diffs in the git extended diff
2655 format. See :hg:`help diffs` for more information.
2655 format. See :hg:`help diffs` for more information.
2656
2656
2657 With the --switch-parent option, the diff will be against the
2657 With the --switch-parent option, the diff will be against the
2658 second parent. It can be useful to review a merge.
2658 second parent. It can be useful to review a merge.
2659
2659
2660 .. container:: verbose
2660 .. container:: verbose
2661
2661
2662 Template:
2662 Template:
2663
2663
2664 The following keywords are supported in addition to the common template
2664 The following keywords are supported in addition to the common template
2665 keywords and functions. See also :hg:`help templates`.
2665 keywords and functions. See also :hg:`help templates`.
2666
2666
2667 :diff: String. Diff content.
2667 :diff: String. Diff content.
2668 :parents: List of strings. Parent nodes of the changeset.
2668 :parents: List of strings. Parent nodes of the changeset.
2669
2669
2670 Examples:
2670 Examples:
2671
2671
2672 - use export and import to transplant a bugfix to the current
2672 - use export and import to transplant a bugfix to the current
2673 branch::
2673 branch::
2674
2674
2675 hg export -r 9353 | hg import -
2675 hg export -r 9353 | hg import -
2676
2676
2677 - export all the changesets between two revisions to a file with
2677 - export all the changesets between two revisions to a file with
2678 rename information::
2678 rename information::
2679
2679
2680 hg export --git -r 123:150 > changes.txt
2680 hg export --git -r 123:150 > changes.txt
2681
2681
2682 - split outgoing changes into a series of patches with
2682 - split outgoing changes into a series of patches with
2683 descriptive names::
2683 descriptive names::
2684
2684
2685 hg export -r "outgoing()" -o "%n-%m.patch"
2685 hg export -r "outgoing()" -o "%n-%m.patch"
2686
2686
2687 Returns 0 on success.
2687 Returns 0 on success.
2688 """
2688 """
2689 opts = pycompat.byteskwargs(opts)
2689 opts = pycompat.byteskwargs(opts)
2690 bookmark = opts.get(b'bookmark')
2690 bookmark = opts.get(b'bookmark')
2691 changesets += tuple(opts.get(b'rev', []))
2691 changesets += tuple(opts.get(b'rev', []))
2692
2692
2693 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2693 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2694
2694
2695 if bookmark:
2695 if bookmark:
2696 if bookmark not in repo._bookmarks:
2696 if bookmark not in repo._bookmarks:
2697 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2697 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2698
2698
2699 revs = scmutil.bookmarkrevs(repo, bookmark)
2699 revs = scmutil.bookmarkrevs(repo, bookmark)
2700 else:
2700 else:
2701 if not changesets:
2701 if not changesets:
2702 changesets = [b'.']
2702 changesets = [b'.']
2703
2703
2704 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2704 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2705 revs = scmutil.revrange(repo, changesets)
2705 revs = scmutil.revrange(repo, changesets)
2706
2706
2707 if not revs:
2707 if not revs:
2708 raise error.InputError(_(b"export requires at least one changeset"))
2708 raise error.InputError(_(b"export requires at least one changeset"))
2709 if len(revs) > 1:
2709 if len(revs) > 1:
2710 ui.note(_(b'exporting patches:\n'))
2710 ui.note(_(b'exporting patches:\n'))
2711 else:
2711 else:
2712 ui.note(_(b'exporting patch:\n'))
2712 ui.note(_(b'exporting patch:\n'))
2713
2713
2714 fntemplate = opts.get(b'output')
2714 fntemplate = opts.get(b'output')
2715 if cmdutil.isstdiofilename(fntemplate):
2715 if cmdutil.isstdiofilename(fntemplate):
2716 fntemplate = b''
2716 fntemplate = b''
2717
2717
2718 if fntemplate:
2718 if fntemplate:
2719 fm = formatter.nullformatter(ui, b'export', opts)
2719 fm = formatter.nullformatter(ui, b'export', opts)
2720 else:
2720 else:
2721 ui.pager(b'export')
2721 ui.pager(b'export')
2722 fm = ui.formatter(b'export', opts)
2722 fm = ui.formatter(b'export', opts)
2723 with fm:
2723 with fm:
2724 cmdutil.export(
2724 cmdutil.export(
2725 repo,
2725 repo,
2726 revs,
2726 revs,
2727 fm,
2727 fm,
2728 fntemplate=fntemplate,
2728 fntemplate=fntemplate,
2729 switch_parent=opts.get(b'switch_parent'),
2729 switch_parent=opts.get(b'switch_parent'),
2730 opts=patch.diffallopts(ui, opts),
2730 opts=patch.diffallopts(ui, opts),
2731 )
2731 )
2732
2732
2733
2733
2734 @command(
2734 @command(
2735 b'files',
2735 b'files',
2736 [
2736 [
2737 (
2737 (
2738 b'r',
2738 b'r',
2739 b'rev',
2739 b'rev',
2740 b'',
2740 b'',
2741 _(b'search the repository as it is in REV'),
2741 _(b'search the repository as it is in REV'),
2742 _(b'REV'),
2742 _(b'REV'),
2743 ),
2743 ),
2744 (
2744 (
2745 b'0',
2745 b'0',
2746 b'print0',
2746 b'print0',
2747 None,
2747 None,
2748 _(b'end filenames with NUL, for use with xargs'),
2748 _(b'end filenames with NUL, for use with xargs'),
2749 ),
2749 ),
2750 ]
2750 ]
2751 + walkopts
2751 + walkopts
2752 + formatteropts
2752 + formatteropts
2753 + subrepoopts,
2753 + subrepoopts,
2754 _(b'[OPTION]... [FILE]...'),
2754 _(b'[OPTION]... [FILE]...'),
2755 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2755 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2756 intents={INTENT_READONLY},
2756 intents={INTENT_READONLY},
2757 )
2757 )
2758 def files(ui, repo, *pats, **opts):
2758 def files(ui, repo, *pats, **opts):
2759 """list tracked files
2759 """list tracked files
2760
2760
2761 Print files under Mercurial control in the working directory or
2761 Print files under Mercurial control in the working directory or
2762 specified revision for given files (excluding removed files).
2762 specified revision for given files (excluding removed files).
2763 Files can be specified as filenames or filesets.
2763 Files can be specified as filenames or filesets.
2764
2764
2765 If no files are given to match, this command prints the names
2765 If no files are given to match, this command prints the names
2766 of all files under Mercurial control.
2766 of all files under Mercurial control.
2767
2767
2768 .. container:: verbose
2768 .. container:: verbose
2769
2769
2770 Template:
2770 Template:
2771
2771
2772 The following keywords are supported in addition to the common template
2772 The following keywords are supported in addition to the common template
2773 keywords and functions. See also :hg:`help templates`.
2773 keywords and functions. See also :hg:`help templates`.
2774
2774
2775 :flags: String. Character denoting file's symlink and executable bits.
2775 :flags: String. Character denoting file's symlink and executable bits.
2776 :path: String. Repository-absolute path of the file.
2776 :path: String. Repository-absolute path of the file.
2777 :size: Integer. Size of the file in bytes.
2777 :size: Integer. Size of the file in bytes.
2778
2778
2779 Examples:
2779 Examples:
2780
2780
2781 - list all files under the current directory::
2781 - list all files under the current directory::
2782
2782
2783 hg files .
2783 hg files .
2784
2784
2785 - shows sizes and flags for current revision::
2785 - shows sizes and flags for current revision::
2786
2786
2787 hg files -vr .
2787 hg files -vr .
2788
2788
2789 - list all files named README::
2789 - list all files named README::
2790
2790
2791 hg files -I "**/README"
2791 hg files -I "**/README"
2792
2792
2793 - list all binary files::
2793 - list all binary files::
2794
2794
2795 hg files "set:binary()"
2795 hg files "set:binary()"
2796
2796
2797 - find files containing a regular expression::
2797 - find files containing a regular expression::
2798
2798
2799 hg files "set:grep('bob')"
2799 hg files "set:grep('bob')"
2800
2800
2801 - search tracked file contents with xargs and grep::
2801 - search tracked file contents with xargs and grep::
2802
2802
2803 hg files -0 | xargs -0 grep foo
2803 hg files -0 | xargs -0 grep foo
2804
2804
2805 See :hg:`help patterns` and :hg:`help filesets` for more information
2805 See :hg:`help patterns` and :hg:`help filesets` for more information
2806 on specifying file patterns.
2806 on specifying file patterns.
2807
2807
2808 Returns 0 if a match is found, 1 otherwise.
2808 Returns 0 if a match is found, 1 otherwise.
2809
2809
2810 """
2810 """
2811
2811
2812 opts = pycompat.byteskwargs(opts)
2812 opts = pycompat.byteskwargs(opts)
2813 rev = opts.get(b'rev')
2813 rev = opts.get(b'rev')
2814 if rev:
2814 if rev:
2815 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2815 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2816 ctx = scmutil.revsingle(repo, rev, None)
2816 ctx = scmutil.revsingle(repo, rev, None)
2817
2817
2818 end = b'\n'
2818 end = b'\n'
2819 if opts.get(b'print0'):
2819 if opts.get(b'print0'):
2820 end = b'\0'
2820 end = b'\0'
2821 fmt = b'%s' + end
2821 fmt = b'%s' + end
2822
2822
2823 m = scmutil.match(ctx, pats, opts)
2823 m = scmutil.match(ctx, pats, opts)
2824 ui.pager(b'files')
2824 ui.pager(b'files')
2825 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2825 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2826 with ui.formatter(b'files', opts) as fm:
2826 with ui.formatter(b'files', opts) as fm:
2827 return cmdutil.files(
2827 return cmdutil.files(
2828 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2828 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2829 )
2829 )
2830
2830
2831
2831
2832 @command(
2832 @command(
2833 b'forget',
2833 b'forget',
2834 [
2834 [
2835 (b'i', b'interactive', None, _(b'use interactive mode')),
2835 (b'i', b'interactive', None, _(b'use interactive mode')),
2836 ]
2836 ]
2837 + walkopts
2837 + walkopts
2838 + dryrunopts,
2838 + dryrunopts,
2839 _(b'[OPTION]... FILE...'),
2839 _(b'[OPTION]... FILE...'),
2840 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2840 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2841 helpbasic=True,
2841 helpbasic=True,
2842 inferrepo=True,
2842 inferrepo=True,
2843 )
2843 )
2844 def forget(ui, repo, *pats, **opts):
2844 def forget(ui, repo, *pats, **opts):
2845 """forget the specified files on the next commit
2845 """forget the specified files on the next commit
2846
2846
2847 Mark the specified files so they will no longer be tracked
2847 Mark the specified files so they will no longer be tracked
2848 after the next commit.
2848 after the next commit.
2849
2849
2850 This only removes files from the current branch, not from the
2850 This only removes files from the current branch, not from the
2851 entire project history, and it does not delete them from the
2851 entire project history, and it does not delete them from the
2852 working directory.
2852 working directory.
2853
2853
2854 To delete the file from the working directory, see :hg:`remove`.
2854 To delete the file from the working directory, see :hg:`remove`.
2855
2855
2856 To undo a forget before the next commit, see :hg:`add`.
2856 To undo a forget before the next commit, see :hg:`add`.
2857
2857
2858 .. container:: verbose
2858 .. container:: verbose
2859
2859
2860 Examples:
2860 Examples:
2861
2861
2862 - forget newly-added binary files::
2862 - forget newly-added binary files::
2863
2863
2864 hg forget "set:added() and binary()"
2864 hg forget "set:added() and binary()"
2865
2865
2866 - forget files that would be excluded by .hgignore::
2866 - forget files that would be excluded by .hgignore::
2867
2867
2868 hg forget "set:hgignore()"
2868 hg forget "set:hgignore()"
2869
2869
2870 Returns 0 on success.
2870 Returns 0 on success.
2871 """
2871 """
2872
2872
2873 opts = pycompat.byteskwargs(opts)
2873 opts = pycompat.byteskwargs(opts)
2874 if not pats:
2874 if not pats:
2875 raise error.InputError(_(b'no files specified'))
2875 raise error.InputError(_(b'no files specified'))
2876
2876
2877 m = scmutil.match(repo[None], pats, opts)
2877 m = scmutil.match(repo[None], pats, opts)
2878 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2878 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2879 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2879 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2880 rejected = cmdutil.forget(
2880 rejected = cmdutil.forget(
2881 ui,
2881 ui,
2882 repo,
2882 repo,
2883 m,
2883 m,
2884 prefix=b"",
2884 prefix=b"",
2885 uipathfn=uipathfn,
2885 uipathfn=uipathfn,
2886 explicitonly=False,
2886 explicitonly=False,
2887 dryrun=dryrun,
2887 dryrun=dryrun,
2888 interactive=interactive,
2888 interactive=interactive,
2889 )[0]
2889 )[0]
2890 return rejected and 1 or 0
2890 return rejected and 1 or 0
2891
2891
2892
2892
2893 @command(
2893 @command(
2894 b'graft',
2894 b'graft',
2895 [
2895 [
2896 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2896 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2897 (
2897 (
2898 b'',
2898 b'',
2899 b'base',
2899 b'base',
2900 b'',
2900 b'',
2901 _(b'base revision when doing the graft merge (ADVANCED)'),
2901 _(b'base revision when doing the graft merge (ADVANCED)'),
2902 _(b'REV'),
2902 _(b'REV'),
2903 ),
2903 ),
2904 (b'c', b'continue', False, _(b'resume interrupted graft')),
2904 (b'c', b'continue', False, _(b'resume interrupted graft')),
2905 (b'', b'stop', False, _(b'stop interrupted graft')),
2905 (b'', b'stop', False, _(b'stop interrupted graft')),
2906 (b'', b'abort', False, _(b'abort interrupted graft')),
2906 (b'', b'abort', False, _(b'abort interrupted graft')),
2907 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2907 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2908 (b'', b'log', None, _(b'append graft info to log message')),
2908 (b'', b'log', None, _(b'append graft info to log message')),
2909 (
2909 (
2910 b'',
2910 b'',
2911 b'no-commit',
2911 b'no-commit',
2912 None,
2912 None,
2913 _(b"don't commit, just apply the changes in working directory"),
2913 _(b"don't commit, just apply the changes in working directory"),
2914 ),
2914 ),
2915 (b'f', b'force', False, _(b'force graft')),
2915 (b'f', b'force', False, _(b'force graft')),
2916 (
2916 (
2917 b'D',
2917 b'D',
2918 b'currentdate',
2918 b'currentdate',
2919 False,
2919 False,
2920 _(b'record the current date as commit date'),
2920 _(b'record the current date as commit date'),
2921 ),
2921 ),
2922 (
2922 (
2923 b'U',
2923 b'U',
2924 b'currentuser',
2924 b'currentuser',
2925 False,
2925 False,
2926 _(b'record the current user as committer'),
2926 _(b'record the current user as committer'),
2927 ),
2927 ),
2928 ]
2928 ]
2929 + commitopts2
2929 + commitopts2
2930 + mergetoolopts
2930 + mergetoolopts
2931 + dryrunopts,
2931 + dryrunopts,
2932 _(b'[OPTION]... [-r REV]... REV...'),
2932 _(b'[OPTION]... [-r REV]... REV...'),
2933 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2933 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2934 )
2934 )
2935 def graft(ui, repo, *revs, **opts):
2935 def graft(ui, repo, *revs, **opts):
2936 """copy changes from other branches onto the current branch
2936 """copy changes from other branches onto the current branch
2937
2937
2938 This command uses Mercurial's merge logic to copy individual
2938 This command uses Mercurial's merge logic to copy individual
2939 changes from other branches without merging branches in the
2939 changes from other branches without merging branches in the
2940 history graph. This is sometimes known as 'backporting' or
2940 history graph. This is sometimes known as 'backporting' or
2941 'cherry-picking'. By default, graft will copy user, date, and
2941 'cherry-picking'. By default, graft will copy user, date, and
2942 description from the source changesets.
2942 description from the source changesets.
2943
2943
2944 Changesets that are ancestors of the current revision, that have
2944 Changesets that are ancestors of the current revision, that have
2945 already been grafted, or that are merges will be skipped.
2945 already been grafted, or that are merges will be skipped.
2946
2946
2947 If --log is specified, log messages will have a comment appended
2947 If --log is specified, log messages will have a comment appended
2948 of the form::
2948 of the form::
2949
2949
2950 (grafted from CHANGESETHASH)
2950 (grafted from CHANGESETHASH)
2951
2951
2952 If --force is specified, revisions will be grafted even if they
2952 If --force is specified, revisions will be grafted even if they
2953 are already ancestors of, or have been grafted to, the destination.
2953 are already ancestors of, or have been grafted to, the destination.
2954 This is useful when the revisions have since been backed out.
2954 This is useful when the revisions have since been backed out.
2955
2955
2956 If a graft merge results in conflicts, the graft process is
2956 If a graft merge results in conflicts, the graft process is
2957 interrupted so that the current merge can be manually resolved.
2957 interrupted so that the current merge can be manually resolved.
2958 Once all conflicts are addressed, the graft process can be
2958 Once all conflicts are addressed, the graft process can be
2959 continued with the -c/--continue option.
2959 continued with the -c/--continue option.
2960
2960
2961 The -c/--continue option reapplies all the earlier options.
2961 The -c/--continue option reapplies all the earlier options.
2962
2962
2963 .. container:: verbose
2963 .. container:: verbose
2964
2964
2965 The --base option exposes more of how graft internally uses merge with a
2965 The --base option exposes more of how graft internally uses merge with a
2966 custom base revision. --base can be used to specify another ancestor than
2966 custom base revision. --base can be used to specify another ancestor than
2967 the first and only parent.
2967 the first and only parent.
2968
2968
2969 The command::
2969 The command::
2970
2970
2971 hg graft -r 345 --base 234
2971 hg graft -r 345 --base 234
2972
2972
2973 is thus pretty much the same as::
2973 is thus pretty much the same as::
2974
2974
2975 hg diff --from 234 --to 345 | hg import
2975 hg diff --from 234 --to 345 | hg import
2976
2976
2977 but using merge to resolve conflicts and track moved files.
2977 but using merge to resolve conflicts and track moved files.
2978
2978
2979 The result of a merge can thus be backported as a single commit by
2979 The result of a merge can thus be backported as a single commit by
2980 specifying one of the merge parents as base, and thus effectively
2980 specifying one of the merge parents as base, and thus effectively
2981 grafting the changes from the other side.
2981 grafting the changes from the other side.
2982
2982
2983 It is also possible to collapse multiple changesets and clean up history
2983 It is also possible to collapse multiple changesets and clean up history
2984 by specifying another ancestor as base, much like rebase --collapse
2984 by specifying another ancestor as base, much like rebase --collapse
2985 --keep.
2985 --keep.
2986
2986
2987 The commit message can be tweaked after the fact using commit --amend .
2987 The commit message can be tweaked after the fact using commit --amend .
2988
2988
2989 For using non-ancestors as the base to backout changes, see the backout
2989 For using non-ancestors as the base to backout changes, see the backout
2990 command and the hidden --parent option.
2990 command and the hidden --parent option.
2991
2991
2992 .. container:: verbose
2992 .. container:: verbose
2993
2993
2994 Examples:
2994 Examples:
2995
2995
2996 - copy a single change to the stable branch and edit its description::
2996 - copy a single change to the stable branch and edit its description::
2997
2997
2998 hg update stable
2998 hg update stable
2999 hg graft --edit 9393
2999 hg graft --edit 9393
3000
3000
3001 - graft a range of changesets with one exception, updating dates::
3001 - graft a range of changesets with one exception, updating dates::
3002
3002
3003 hg graft -D "2085::2093 and not 2091"
3003 hg graft -D "2085::2093 and not 2091"
3004
3004
3005 - continue a graft after resolving conflicts::
3005 - continue a graft after resolving conflicts::
3006
3006
3007 hg graft -c
3007 hg graft -c
3008
3008
3009 - show the source of a grafted changeset::
3009 - show the source of a grafted changeset::
3010
3010
3011 hg log --debug -r .
3011 hg log --debug -r .
3012
3012
3013 - show revisions sorted by date::
3013 - show revisions sorted by date::
3014
3014
3015 hg log -r "sort(all(), date)"
3015 hg log -r "sort(all(), date)"
3016
3016
3017 - backport the result of a merge as a single commit::
3017 - backport the result of a merge as a single commit::
3018
3018
3019 hg graft -r 123 --base 123^
3019 hg graft -r 123 --base 123^
3020
3020
3021 - land a feature branch as one changeset::
3021 - land a feature branch as one changeset::
3022
3022
3023 hg up -cr default
3023 hg up -cr default
3024 hg graft -r featureX --base "ancestor('featureX', 'default')"
3024 hg graft -r featureX --base "ancestor('featureX', 'default')"
3025
3025
3026 See :hg:`help revisions` for more about specifying revisions.
3026 See :hg:`help revisions` for more about specifying revisions.
3027
3027
3028 Returns 0 on successful completion, 1 if there are unresolved files.
3028 Returns 0 on successful completion, 1 if there are unresolved files.
3029 """
3029 """
3030 with repo.wlock():
3030 with repo.wlock():
3031 return _dograft(ui, repo, *revs, **opts)
3031 return _dograft(ui, repo, *revs, **opts)
3032
3032
3033
3033
3034 def _dograft(ui, repo, *revs, **opts):
3034 def _dograft(ui, repo, *revs, **opts):
3035 opts = pycompat.byteskwargs(opts)
3035 opts = pycompat.byteskwargs(opts)
3036 if revs and opts.get(b'rev'):
3036 if revs and opts.get(b'rev'):
3037 ui.warn(
3037 ui.warn(
3038 _(
3038 _(
3039 b'warning: inconsistent use of --rev might give unexpected '
3039 b'warning: inconsistent use of --rev might give unexpected '
3040 b'revision ordering!\n'
3040 b'revision ordering!\n'
3041 )
3041 )
3042 )
3042 )
3043
3043
3044 revs = list(revs)
3044 revs = list(revs)
3045 revs.extend(opts.get(b'rev'))
3045 revs.extend(opts.get(b'rev'))
3046 # a dict of data to be stored in state file
3046 # a dict of data to be stored in state file
3047 statedata = {}
3047 statedata = {}
3048 # list of new nodes created by ongoing graft
3048 # list of new nodes created by ongoing graft
3049 statedata[b'newnodes'] = []
3049 statedata[b'newnodes'] = []
3050
3050
3051 cmdutil.resolvecommitoptions(ui, opts)
3051 cmdutil.resolvecommitoptions(ui, opts)
3052
3052
3053 editor = cmdutil.getcommiteditor(
3053 editor = cmdutil.getcommiteditor(
3054 editform=b'graft', **pycompat.strkwargs(opts)
3054 editform=b'graft', **pycompat.strkwargs(opts)
3055 )
3055 )
3056
3056
3057 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3057 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3058
3058
3059 cont = False
3059 cont = False
3060 if opts.get(b'no_commit'):
3060 if opts.get(b'no_commit'):
3061 cmdutil.check_incompatible_arguments(
3061 cmdutil.check_incompatible_arguments(
3062 opts,
3062 opts,
3063 b'no_commit',
3063 b'no_commit',
3064 [b'edit', b'currentuser', b'currentdate', b'log'],
3064 [b'edit', b'currentuser', b'currentdate', b'log'],
3065 )
3065 )
3066
3066
3067 graftstate = statemod.cmdstate(repo, b'graftstate')
3067 graftstate = statemod.cmdstate(repo, b'graftstate')
3068
3068
3069 if opts.get(b'stop'):
3069 if opts.get(b'stop'):
3070 cmdutil.check_incompatible_arguments(
3070 cmdutil.check_incompatible_arguments(
3071 opts,
3071 opts,
3072 b'stop',
3072 b'stop',
3073 [
3073 [
3074 b'edit',
3074 b'edit',
3075 b'log',
3075 b'log',
3076 b'user',
3076 b'user',
3077 b'date',
3077 b'date',
3078 b'currentdate',
3078 b'currentdate',
3079 b'currentuser',
3079 b'currentuser',
3080 b'rev',
3080 b'rev',
3081 ],
3081 ],
3082 )
3082 )
3083 return _stopgraft(ui, repo, graftstate)
3083 return _stopgraft(ui, repo, graftstate)
3084 elif opts.get(b'abort'):
3084 elif opts.get(b'abort'):
3085 cmdutil.check_incompatible_arguments(
3085 cmdutil.check_incompatible_arguments(
3086 opts,
3086 opts,
3087 b'abort',
3087 b'abort',
3088 [
3088 [
3089 b'edit',
3089 b'edit',
3090 b'log',
3090 b'log',
3091 b'user',
3091 b'user',
3092 b'date',
3092 b'date',
3093 b'currentdate',
3093 b'currentdate',
3094 b'currentuser',
3094 b'currentuser',
3095 b'rev',
3095 b'rev',
3096 ],
3096 ],
3097 )
3097 )
3098 return cmdutil.abortgraft(ui, repo, graftstate)
3098 return cmdutil.abortgraft(ui, repo, graftstate)
3099 elif opts.get(b'continue'):
3099 elif opts.get(b'continue'):
3100 cont = True
3100 cont = True
3101 if revs:
3101 if revs:
3102 raise error.InputError(_(b"can't specify --continue and revisions"))
3102 raise error.InputError(_(b"can't specify --continue and revisions"))
3103 # read in unfinished revisions
3103 # read in unfinished revisions
3104 if graftstate.exists():
3104 if graftstate.exists():
3105 statedata = cmdutil.readgraftstate(repo, graftstate)
3105 statedata = cmdutil.readgraftstate(repo, graftstate)
3106 if statedata.get(b'date'):
3106 if statedata.get(b'date'):
3107 opts[b'date'] = statedata[b'date']
3107 opts[b'date'] = statedata[b'date']
3108 if statedata.get(b'user'):
3108 if statedata.get(b'user'):
3109 opts[b'user'] = statedata[b'user']
3109 opts[b'user'] = statedata[b'user']
3110 if statedata.get(b'log'):
3110 if statedata.get(b'log'):
3111 opts[b'log'] = True
3111 opts[b'log'] = True
3112 if statedata.get(b'no_commit'):
3112 if statedata.get(b'no_commit'):
3113 opts[b'no_commit'] = statedata.get(b'no_commit')
3113 opts[b'no_commit'] = statedata.get(b'no_commit')
3114 if statedata.get(b'base'):
3114 if statedata.get(b'base'):
3115 opts[b'base'] = statedata.get(b'base')
3115 opts[b'base'] = statedata.get(b'base')
3116 nodes = statedata[b'nodes']
3116 nodes = statedata[b'nodes']
3117 revs = [repo[node].rev() for node in nodes]
3117 revs = [repo[node].rev() for node in nodes]
3118 else:
3118 else:
3119 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3119 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3120 else:
3120 else:
3121 if not revs:
3121 if not revs:
3122 raise error.InputError(_(b'no revisions specified'))
3122 raise error.InputError(_(b'no revisions specified'))
3123 cmdutil.checkunfinished(repo)
3123 cmdutil.checkunfinished(repo)
3124 cmdutil.bailifchanged(repo)
3124 cmdutil.bailifchanged(repo)
3125 revs = scmutil.revrange(repo, revs)
3125 revs = scmutil.revrange(repo, revs)
3126
3126
3127 skipped = set()
3127 skipped = set()
3128 basectx = None
3128 basectx = None
3129 if opts.get(b'base'):
3129 if opts.get(b'base'):
3130 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3130 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3131 if basectx is None:
3131 if basectx is None:
3132 # check for merges
3132 # check for merges
3133 for rev in repo.revs(b'%ld and merge()', revs):
3133 for rev in repo.revs(b'%ld and merge()', revs):
3134 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3134 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3135 skipped.add(rev)
3135 skipped.add(rev)
3136 revs = [r for r in revs if r not in skipped]
3136 revs = [r for r in revs if r not in skipped]
3137 if not revs:
3137 if not revs:
3138 return -1
3138 return -1
3139 if basectx is not None and len(revs) != 1:
3139 if basectx is not None and len(revs) != 1:
3140 raise error.InputError(_(b'only one revision allowed with --base '))
3140 raise error.InputError(_(b'only one revision allowed with --base '))
3141
3141
3142 # Don't check in the --continue case, in effect retaining --force across
3142 # Don't check in the --continue case, in effect retaining --force across
3143 # --continues. That's because without --force, any revisions we decided to
3143 # --continues. That's because without --force, any revisions we decided to
3144 # skip would have been filtered out here, so they wouldn't have made their
3144 # skip would have been filtered out here, so they wouldn't have made their
3145 # way to the graftstate. With --force, any revisions we would have otherwise
3145 # way to the graftstate. With --force, any revisions we would have otherwise
3146 # skipped would not have been filtered out, and if they hadn't been applied
3146 # skipped would not have been filtered out, and if they hadn't been applied
3147 # already, they'd have been in the graftstate.
3147 # already, they'd have been in the graftstate.
3148 if not (cont or opts.get(b'force')) and basectx is None:
3148 if not (cont or opts.get(b'force')) and basectx is None:
3149 # check for ancestors of dest branch
3149 # check for ancestors of dest branch
3150 ancestors = repo.revs(b'%ld & (::.)', revs)
3150 ancestors = repo.revs(b'%ld & (::.)', revs)
3151 for rev in ancestors:
3151 for rev in ancestors:
3152 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3152 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3153
3153
3154 revs = [r for r in revs if r not in ancestors]
3154 revs = [r for r in revs if r not in ancestors]
3155
3155
3156 if not revs:
3156 if not revs:
3157 return -1
3157 return -1
3158
3158
3159 # analyze revs for earlier grafts
3159 # analyze revs for earlier grafts
3160 ids = {}
3160 ids = {}
3161 for ctx in repo.set(b"%ld", revs):
3161 for ctx in repo.set(b"%ld", revs):
3162 ids[ctx.hex()] = ctx.rev()
3162 ids[ctx.hex()] = ctx.rev()
3163 n = ctx.extra().get(b'source')
3163 n = ctx.extra().get(b'source')
3164 if n:
3164 if n:
3165 ids[n] = ctx.rev()
3165 ids[n] = ctx.rev()
3166
3166
3167 # check ancestors for earlier grafts
3167 # check ancestors for earlier grafts
3168 ui.debug(b'scanning for duplicate grafts\n')
3168 ui.debug(b'scanning for duplicate grafts\n')
3169
3169
3170 # The only changesets we can be sure doesn't contain grafts of any
3170 # The only changesets we can be sure doesn't contain grafts of any
3171 # revs, are the ones that are common ancestors of *all* revs:
3171 # revs, are the ones that are common ancestors of *all* revs:
3172 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3172 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3173 ctx = repo[rev]
3173 ctx = repo[rev]
3174 n = ctx.extra().get(b'source')
3174 n = ctx.extra().get(b'source')
3175 if n in ids:
3175 if n in ids:
3176 try:
3176 try:
3177 r = repo[n].rev()
3177 r = repo[n].rev()
3178 except error.RepoLookupError:
3178 except error.RepoLookupError:
3179 r = None
3179 r = None
3180 if r in revs:
3180 if r in revs:
3181 ui.warn(
3181 ui.warn(
3182 _(
3182 _(
3183 b'skipping revision %d:%s '
3183 b'skipping revision %d:%s '
3184 b'(already grafted to %d:%s)\n'
3184 b'(already grafted to %d:%s)\n'
3185 )
3185 )
3186 % (r, repo[r], rev, ctx)
3186 % (r, repo[r], rev, ctx)
3187 )
3187 )
3188 revs.remove(r)
3188 revs.remove(r)
3189 elif ids[n] in revs:
3189 elif ids[n] in revs:
3190 if r is None:
3190 if r is None:
3191 ui.warn(
3191 ui.warn(
3192 _(
3192 _(
3193 b'skipping already grafted revision %d:%s '
3193 b'skipping already grafted revision %d:%s '
3194 b'(%d:%s also has unknown origin %s)\n'
3194 b'(%d:%s also has unknown origin %s)\n'
3195 )
3195 )
3196 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3196 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3197 )
3197 )
3198 else:
3198 else:
3199 ui.warn(
3199 ui.warn(
3200 _(
3200 _(
3201 b'skipping already grafted revision %d:%s '
3201 b'skipping already grafted revision %d:%s '
3202 b'(%d:%s also has origin %d:%s)\n'
3202 b'(%d:%s also has origin %d:%s)\n'
3203 )
3203 )
3204 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3204 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3205 )
3205 )
3206 revs.remove(ids[n])
3206 revs.remove(ids[n])
3207 elif ctx.hex() in ids:
3207 elif ctx.hex() in ids:
3208 r = ids[ctx.hex()]
3208 r = ids[ctx.hex()]
3209 if r in revs:
3209 if r in revs:
3210 ui.warn(
3210 ui.warn(
3211 _(
3211 _(
3212 b'skipping already grafted revision %d:%s '
3212 b'skipping already grafted revision %d:%s '
3213 b'(was grafted from %d:%s)\n'
3213 b'(was grafted from %d:%s)\n'
3214 )
3214 )
3215 % (r, repo[r], rev, ctx)
3215 % (r, repo[r], rev, ctx)
3216 )
3216 )
3217 revs.remove(r)
3217 revs.remove(r)
3218 if not revs:
3218 if not revs:
3219 return -1
3219 return -1
3220
3220
3221 if opts.get(b'no_commit'):
3221 if opts.get(b'no_commit'):
3222 statedata[b'no_commit'] = True
3222 statedata[b'no_commit'] = True
3223 if opts.get(b'base'):
3223 if opts.get(b'base'):
3224 statedata[b'base'] = opts[b'base']
3224 statedata[b'base'] = opts[b'base']
3225 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3225 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3226 desc = b'%d:%s "%s"' % (
3226 desc = b'%d:%s "%s"' % (
3227 ctx.rev(),
3227 ctx.rev(),
3228 ctx,
3228 ctx,
3229 ctx.description().split(b'\n', 1)[0],
3229 ctx.description().split(b'\n', 1)[0],
3230 )
3230 )
3231 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3231 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3232 if names:
3232 if names:
3233 desc += b' (%s)' % b' '.join(names)
3233 desc += b' (%s)' % b' '.join(names)
3234 ui.status(_(b'grafting %s\n') % desc)
3234 ui.status(_(b'grafting %s\n') % desc)
3235 if opts.get(b'dry_run'):
3235 if opts.get(b'dry_run'):
3236 continue
3236 continue
3237
3237
3238 source = ctx.extra().get(b'source')
3238 source = ctx.extra().get(b'source')
3239 extra = {}
3239 extra = {}
3240 if source:
3240 if source:
3241 extra[b'source'] = source
3241 extra[b'source'] = source
3242 extra[b'intermediate-source'] = ctx.hex()
3242 extra[b'intermediate-source'] = ctx.hex()
3243 else:
3243 else:
3244 extra[b'source'] = ctx.hex()
3244 extra[b'source'] = ctx.hex()
3245 user = ctx.user()
3245 user = ctx.user()
3246 if opts.get(b'user'):
3246 if opts.get(b'user'):
3247 user = opts[b'user']
3247 user = opts[b'user']
3248 statedata[b'user'] = user
3248 statedata[b'user'] = user
3249 date = ctx.date()
3249 date = ctx.date()
3250 if opts.get(b'date'):
3250 if opts.get(b'date'):
3251 date = opts[b'date']
3251 date = opts[b'date']
3252 statedata[b'date'] = date
3252 statedata[b'date'] = date
3253 message = ctx.description()
3253 message = ctx.description()
3254 if opts.get(b'log'):
3254 if opts.get(b'log'):
3255 message += b'\n(grafted from %s)' % ctx.hex()
3255 message += b'\n(grafted from %s)' % ctx.hex()
3256 statedata[b'log'] = True
3256 statedata[b'log'] = True
3257
3257
3258 # we don't merge the first commit when continuing
3258 # we don't merge the first commit when continuing
3259 if not cont:
3259 if not cont:
3260 # perform the graft merge with p1(rev) as 'ancestor'
3260 # perform the graft merge with p1(rev) as 'ancestor'
3261 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3261 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3262 base = ctx.p1() if basectx is None else basectx
3262 base = ctx.p1() if basectx is None else basectx
3263 with ui.configoverride(overrides, b'graft'):
3263 with ui.configoverride(overrides, b'graft'):
3264 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3264 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3265 # report any conflicts
3265 # report any conflicts
3266 if stats.unresolvedcount > 0:
3266 if stats.unresolvedcount > 0:
3267 # write out state for --continue
3267 # write out state for --continue
3268 nodes = [repo[rev].hex() for rev in revs[pos:]]
3268 nodes = [repo[rev].hex() for rev in revs[pos:]]
3269 statedata[b'nodes'] = nodes
3269 statedata[b'nodes'] = nodes
3270 stateversion = 1
3270 stateversion = 1
3271 graftstate.save(stateversion, statedata)
3271 graftstate.save(stateversion, statedata)
3272 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3272 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3273 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3273 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3274 return 1
3274 return 1
3275 else:
3275 else:
3276 cont = False
3276 cont = False
3277
3277
3278 # commit if --no-commit is false
3278 # commit if --no-commit is false
3279 if not opts.get(b'no_commit'):
3279 if not opts.get(b'no_commit'):
3280 node = repo.commit(
3280 node = repo.commit(
3281 text=message, user=user, date=date, extra=extra, editor=editor
3281 text=message, user=user, date=date, extra=extra, editor=editor
3282 )
3282 )
3283 if node is None:
3283 if node is None:
3284 ui.warn(
3284 ui.warn(
3285 _(b'note: graft of %d:%s created no changes to commit\n')
3285 _(b'note: graft of %d:%s created no changes to commit\n')
3286 % (ctx.rev(), ctx)
3286 % (ctx.rev(), ctx)
3287 )
3287 )
3288 # checking that newnodes exist because old state files won't have it
3288 # checking that newnodes exist because old state files won't have it
3289 elif statedata.get(b'newnodes') is not None:
3289 elif statedata.get(b'newnodes') is not None:
3290 statedata[b'newnodes'].append(node)
3290 statedata[b'newnodes'].append(node)
3291
3291
3292 # remove state when we complete successfully
3292 # remove state when we complete successfully
3293 if not opts.get(b'dry_run'):
3293 if not opts.get(b'dry_run'):
3294 graftstate.delete()
3294 graftstate.delete()
3295
3295
3296 return 0
3296 return 0
3297
3297
3298
3298
3299 def _stopgraft(ui, repo, graftstate):
3299 def _stopgraft(ui, repo, graftstate):
3300 """stop the interrupted graft"""
3300 """stop the interrupted graft"""
3301 if not graftstate.exists():
3301 if not graftstate.exists():
3302 raise error.StateError(_(b"no interrupted graft found"))
3302 raise error.StateError(_(b"no interrupted graft found"))
3303 pctx = repo[b'.']
3303 pctx = repo[b'.']
3304 mergemod.clean_update(pctx)
3304 mergemod.clean_update(pctx)
3305 graftstate.delete()
3305 graftstate.delete()
3306 ui.status(_(b"stopped the interrupted graft\n"))
3306 ui.status(_(b"stopped the interrupted graft\n"))
3307 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3307 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3308 return 0
3308 return 0
3309
3309
3310
3310
3311 statemod.addunfinished(
3311 statemod.addunfinished(
3312 b'graft',
3312 b'graft',
3313 fname=b'graftstate',
3313 fname=b'graftstate',
3314 clearable=True,
3314 clearable=True,
3315 stopflag=True,
3315 stopflag=True,
3316 continueflag=True,
3316 continueflag=True,
3317 abortfunc=cmdutil.hgabortgraft,
3317 abortfunc=cmdutil.hgabortgraft,
3318 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3318 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3319 )
3319 )
3320
3320
3321
3321
3322 @command(
3322 @command(
3323 b'grep',
3323 b'grep',
3324 [
3324 [
3325 (b'0', b'print0', None, _(b'end fields with NUL')),
3325 (b'0', b'print0', None, _(b'end fields with NUL')),
3326 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3326 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3327 (
3327 (
3328 b'',
3328 b'',
3329 b'diff',
3329 b'diff',
3330 None,
3330 None,
3331 _(
3331 _(
3332 b'search revision differences for when the pattern was added '
3332 b'search revision differences for when the pattern was added '
3333 b'or removed'
3333 b'or removed'
3334 ),
3334 ),
3335 ),
3335 ),
3336 (b'a', b'text', None, _(b'treat all files as text')),
3336 (b'a', b'text', None, _(b'treat all files as text')),
3337 (
3337 (
3338 b'f',
3338 b'f',
3339 b'follow',
3339 b'follow',
3340 None,
3340 None,
3341 _(
3341 _(
3342 b'follow changeset history,'
3342 b'follow changeset history,'
3343 b' or file history across copies and renames'
3343 b' or file history across copies and renames'
3344 ),
3344 ),
3345 ),
3345 ),
3346 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3346 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3347 (
3347 (
3348 b'l',
3348 b'l',
3349 b'files-with-matches',
3349 b'files-with-matches',
3350 None,
3350 None,
3351 _(b'print only filenames and revisions that match'),
3351 _(b'print only filenames and revisions that match'),
3352 ),
3352 ),
3353 (b'n', b'line-number', None, _(b'print matching line numbers')),
3353 (b'n', b'line-number', None, _(b'print matching line numbers')),
3354 (
3354 (
3355 b'r',
3355 b'r',
3356 b'rev',
3356 b'rev',
3357 [],
3357 [],
3358 _(b'search files changed within revision range'),
3358 _(b'search files changed within revision range'),
3359 _(b'REV'),
3359 _(b'REV'),
3360 ),
3360 ),
3361 (
3361 (
3362 b'',
3362 b'',
3363 b'all-files',
3363 b'all-files',
3364 None,
3364 None,
3365 _(
3365 _(
3366 b'include all files in the changeset while grepping (DEPRECATED)'
3366 b'include all files in the changeset while grepping (DEPRECATED)'
3367 ),
3367 ),
3368 ),
3368 ),
3369 (b'u', b'user', None, _(b'list the author (long with -v)')),
3369 (b'u', b'user', None, _(b'list the author (long with -v)')),
3370 (b'd', b'date', None, _(b'list the date (short with -q)')),
3370 (b'd', b'date', None, _(b'list the date (short with -q)')),
3371 ]
3371 ]
3372 + formatteropts
3372 + formatteropts
3373 + walkopts,
3373 + walkopts,
3374 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3374 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3375 helpcategory=command.CATEGORY_FILE_CONTENTS,
3375 helpcategory=command.CATEGORY_FILE_CONTENTS,
3376 inferrepo=True,
3376 inferrepo=True,
3377 intents={INTENT_READONLY},
3377 intents={INTENT_READONLY},
3378 )
3378 )
3379 def grep(ui, repo, pattern, *pats, **opts):
3379 def grep(ui, repo, pattern, *pats, **opts):
3380 """search for a pattern in specified files
3380 """search for a pattern in specified files
3381
3381
3382 Search the working directory or revision history for a regular
3382 Search the working directory or revision history for a regular
3383 expression in the specified files for the entire repository.
3383 expression in the specified files for the entire repository.
3384
3384
3385 By default, grep searches the repository files in the working
3385 By default, grep searches the repository files in the working
3386 directory and prints the files where it finds a match. To specify
3386 directory and prints the files where it finds a match. To specify
3387 historical revisions instead of the working directory, use the
3387 historical revisions instead of the working directory, use the
3388 --rev flag.
3388 --rev flag.
3389
3389
3390 To search instead historical revision differences that contains a
3390 To search instead historical revision differences that contains a
3391 change in match status ("-" for a match that becomes a non-match,
3391 change in match status ("-" for a match that becomes a non-match,
3392 or "+" for a non-match that becomes a match), use the --diff flag.
3392 or "+" for a non-match that becomes a match), use the --diff flag.
3393
3393
3394 PATTERN can be any Python (roughly Perl-compatible) regular
3394 PATTERN can be any Python (roughly Perl-compatible) regular
3395 expression.
3395 expression.
3396
3396
3397 If no FILEs are specified and the --rev flag isn't supplied, all
3397 If no FILEs are specified and the --rev flag isn't supplied, all
3398 files in the working directory are searched. When using the --rev
3398 files in the working directory are searched. When using the --rev
3399 flag and specifying FILEs, use the --follow argument to also
3399 flag and specifying FILEs, use the --follow argument to also
3400 follow the specified FILEs across renames and copies.
3400 follow the specified FILEs across renames and copies.
3401
3401
3402 .. container:: verbose
3402 .. container:: verbose
3403
3403
3404 Template:
3404 Template:
3405
3405
3406 The following keywords are supported in addition to the common template
3406 The following keywords are supported in addition to the common template
3407 keywords and functions. See also :hg:`help templates`.
3407 keywords and functions. See also :hg:`help templates`.
3408
3408
3409 :change: String. Character denoting insertion ``+`` or removal ``-``.
3409 :change: String. Character denoting insertion ``+`` or removal ``-``.
3410 Available if ``--diff`` is specified.
3410 Available if ``--diff`` is specified.
3411 :lineno: Integer. Line number of the match.
3411 :lineno: Integer. Line number of the match.
3412 :path: String. Repository-absolute path of the file.
3412 :path: String. Repository-absolute path of the file.
3413 :texts: List of text chunks.
3413 :texts: List of text chunks.
3414
3414
3415 And each entry of ``{texts}`` provides the following sub-keywords.
3415 And each entry of ``{texts}`` provides the following sub-keywords.
3416
3416
3417 :matched: Boolean. True if the chunk matches the specified pattern.
3417 :matched: Boolean. True if the chunk matches the specified pattern.
3418 :text: String. Chunk content.
3418 :text: String. Chunk content.
3419
3419
3420 See :hg:`help templates.operators` for the list expansion syntax.
3420 See :hg:`help templates.operators` for the list expansion syntax.
3421
3421
3422 Returns 0 if a match is found, 1 otherwise.
3422 Returns 0 if a match is found, 1 otherwise.
3423
3423
3424 """
3424 """
3425 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3425 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3426 opts = pycompat.byteskwargs(opts)
3426 opts = pycompat.byteskwargs(opts)
3427 diff = opts.get(b'all') or opts.get(b'diff')
3427 diff = opts.get(b'all') or opts.get(b'diff')
3428 follow = opts.get(b'follow')
3428 follow = opts.get(b'follow')
3429 if opts.get(b'all_files') is None and not diff:
3429 if opts.get(b'all_files') is None and not diff:
3430 opts[b'all_files'] = True
3430 opts[b'all_files'] = True
3431 plaingrep = (
3431 plaingrep = (
3432 opts.get(b'all_files')
3432 opts.get(b'all_files')
3433 and not opts.get(b'rev')
3433 and not opts.get(b'rev')
3434 and not opts.get(b'follow')
3434 and not opts.get(b'follow')
3435 )
3435 )
3436 all_files = opts.get(b'all_files')
3436 all_files = opts.get(b'all_files')
3437 if plaingrep:
3437 if plaingrep:
3438 opts[b'rev'] = [b'wdir()']
3438 opts[b'rev'] = [b'wdir()']
3439
3439
3440 reflags = re.M
3440 reflags = re.M
3441 if opts.get(b'ignore_case'):
3441 if opts.get(b'ignore_case'):
3442 reflags |= re.I
3442 reflags |= re.I
3443 try:
3443 try:
3444 regexp = util.re.compile(pattern, reflags)
3444 regexp = util.re.compile(pattern, reflags)
3445 except re.error as inst:
3445 except re.error as inst:
3446 ui.warn(
3446 ui.warn(
3447 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3447 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3448 )
3448 )
3449 return 1
3449 return 1
3450 sep, eol = b':', b'\n'
3450 sep, eol = b':', b'\n'
3451 if opts.get(b'print0'):
3451 if opts.get(b'print0'):
3452 sep = eol = b'\0'
3452 sep = eol = b'\0'
3453
3453
3454 searcher = grepmod.grepsearcher(
3454 searcher = grepmod.grepsearcher(
3455 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3455 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3456 )
3456 )
3457
3457
3458 getfile = searcher._getfile
3458 getfile = searcher._getfile
3459
3459
3460 uipathfn = scmutil.getuipathfn(repo)
3460 uipathfn = scmutil.getuipathfn(repo)
3461
3461
3462 def display(fm, fn, ctx, pstates, states):
3462 def display(fm, fn, ctx, pstates, states):
3463 rev = scmutil.intrev(ctx)
3463 rev = scmutil.intrev(ctx)
3464 if fm.isplain():
3464 if fm.isplain():
3465 formatuser = ui.shortuser
3465 formatuser = ui.shortuser
3466 else:
3466 else:
3467 formatuser = pycompat.bytestr
3467 formatuser = pycompat.bytestr
3468 if ui.quiet:
3468 if ui.quiet:
3469 datefmt = b'%Y-%m-%d'
3469 datefmt = b'%Y-%m-%d'
3470 else:
3470 else:
3471 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3471 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3472 found = False
3472 found = False
3473
3473
3474 @util.cachefunc
3474 @util.cachefunc
3475 def binary():
3475 def binary():
3476 flog = getfile(fn)
3476 flog = getfile(fn)
3477 try:
3477 try:
3478 return stringutil.binary(flog.read(ctx.filenode(fn)))
3478 return stringutil.binary(flog.read(ctx.filenode(fn)))
3479 except error.WdirUnsupported:
3479 except error.WdirUnsupported:
3480 return ctx[fn].isbinary()
3480 return ctx[fn].isbinary()
3481
3481
3482 fieldnamemap = {b'linenumber': b'lineno'}
3482 fieldnamemap = {b'linenumber': b'lineno'}
3483 if diff:
3483 if diff:
3484 iter = grepmod.difflinestates(pstates, states)
3484 iter = grepmod.difflinestates(pstates, states)
3485 else:
3485 else:
3486 iter = [(b'', l) for l in states]
3486 iter = [(b'', l) for l in states]
3487 for change, l in iter:
3487 for change, l in iter:
3488 fm.startitem()
3488 fm.startitem()
3489 fm.context(ctx=ctx)
3489 fm.context(ctx=ctx)
3490 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3490 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3491 fm.plain(uipathfn(fn), label=b'grep.filename')
3491 fm.plain(uipathfn(fn), label=b'grep.filename')
3492
3492
3493 cols = [
3493 cols = [
3494 (b'rev', b'%d', rev, not plaingrep, b''),
3494 (b'rev', b'%d', rev, not plaingrep, b''),
3495 (
3495 (
3496 b'linenumber',
3496 b'linenumber',
3497 b'%d',
3497 b'%d',
3498 l.linenum,
3498 l.linenum,
3499 opts.get(b'line_number'),
3499 opts.get(b'line_number'),
3500 b'',
3500 b'',
3501 ),
3501 ),
3502 ]
3502 ]
3503 if diff:
3503 if diff:
3504 cols.append(
3504 cols.append(
3505 (
3505 (
3506 b'change',
3506 b'change',
3507 b'%s',
3507 b'%s',
3508 change,
3508 change,
3509 True,
3509 True,
3510 b'grep.inserted '
3510 b'grep.inserted '
3511 if change == b'+'
3511 if change == b'+'
3512 else b'grep.deleted ',
3512 else b'grep.deleted ',
3513 )
3513 )
3514 )
3514 )
3515 cols.extend(
3515 cols.extend(
3516 [
3516 [
3517 (
3517 (
3518 b'user',
3518 b'user',
3519 b'%s',
3519 b'%s',
3520 formatuser(ctx.user()),
3520 formatuser(ctx.user()),
3521 opts.get(b'user'),
3521 opts.get(b'user'),
3522 b'',
3522 b'',
3523 ),
3523 ),
3524 (
3524 (
3525 b'date',
3525 b'date',
3526 b'%s',
3526 b'%s',
3527 fm.formatdate(ctx.date(), datefmt),
3527 fm.formatdate(ctx.date(), datefmt),
3528 opts.get(b'date'),
3528 opts.get(b'date'),
3529 b'',
3529 b'',
3530 ),
3530 ),
3531 ]
3531 ]
3532 )
3532 )
3533 for name, fmt, data, cond, extra_label in cols:
3533 for name, fmt, data, cond, extra_label in cols:
3534 if cond:
3534 if cond:
3535 fm.plain(sep, label=b'grep.sep')
3535 fm.plain(sep, label=b'grep.sep')
3536 field = fieldnamemap.get(name, name)
3536 field = fieldnamemap.get(name, name)
3537 label = extra_label + (b'grep.%s' % name)
3537 label = extra_label + (b'grep.%s' % name)
3538 fm.condwrite(cond, field, fmt, data, label=label)
3538 fm.condwrite(cond, field, fmt, data, label=label)
3539 if not opts.get(b'files_with_matches'):
3539 if not opts.get(b'files_with_matches'):
3540 fm.plain(sep, label=b'grep.sep')
3540 fm.plain(sep, label=b'grep.sep')
3541 if not opts.get(b'text') and binary():
3541 if not opts.get(b'text') and binary():
3542 fm.plain(_(b" Binary file matches"))
3542 fm.plain(_(b" Binary file matches"))
3543 else:
3543 else:
3544 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3544 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3545 fm.plain(eol)
3545 fm.plain(eol)
3546 found = True
3546 found = True
3547 if opts.get(b'files_with_matches'):
3547 if opts.get(b'files_with_matches'):
3548 break
3548 break
3549 return found
3549 return found
3550
3550
3551 def displaymatches(fm, l):
3551 def displaymatches(fm, l):
3552 p = 0
3552 p = 0
3553 for s, e in l.findpos(regexp):
3553 for s, e in l.findpos(regexp):
3554 if p < s:
3554 if p < s:
3555 fm.startitem()
3555 fm.startitem()
3556 fm.write(b'text', b'%s', l.line[p:s])
3556 fm.write(b'text', b'%s', l.line[p:s])
3557 fm.data(matched=False)
3557 fm.data(matched=False)
3558 fm.startitem()
3558 fm.startitem()
3559 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3559 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3560 fm.data(matched=True)
3560 fm.data(matched=True)
3561 p = e
3561 p = e
3562 if p < len(l.line):
3562 if p < len(l.line):
3563 fm.startitem()
3563 fm.startitem()
3564 fm.write(b'text', b'%s', l.line[p:])
3564 fm.write(b'text', b'%s', l.line[p:])
3565 fm.data(matched=False)
3565 fm.data(matched=False)
3566 fm.end()
3566 fm.end()
3567
3567
3568 found = False
3568 found = False
3569
3569
3570 wopts = logcmdutil.walkopts(
3570 wopts = logcmdutil.walkopts(
3571 pats=pats,
3571 pats=pats,
3572 opts=opts,
3572 opts=opts,
3573 revspec=opts[b'rev'],
3573 revspec=opts[b'rev'],
3574 include_pats=opts[b'include'],
3574 include_pats=opts[b'include'],
3575 exclude_pats=opts[b'exclude'],
3575 exclude_pats=opts[b'exclude'],
3576 follow=follow,
3576 follow=follow,
3577 force_changelog_traversal=all_files,
3577 force_changelog_traversal=all_files,
3578 filter_revisions_by_pats=not all_files,
3578 filter_revisions_by_pats=not all_files,
3579 )
3579 )
3580 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3580 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3581
3581
3582 ui.pager(b'grep')
3582 ui.pager(b'grep')
3583 fm = ui.formatter(b'grep', opts)
3583 fm = ui.formatter(b'grep', opts)
3584 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3584 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3585 r = display(fm, fn, ctx, pstates, states)
3585 r = display(fm, fn, ctx, pstates, states)
3586 found = found or r
3586 found = found or r
3587 if r and not diff and not all_files:
3587 if r and not diff and not all_files:
3588 searcher.skipfile(fn, ctx.rev())
3588 searcher.skipfile(fn, ctx.rev())
3589 fm.end()
3589 fm.end()
3590
3590
3591 return not found
3591 return not found
3592
3592
3593
3593
3594 @command(
3594 @command(
3595 b'heads',
3595 b'heads',
3596 [
3596 [
3597 (
3597 (
3598 b'r',
3598 b'r',
3599 b'rev',
3599 b'rev',
3600 b'',
3600 b'',
3601 _(b'show only heads which are descendants of STARTREV'),
3601 _(b'show only heads which are descendants of STARTREV'),
3602 _(b'STARTREV'),
3602 _(b'STARTREV'),
3603 ),
3603 ),
3604 (b't', b'topo', False, _(b'show topological heads only')),
3604 (b't', b'topo', False, _(b'show topological heads only')),
3605 (
3605 (
3606 b'a',
3606 b'a',
3607 b'active',
3607 b'active',
3608 False,
3608 False,
3609 _(b'show active branchheads only (DEPRECATED)'),
3609 _(b'show active branchheads only (DEPRECATED)'),
3610 ),
3610 ),
3611 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3611 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3612 ]
3612 ]
3613 + templateopts,
3613 + templateopts,
3614 _(b'[-ct] [-r STARTREV] [REV]...'),
3614 _(b'[-ct] [-r STARTREV] [REV]...'),
3615 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3615 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3616 intents={INTENT_READONLY},
3616 intents={INTENT_READONLY},
3617 )
3617 )
3618 def heads(ui, repo, *branchrevs, **opts):
3618 def heads(ui, repo, *branchrevs, **opts):
3619 """show branch heads
3619 """show branch heads
3620
3620
3621 With no arguments, show all open branch heads in the repository.
3621 With no arguments, show all open branch heads in the repository.
3622 Branch heads are changesets that have no descendants on the
3622 Branch heads are changesets that have no descendants on the
3623 same branch. They are where development generally takes place and
3623 same branch. They are where development generally takes place and
3624 are the usual targets for update and merge operations.
3624 are the usual targets for update and merge operations.
3625
3625
3626 If one or more REVs are given, only open branch heads on the
3626 If one or more REVs are given, only open branch heads on the
3627 branches associated with the specified changesets are shown. This
3627 branches associated with the specified changesets are shown. This
3628 means that you can use :hg:`heads .` to see the heads on the
3628 means that you can use :hg:`heads .` to see the heads on the
3629 currently checked-out branch.
3629 currently checked-out branch.
3630
3630
3631 If -c/--closed is specified, also show branch heads marked closed
3631 If -c/--closed is specified, also show branch heads marked closed
3632 (see :hg:`commit --close-branch`).
3632 (see :hg:`commit --close-branch`).
3633
3633
3634 If STARTREV is specified, only those heads that are descendants of
3634 If STARTREV is specified, only those heads that are descendants of
3635 STARTREV will be displayed.
3635 STARTREV will be displayed.
3636
3636
3637 If -t/--topo is specified, named branch mechanics will be ignored and only
3637 If -t/--topo is specified, named branch mechanics will be ignored and only
3638 topological heads (changesets with no children) will be shown.
3638 topological heads (changesets with no children) will be shown.
3639
3639
3640 Returns 0 if matching heads are found, 1 if not.
3640 Returns 0 if matching heads are found, 1 if not.
3641 """
3641 """
3642
3642
3643 opts = pycompat.byteskwargs(opts)
3643 opts = pycompat.byteskwargs(opts)
3644 start = None
3644 start = None
3645 rev = opts.get(b'rev')
3645 rev = opts.get(b'rev')
3646 if rev:
3646 if rev:
3647 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3647 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3648 start = scmutil.revsingle(repo, rev, None).node()
3648 start = scmutil.revsingle(repo, rev, None).node()
3649
3649
3650 if opts.get(b'topo'):
3650 if opts.get(b'topo'):
3651 heads = [repo[h] for h in repo.heads(start)]
3651 heads = [repo[h] for h in repo.heads(start)]
3652 else:
3652 else:
3653 heads = []
3653 heads = []
3654 for branch in repo.branchmap():
3654 for branch in repo.branchmap():
3655 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3655 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3656 heads = [repo[h] for h in heads]
3656 heads = [repo[h] for h in heads]
3657
3657
3658 if branchrevs:
3658 if branchrevs:
3659 branches = {
3659 branches = {
3660 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3660 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3661 }
3661 }
3662 heads = [h for h in heads if h.branch() in branches]
3662 heads = [h for h in heads if h.branch() in branches]
3663
3663
3664 if opts.get(b'active') and branchrevs:
3664 if opts.get(b'active') and branchrevs:
3665 dagheads = repo.heads(start)
3665 dagheads = repo.heads(start)
3666 heads = [h for h in heads if h.node() in dagheads]
3666 heads = [h for h in heads if h.node() in dagheads]
3667
3667
3668 if branchrevs:
3668 if branchrevs:
3669 haveheads = {h.branch() for h in heads}
3669 haveheads = {h.branch() for h in heads}
3670 if branches - haveheads:
3670 if branches - haveheads:
3671 headless = b', '.join(b for b in branches - haveheads)
3671 headless = b', '.join(b for b in branches - haveheads)
3672 msg = _(b'no open branch heads found on branches %s')
3672 msg = _(b'no open branch heads found on branches %s')
3673 if opts.get(b'rev'):
3673 if opts.get(b'rev'):
3674 msg += _(b' (started at %s)') % opts[b'rev']
3674 msg += _(b' (started at %s)') % opts[b'rev']
3675 ui.warn((msg + b'\n') % headless)
3675 ui.warn((msg + b'\n') % headless)
3676
3676
3677 if not heads:
3677 if not heads:
3678 return 1
3678 return 1
3679
3679
3680 ui.pager(b'heads')
3680 ui.pager(b'heads')
3681 heads = sorted(heads, key=lambda x: -(x.rev()))
3681 heads = sorted(heads, key=lambda x: -(x.rev()))
3682 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3682 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3683 for ctx in heads:
3683 for ctx in heads:
3684 displayer.show(ctx)
3684 displayer.show(ctx)
3685 displayer.close()
3685 displayer.close()
3686
3686
3687
3687
3688 @command(
3688 @command(
3689 b'help',
3689 b'help',
3690 [
3690 [
3691 (b'e', b'extension', None, _(b'show only help for extensions')),
3691 (b'e', b'extension', None, _(b'show only help for extensions')),
3692 (b'c', b'command', None, _(b'show only help for commands')),
3692 (b'c', b'command', None, _(b'show only help for commands')),
3693 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3693 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3694 (
3694 (
3695 b's',
3695 b's',
3696 b'system',
3696 b'system',
3697 [],
3697 [],
3698 _(b'show help for specific platform(s)'),
3698 _(b'show help for specific platform(s)'),
3699 _(b'PLATFORM'),
3699 _(b'PLATFORM'),
3700 ),
3700 ),
3701 ],
3701 ],
3702 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3702 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3703 helpcategory=command.CATEGORY_HELP,
3703 helpcategory=command.CATEGORY_HELP,
3704 norepo=True,
3704 norepo=True,
3705 intents={INTENT_READONLY},
3705 intents={INTENT_READONLY},
3706 )
3706 )
3707 def help_(ui, name=None, **opts):
3707 def help_(ui, name=None, **opts):
3708 """show help for a given topic or a help overview
3708 """show help for a given topic or a help overview
3709
3709
3710 With no arguments, print a list of commands with short help messages.
3710 With no arguments, print a list of commands with short help messages.
3711
3711
3712 Given a topic, extension, or command name, print help for that
3712 Given a topic, extension, or command name, print help for that
3713 topic.
3713 topic.
3714
3714
3715 Returns 0 if successful.
3715 Returns 0 if successful.
3716 """
3716 """
3717
3717
3718 keep = opts.get('system') or []
3718 keep = opts.get('system') or []
3719 if len(keep) == 0:
3719 if len(keep) == 0:
3720 if pycompat.sysplatform.startswith(b'win'):
3720 if pycompat.sysplatform.startswith(b'win'):
3721 keep.append(b'windows')
3721 keep.append(b'windows')
3722 elif pycompat.sysplatform == b'OpenVMS':
3722 elif pycompat.sysplatform == b'OpenVMS':
3723 keep.append(b'vms')
3723 keep.append(b'vms')
3724 elif pycompat.sysplatform == b'plan9':
3724 elif pycompat.sysplatform == b'plan9':
3725 keep.append(b'plan9')
3725 keep.append(b'plan9')
3726 else:
3726 else:
3727 keep.append(b'unix')
3727 keep.append(b'unix')
3728 keep.append(pycompat.sysplatform.lower())
3728 keep.append(pycompat.sysplatform.lower())
3729 if ui.verbose:
3729 if ui.verbose:
3730 keep.append(b'verbose')
3730 keep.append(b'verbose')
3731
3731
3732 commands = sys.modules[__name__]
3732 commands = sys.modules[__name__]
3733 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3733 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3734 ui.pager(b'help')
3734 ui.pager(b'help')
3735 ui.write(formatted)
3735 ui.write(formatted)
3736
3736
3737
3737
3738 @command(
3738 @command(
3739 b'identify|id',
3739 b'identify|id',
3740 [
3740 [
3741 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3741 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3742 (b'n', b'num', None, _(b'show local revision number')),
3742 (b'n', b'num', None, _(b'show local revision number')),
3743 (b'i', b'id', None, _(b'show global revision id')),
3743 (b'i', b'id', None, _(b'show global revision id')),
3744 (b'b', b'branch', None, _(b'show branch')),
3744 (b'b', b'branch', None, _(b'show branch')),
3745 (b't', b'tags', None, _(b'show tags')),
3745 (b't', b'tags', None, _(b'show tags')),
3746 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3746 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3747 ]
3747 ]
3748 + remoteopts
3748 + remoteopts
3749 + formatteropts,
3749 + formatteropts,
3750 _(b'[-nibtB] [-r REV] [SOURCE]'),
3750 _(b'[-nibtB] [-r REV] [SOURCE]'),
3751 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3751 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3752 optionalrepo=True,
3752 optionalrepo=True,
3753 intents={INTENT_READONLY},
3753 intents={INTENT_READONLY},
3754 )
3754 )
3755 def identify(
3755 def identify(
3756 ui,
3756 ui,
3757 repo,
3757 repo,
3758 source=None,
3758 source=None,
3759 rev=None,
3759 rev=None,
3760 num=None,
3760 num=None,
3761 id=None,
3761 id=None,
3762 branch=None,
3762 branch=None,
3763 tags=None,
3763 tags=None,
3764 bookmarks=None,
3764 bookmarks=None,
3765 **opts
3765 **opts
3766 ):
3766 ):
3767 """identify the working directory or specified revision
3767 """identify the working directory or specified revision
3768
3768
3769 Print a summary identifying the repository state at REV using one or
3769 Print a summary identifying the repository state at REV using one or
3770 two parent hash identifiers, followed by a "+" if the working
3770 two parent hash identifiers, followed by a "+" if the working
3771 directory has uncommitted changes, the branch name (if not default),
3771 directory has uncommitted changes, the branch name (if not default),
3772 a list of tags, and a list of bookmarks.
3772 a list of tags, and a list of bookmarks.
3773
3773
3774 When REV is not given, print a summary of the current state of the
3774 When REV is not given, print a summary of the current state of the
3775 repository including the working directory. Specify -r. to get information
3775 repository including the working directory. Specify -r. to get information
3776 of the working directory parent without scanning uncommitted changes.
3776 of the working directory parent without scanning uncommitted changes.
3777
3777
3778 Specifying a path to a repository root or Mercurial bundle will
3778 Specifying a path to a repository root or Mercurial bundle will
3779 cause lookup to operate on that repository/bundle.
3779 cause lookup to operate on that repository/bundle.
3780
3780
3781 .. container:: verbose
3781 .. container:: verbose
3782
3782
3783 Template:
3783 Template:
3784
3784
3785 The following keywords are supported in addition to the common template
3785 The following keywords are supported in addition to the common template
3786 keywords and functions. See also :hg:`help templates`.
3786 keywords and functions. See also :hg:`help templates`.
3787
3787
3788 :dirty: String. Character ``+`` denoting if the working directory has
3788 :dirty: String. Character ``+`` denoting if the working directory has
3789 uncommitted changes.
3789 uncommitted changes.
3790 :id: String. One or two nodes, optionally followed by ``+``.
3790 :id: String. One or two nodes, optionally followed by ``+``.
3791 :parents: List of strings. Parent nodes of the changeset.
3791 :parents: List of strings. Parent nodes of the changeset.
3792
3792
3793 Examples:
3793 Examples:
3794
3794
3795 - generate a build identifier for the working directory::
3795 - generate a build identifier for the working directory::
3796
3796
3797 hg id --id > build-id.dat
3797 hg id --id > build-id.dat
3798
3798
3799 - find the revision corresponding to a tag::
3799 - find the revision corresponding to a tag::
3800
3800
3801 hg id -n -r 1.3
3801 hg id -n -r 1.3
3802
3802
3803 - check the most recent revision of a remote repository::
3803 - check the most recent revision of a remote repository::
3804
3804
3805 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3805 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3806
3806
3807 See :hg:`log` for generating more information about specific revisions,
3807 See :hg:`log` for generating more information about specific revisions,
3808 including full hash identifiers.
3808 including full hash identifiers.
3809
3809
3810 Returns 0 if successful.
3810 Returns 0 if successful.
3811 """
3811 """
3812
3812
3813 opts = pycompat.byteskwargs(opts)
3813 opts = pycompat.byteskwargs(opts)
3814 if not repo and not source:
3814 if not repo and not source:
3815 raise error.InputError(
3815 raise error.InputError(
3816 _(b"there is no Mercurial repository here (.hg not found)")
3816 _(b"there is no Mercurial repository here (.hg not found)")
3817 )
3817 )
3818
3818
3819 default = not (num or id or branch or tags or bookmarks)
3819 default = not (num or id or branch or tags or bookmarks)
3820 output = []
3820 output = []
3821 revs = []
3821 revs = []
3822
3822
3823 if source:
3823 peer = None
3824 source, branches = hg.parseurl(ui.expandpath(source))
3824 try:
3825 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3825 if source:
3826 repo = peer.local()
3826 source, branches = hg.parseurl(ui.expandpath(source))
3827 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3827 # only pass ui when no repo
3828
3828 peer = hg.peer(repo or ui, opts, source)
3829 fm = ui.formatter(b'identify', opts)
3829 repo = peer.local()
3830 fm.startitem()
3830 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3831
3831
3832 if not repo:
3832 fm = ui.formatter(b'identify', opts)
3833 if num or branch or tags:
3833 fm.startitem()
3834 raise error.InputError(
3834
3835 _(b"can't query remote revision number, branch, or tags")
3835 if not repo:
3836 )
3836 if num or branch or tags:
3837 if not rev and revs:
3837 raise error.InputError(
3838 rev = revs[0]
3838 _(b"can't query remote revision number, branch, or tags")
3839 if not rev:
3839 )
3840 rev = b"tip"
3840 if not rev and revs:
3841
3841 rev = revs[0]
3842 remoterev = peer.lookup(rev)
3842 if not rev:
3843 hexrev = fm.hexfunc(remoterev)
3843 rev = b"tip"
3844 if default or id:
3844
3845 output = [hexrev]
3845 remoterev = peer.lookup(rev)
3846 fm.data(id=hexrev)
3846 hexrev = fm.hexfunc(remoterev)
3847
3847 if default or id:
3848 @util.cachefunc
3848 output = [hexrev]
3849 def getbms():
3849 fm.data(id=hexrev)
3850 bms = []
3850
3851
3851 @util.cachefunc
3852 if b'bookmarks' in peer.listkeys(b'namespaces'):
3852 def getbms():
3853 hexremoterev = hex(remoterev)
3853 bms = []
3854 bms = [
3854
3855 bm
3855 if b'bookmarks' in peer.listkeys(b'namespaces'):
3856 for bm, bmr in pycompat.iteritems(
3856 hexremoterev = hex(remoterev)
3857 peer.listkeys(b'bookmarks')
3857 bms = [
3858 bm
3859 for bm, bmr in pycompat.iteritems(
3860 peer.listkeys(b'bookmarks')
3861 )
3862 if bmr == hexremoterev
3863 ]
3864
3865 return sorted(bms)
3866
3867 if fm.isplain():
3868 if bookmarks:
3869 output.extend(getbms())
3870 elif default and not ui.quiet:
3871 # multiple bookmarks for a single parent separated by '/'
3872 bm = b'/'.join(getbms())
3873 if bm:
3874 output.append(bm)
3875 else:
3876 fm.data(node=hex(remoterev))
3877 if bookmarks or b'bookmarks' in fm.datahint():
3878 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3879 else:
3880 if rev:
3881 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3882 ctx = scmutil.revsingle(repo, rev, None)
3883
3884 if ctx.rev() is None:
3885 ctx = repo[None]
3886 parents = ctx.parents()
3887 taglist = []
3888 for p in parents:
3889 taglist.extend(p.tags())
3890
3891 dirty = b""
3892 if ctx.dirty(missing=True, merge=False, branch=False):
3893 dirty = b'+'
3894 fm.data(dirty=dirty)
3895
3896 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3897 if default or id:
3898 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3899 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3900
3901 if num:
3902 numoutput = [b"%d" % p.rev() for p in parents]
3903 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3904
3905 fm.data(
3906 parents=fm.formatlist(
3907 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3858 )
3908 )
3859 if bmr == hexremoterev
3909 )
3860 ]
3910 else:
3861
3911 hexoutput = fm.hexfunc(ctx.node())
3862 return sorted(bms)
3912 if default or id:
3863
3913 output = [hexoutput]
3864 if fm.isplain():
3914 fm.data(id=hexoutput)
3865 if bookmarks:
3915
3866 output.extend(getbms())
3916 if num:
3867 elif default and not ui.quiet:
3917 output.append(pycompat.bytestr(ctx.rev()))
3918 taglist = ctx.tags()
3919
3920 if default and not ui.quiet:
3921 b = ctx.branch()
3922 if b != b'default':
3923 output.append(b"(%s)" % b)
3924
3925 # multiple tags for a single parent separated by '/'
3926 t = b'/'.join(taglist)
3927 if t:
3928 output.append(t)
3929
3868 # multiple bookmarks for a single parent separated by '/'
3930 # multiple bookmarks for a single parent separated by '/'
3869 bm = b'/'.join(getbms())
3931 bm = b'/'.join(ctx.bookmarks())
3870 if bm:
3932 if bm:
3871 output.append(bm)
3933 output.append(bm)
3872 else:
3934 else:
3873 fm.data(node=hex(remoterev))
3935 if branch:
3874 if bookmarks or b'bookmarks' in fm.datahint():
3936 output.append(ctx.branch())
3875 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3937
3876 else:
3938 if tags:
3877 if rev:
3939 output.extend(taglist)
3878 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3940
3879 ctx = scmutil.revsingle(repo, rev, None)
3941 if bookmarks:
3880
3942 output.extend(ctx.bookmarks())
3881 if ctx.rev() is None:
3943
3882 ctx = repo[None]
3944 fm.data(node=ctx.hex())
3883 parents = ctx.parents()
3945 fm.data(branch=ctx.branch())
3884 taglist = []
3946 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
3885 for p in parents:
3947 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
3886 taglist.extend(p.tags())
3948 fm.context(ctx=ctx)
3887
3949
3888 dirty = b""
3950 fm.plain(b"%s\n" % b' '.join(output))
3889 if ctx.dirty(missing=True, merge=False, branch=False):
3951 fm.end()
3890 dirty = b'+'
3952 finally:
3891 fm.data(dirty=dirty)
3953 if peer:
3892
3954 peer.close()
3893 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3894 if default or id:
3895 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3896 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3897
3898 if num:
3899 numoutput = [b"%d" % p.rev() for p in parents]
3900 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3901
3902 fm.data(
3903 parents=fm.formatlist(
3904 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3905 )
3906 )
3907 else:
3908 hexoutput = fm.hexfunc(ctx.node())
3909 if default or id:
3910 output = [hexoutput]
3911 fm.data(id=hexoutput)
3912
3913 if num:
3914 output.append(pycompat.bytestr(ctx.rev()))
3915 taglist = ctx.tags()
3916
3917 if default and not ui.quiet:
3918 b = ctx.branch()
3919 if b != b'default':
3920 output.append(b"(%s)" % b)
3921
3922 # multiple tags for a single parent separated by '/'
3923 t = b'/'.join(taglist)
3924 if t:
3925 output.append(t)
3926
3927 # multiple bookmarks for a single parent separated by '/'
3928 bm = b'/'.join(ctx.bookmarks())
3929 if bm:
3930 output.append(bm)
3931 else:
3932 if branch:
3933 output.append(ctx.branch())
3934
3935 if tags:
3936 output.extend(taglist)
3937
3938 if bookmarks:
3939 output.extend(ctx.bookmarks())
3940
3941 fm.data(node=ctx.hex())
3942 fm.data(branch=ctx.branch())
3943 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
3944 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
3945 fm.context(ctx=ctx)
3946
3947 fm.plain(b"%s\n" % b' '.join(output))
3948 fm.end()
3949
3955
3950
3956
3951 @command(
3957 @command(
3952 b'import|patch',
3958 b'import|patch',
3953 [
3959 [
3954 (
3960 (
3955 b'p',
3961 b'p',
3956 b'strip',
3962 b'strip',
3957 1,
3963 1,
3958 _(
3964 _(
3959 b'directory strip option for patch. This has the same '
3965 b'directory strip option for patch. This has the same '
3960 b'meaning as the corresponding patch option'
3966 b'meaning as the corresponding patch option'
3961 ),
3967 ),
3962 _(b'NUM'),
3968 _(b'NUM'),
3963 ),
3969 ),
3964 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
3970 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
3965 (b'', b'secret', None, _(b'use the secret phase for committing')),
3971 (b'', b'secret', None, _(b'use the secret phase for committing')),
3966 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
3972 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
3967 (
3973 (
3968 b'f',
3974 b'f',
3969 b'force',
3975 b'force',
3970 None,
3976 None,
3971 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
3977 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
3972 ),
3978 ),
3973 (
3979 (
3974 b'',
3980 b'',
3975 b'no-commit',
3981 b'no-commit',
3976 None,
3982 None,
3977 _(b"don't commit, just update the working directory"),
3983 _(b"don't commit, just update the working directory"),
3978 ),
3984 ),
3979 (
3985 (
3980 b'',
3986 b'',
3981 b'bypass',
3987 b'bypass',
3982 None,
3988 None,
3983 _(b"apply patch without touching the working directory"),
3989 _(b"apply patch without touching the working directory"),
3984 ),
3990 ),
3985 (b'', b'partial', None, _(b'commit even if some hunks fail')),
3991 (b'', b'partial', None, _(b'commit even if some hunks fail')),
3986 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
3992 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
3987 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
3993 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
3988 (
3994 (
3989 b'',
3995 b'',
3990 b'import-branch',
3996 b'import-branch',
3991 None,
3997 None,
3992 _(b'use any branch information in patch (implied by --exact)'),
3998 _(b'use any branch information in patch (implied by --exact)'),
3993 ),
3999 ),
3994 ]
4000 ]
3995 + commitopts
4001 + commitopts
3996 + commitopts2
4002 + commitopts2
3997 + similarityopts,
4003 + similarityopts,
3998 _(b'[OPTION]... PATCH...'),
4004 _(b'[OPTION]... PATCH...'),
3999 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4005 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4000 )
4006 )
4001 def import_(ui, repo, patch1=None, *patches, **opts):
4007 def import_(ui, repo, patch1=None, *patches, **opts):
4002 """import an ordered set of patches
4008 """import an ordered set of patches
4003
4009
4004 Import a list of patches and commit them individually (unless
4010 Import a list of patches and commit them individually (unless
4005 --no-commit is specified).
4011 --no-commit is specified).
4006
4012
4007 To read a patch from standard input (stdin), use "-" as the patch
4013 To read a patch from standard input (stdin), use "-" as the patch
4008 name. If a URL is specified, the patch will be downloaded from
4014 name. If a URL is specified, the patch will be downloaded from
4009 there.
4015 there.
4010
4016
4011 Import first applies changes to the working directory (unless
4017 Import first applies changes to the working directory (unless
4012 --bypass is specified), import will abort if there are outstanding
4018 --bypass is specified), import will abort if there are outstanding
4013 changes.
4019 changes.
4014
4020
4015 Use --bypass to apply and commit patches directly to the
4021 Use --bypass to apply and commit patches directly to the
4016 repository, without affecting the working directory. Without
4022 repository, without affecting the working directory. Without
4017 --exact, patches will be applied on top of the working directory
4023 --exact, patches will be applied on top of the working directory
4018 parent revision.
4024 parent revision.
4019
4025
4020 You can import a patch straight from a mail message. Even patches
4026 You can import a patch straight from a mail message. Even patches
4021 as attachments work (to use the body part, it must have type
4027 as attachments work (to use the body part, it must have type
4022 text/plain or text/x-patch). From and Subject headers of email
4028 text/plain or text/x-patch). From and Subject headers of email
4023 message are used as default committer and commit message. All
4029 message are used as default committer and commit message. All
4024 text/plain body parts before first diff are added to the commit
4030 text/plain body parts before first diff are added to the commit
4025 message.
4031 message.
4026
4032
4027 If the imported patch was generated by :hg:`export`, user and
4033 If the imported patch was generated by :hg:`export`, user and
4028 description from patch override values from message headers and
4034 description from patch override values from message headers and
4029 body. Values given on command line with -m/--message and -u/--user
4035 body. Values given on command line with -m/--message and -u/--user
4030 override these.
4036 override these.
4031
4037
4032 If --exact is specified, import will set the working directory to
4038 If --exact is specified, import will set the working directory to
4033 the parent of each patch before applying it, and will abort if the
4039 the parent of each patch before applying it, and will abort if the
4034 resulting changeset has a different ID than the one recorded in
4040 resulting changeset has a different ID than the one recorded in
4035 the patch. This will guard against various ways that portable
4041 the patch. This will guard against various ways that portable
4036 patch formats and mail systems might fail to transfer Mercurial
4042 patch formats and mail systems might fail to transfer Mercurial
4037 data or metadata. See :hg:`bundle` for lossless transmission.
4043 data or metadata. See :hg:`bundle` for lossless transmission.
4038
4044
4039 Use --partial to ensure a changeset will be created from the patch
4045 Use --partial to ensure a changeset will be created from the patch
4040 even if some hunks fail to apply. Hunks that fail to apply will be
4046 even if some hunks fail to apply. Hunks that fail to apply will be
4041 written to a <target-file>.rej file. Conflicts can then be resolved
4047 written to a <target-file>.rej file. Conflicts can then be resolved
4042 by hand before :hg:`commit --amend` is run to update the created
4048 by hand before :hg:`commit --amend` is run to update the created
4043 changeset. This flag exists to let people import patches that
4049 changeset. This flag exists to let people import patches that
4044 partially apply without losing the associated metadata (author,
4050 partially apply without losing the associated metadata (author,
4045 date, description, ...).
4051 date, description, ...).
4046
4052
4047 .. note::
4053 .. note::
4048
4054
4049 When no hunks apply cleanly, :hg:`import --partial` will create
4055 When no hunks apply cleanly, :hg:`import --partial` will create
4050 an empty changeset, importing only the patch metadata.
4056 an empty changeset, importing only the patch metadata.
4051
4057
4052 With -s/--similarity, hg will attempt to discover renames and
4058 With -s/--similarity, hg will attempt to discover renames and
4053 copies in the patch in the same way as :hg:`addremove`.
4059 copies in the patch in the same way as :hg:`addremove`.
4054
4060
4055 It is possible to use external patch programs to perform the patch
4061 It is possible to use external patch programs to perform the patch
4056 by setting the ``ui.patch`` configuration option. For the default
4062 by setting the ``ui.patch`` configuration option. For the default
4057 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4063 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4058 See :hg:`help config` for more information about configuration
4064 See :hg:`help config` for more information about configuration
4059 files and how to use these options.
4065 files and how to use these options.
4060
4066
4061 See :hg:`help dates` for a list of formats valid for -d/--date.
4067 See :hg:`help dates` for a list of formats valid for -d/--date.
4062
4068
4063 .. container:: verbose
4069 .. container:: verbose
4064
4070
4065 Examples:
4071 Examples:
4066
4072
4067 - import a traditional patch from a website and detect renames::
4073 - import a traditional patch from a website and detect renames::
4068
4074
4069 hg import -s 80 http://example.com/bugfix.patch
4075 hg import -s 80 http://example.com/bugfix.patch
4070
4076
4071 - import a changeset from an hgweb server::
4077 - import a changeset from an hgweb server::
4072
4078
4073 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4079 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4074
4080
4075 - import all the patches in an Unix-style mbox::
4081 - import all the patches in an Unix-style mbox::
4076
4082
4077 hg import incoming-patches.mbox
4083 hg import incoming-patches.mbox
4078
4084
4079 - import patches from stdin::
4085 - import patches from stdin::
4080
4086
4081 hg import -
4087 hg import -
4082
4088
4083 - attempt to exactly restore an exported changeset (not always
4089 - attempt to exactly restore an exported changeset (not always
4084 possible)::
4090 possible)::
4085
4091
4086 hg import --exact proposed-fix.patch
4092 hg import --exact proposed-fix.patch
4087
4093
4088 - use an external tool to apply a patch which is too fuzzy for
4094 - use an external tool to apply a patch which is too fuzzy for
4089 the default internal tool.
4095 the default internal tool.
4090
4096
4091 hg import --config ui.patch="patch --merge" fuzzy.patch
4097 hg import --config ui.patch="patch --merge" fuzzy.patch
4092
4098
4093 - change the default fuzzing from 2 to a less strict 7
4099 - change the default fuzzing from 2 to a less strict 7
4094
4100
4095 hg import --config ui.fuzz=7 fuzz.patch
4101 hg import --config ui.fuzz=7 fuzz.patch
4096
4102
4097 Returns 0 on success, 1 on partial success (see --partial).
4103 Returns 0 on success, 1 on partial success (see --partial).
4098 """
4104 """
4099
4105
4100 cmdutil.check_incompatible_arguments(
4106 cmdutil.check_incompatible_arguments(
4101 opts, 'no_commit', ['bypass', 'secret']
4107 opts, 'no_commit', ['bypass', 'secret']
4102 )
4108 )
4103 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4109 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4104 opts = pycompat.byteskwargs(opts)
4110 opts = pycompat.byteskwargs(opts)
4105 if not patch1:
4111 if not patch1:
4106 raise error.InputError(_(b'need at least one patch to import'))
4112 raise error.InputError(_(b'need at least one patch to import'))
4107
4113
4108 patches = (patch1,) + patches
4114 patches = (patch1,) + patches
4109
4115
4110 date = opts.get(b'date')
4116 date = opts.get(b'date')
4111 if date:
4117 if date:
4112 opts[b'date'] = dateutil.parsedate(date)
4118 opts[b'date'] = dateutil.parsedate(date)
4113
4119
4114 exact = opts.get(b'exact')
4120 exact = opts.get(b'exact')
4115 update = not opts.get(b'bypass')
4121 update = not opts.get(b'bypass')
4116 try:
4122 try:
4117 sim = float(opts.get(b'similarity') or 0)
4123 sim = float(opts.get(b'similarity') or 0)
4118 except ValueError:
4124 except ValueError:
4119 raise error.InputError(_(b'similarity must be a number'))
4125 raise error.InputError(_(b'similarity must be a number'))
4120 if sim < 0 or sim > 100:
4126 if sim < 0 or sim > 100:
4121 raise error.InputError(_(b'similarity must be between 0 and 100'))
4127 raise error.InputError(_(b'similarity must be between 0 and 100'))
4122 if sim and not update:
4128 if sim and not update:
4123 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4129 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4124
4130
4125 base = opts[b"base"]
4131 base = opts[b"base"]
4126 msgs = []
4132 msgs = []
4127 ret = 0
4133 ret = 0
4128
4134
4129 with repo.wlock():
4135 with repo.wlock():
4130 if update:
4136 if update:
4131 cmdutil.checkunfinished(repo)
4137 cmdutil.checkunfinished(repo)
4132 if exact or not opts.get(b'force'):
4138 if exact or not opts.get(b'force'):
4133 cmdutil.bailifchanged(repo)
4139 cmdutil.bailifchanged(repo)
4134
4140
4135 if not opts.get(b'no_commit'):
4141 if not opts.get(b'no_commit'):
4136 lock = repo.lock
4142 lock = repo.lock
4137 tr = lambda: repo.transaction(b'import')
4143 tr = lambda: repo.transaction(b'import')
4138 dsguard = util.nullcontextmanager
4144 dsguard = util.nullcontextmanager
4139 else:
4145 else:
4140 lock = util.nullcontextmanager
4146 lock = util.nullcontextmanager
4141 tr = util.nullcontextmanager
4147 tr = util.nullcontextmanager
4142 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4148 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4143 with lock(), tr(), dsguard():
4149 with lock(), tr(), dsguard():
4144 parents = repo[None].parents()
4150 parents = repo[None].parents()
4145 for patchurl in patches:
4151 for patchurl in patches:
4146 if patchurl == b'-':
4152 if patchurl == b'-':
4147 ui.status(_(b'applying patch from stdin\n'))
4153 ui.status(_(b'applying patch from stdin\n'))
4148 patchfile = ui.fin
4154 patchfile = ui.fin
4149 patchurl = b'stdin' # for error message
4155 patchurl = b'stdin' # for error message
4150 else:
4156 else:
4151 patchurl = os.path.join(base, patchurl)
4157 patchurl = os.path.join(base, patchurl)
4152 ui.status(_(b'applying %s\n') % patchurl)
4158 ui.status(_(b'applying %s\n') % patchurl)
4153 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4159 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4154
4160
4155 haspatch = False
4161 haspatch = False
4156 for hunk in patch.split(patchfile):
4162 for hunk in patch.split(patchfile):
4157 with patch.extract(ui, hunk) as patchdata:
4163 with patch.extract(ui, hunk) as patchdata:
4158 msg, node, rej = cmdutil.tryimportone(
4164 msg, node, rej = cmdutil.tryimportone(
4159 ui, repo, patchdata, parents, opts, msgs, hg.clean
4165 ui, repo, patchdata, parents, opts, msgs, hg.clean
4160 )
4166 )
4161 if msg:
4167 if msg:
4162 haspatch = True
4168 haspatch = True
4163 ui.note(msg + b'\n')
4169 ui.note(msg + b'\n')
4164 if update or exact:
4170 if update or exact:
4165 parents = repo[None].parents()
4171 parents = repo[None].parents()
4166 else:
4172 else:
4167 parents = [repo[node]]
4173 parents = [repo[node]]
4168 if rej:
4174 if rej:
4169 ui.write_err(_(b"patch applied partially\n"))
4175 ui.write_err(_(b"patch applied partially\n"))
4170 ui.write_err(
4176 ui.write_err(
4171 _(
4177 _(
4172 b"(fix the .rej files and run "
4178 b"(fix the .rej files and run "
4173 b"`hg commit --amend`)\n"
4179 b"`hg commit --amend`)\n"
4174 )
4180 )
4175 )
4181 )
4176 ret = 1
4182 ret = 1
4177 break
4183 break
4178
4184
4179 if not haspatch:
4185 if not haspatch:
4180 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4186 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4181
4187
4182 if msgs:
4188 if msgs:
4183 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4189 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4184 return ret
4190 return ret
4185
4191
4186
4192
4187 @command(
4193 @command(
4188 b'incoming|in',
4194 b'incoming|in',
4189 [
4195 [
4190 (
4196 (
4191 b'f',
4197 b'f',
4192 b'force',
4198 b'force',
4193 None,
4199 None,
4194 _(b'run even if remote repository is unrelated'),
4200 _(b'run even if remote repository is unrelated'),
4195 ),
4201 ),
4196 (b'n', b'newest-first', None, _(b'show newest record first')),
4202 (b'n', b'newest-first', None, _(b'show newest record first')),
4197 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4203 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4198 (
4204 (
4199 b'r',
4205 b'r',
4200 b'rev',
4206 b'rev',
4201 [],
4207 [],
4202 _(b'a remote changeset intended to be added'),
4208 _(b'a remote changeset intended to be added'),
4203 _(b'REV'),
4209 _(b'REV'),
4204 ),
4210 ),
4205 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4211 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4206 (
4212 (
4207 b'b',
4213 b'b',
4208 b'branch',
4214 b'branch',
4209 [],
4215 [],
4210 _(b'a specific branch you would like to pull'),
4216 _(b'a specific branch you would like to pull'),
4211 _(b'BRANCH'),
4217 _(b'BRANCH'),
4212 ),
4218 ),
4213 ]
4219 ]
4214 + logopts
4220 + logopts
4215 + remoteopts
4221 + remoteopts
4216 + subrepoopts,
4222 + subrepoopts,
4217 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4223 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4218 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4224 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4219 )
4225 )
4220 def incoming(ui, repo, source=b"default", **opts):
4226 def incoming(ui, repo, source=b"default", **opts):
4221 """show new changesets found in source
4227 """show new changesets found in source
4222
4228
4223 Show new changesets found in the specified path/URL or the default
4229 Show new changesets found in the specified path/URL or the default
4224 pull location. These are the changesets that would have been pulled
4230 pull location. These are the changesets that would have been pulled
4225 by :hg:`pull` at the time you issued this command.
4231 by :hg:`pull` at the time you issued this command.
4226
4232
4227 See pull for valid source format details.
4233 See pull for valid source format details.
4228
4234
4229 .. container:: verbose
4235 .. container:: verbose
4230
4236
4231 With -B/--bookmarks, the result of bookmark comparison between
4237 With -B/--bookmarks, the result of bookmark comparison between
4232 local and remote repositories is displayed. With -v/--verbose,
4238 local and remote repositories is displayed. With -v/--verbose,
4233 status is also displayed for each bookmark like below::
4239 status is also displayed for each bookmark like below::
4234
4240
4235 BM1 01234567890a added
4241 BM1 01234567890a added
4236 BM2 1234567890ab advanced
4242 BM2 1234567890ab advanced
4237 BM3 234567890abc diverged
4243 BM3 234567890abc diverged
4238 BM4 34567890abcd changed
4244 BM4 34567890abcd changed
4239
4245
4240 The action taken locally when pulling depends on the
4246 The action taken locally when pulling depends on the
4241 status of each bookmark:
4247 status of each bookmark:
4242
4248
4243 :``added``: pull will create it
4249 :``added``: pull will create it
4244 :``advanced``: pull will update it
4250 :``advanced``: pull will update it
4245 :``diverged``: pull will create a divergent bookmark
4251 :``diverged``: pull will create a divergent bookmark
4246 :``changed``: result depends on remote changesets
4252 :``changed``: result depends on remote changesets
4247
4253
4248 From the point of view of pulling behavior, bookmark
4254 From the point of view of pulling behavior, bookmark
4249 existing only in the remote repository are treated as ``added``,
4255 existing only in the remote repository are treated as ``added``,
4250 even if it is in fact locally deleted.
4256 even if it is in fact locally deleted.
4251
4257
4252 .. container:: verbose
4258 .. container:: verbose
4253
4259
4254 For remote repository, using --bundle avoids downloading the
4260 For remote repository, using --bundle avoids downloading the
4255 changesets twice if the incoming is followed by a pull.
4261 changesets twice if the incoming is followed by a pull.
4256
4262
4257 Examples:
4263 Examples:
4258
4264
4259 - show incoming changes with patches and full description::
4265 - show incoming changes with patches and full description::
4260
4266
4261 hg incoming -vp
4267 hg incoming -vp
4262
4268
4263 - show incoming changes excluding merges, store a bundle::
4269 - show incoming changes excluding merges, store a bundle::
4264
4270
4265 hg in -vpM --bundle incoming.hg
4271 hg in -vpM --bundle incoming.hg
4266 hg pull incoming.hg
4272 hg pull incoming.hg
4267
4273
4268 - briefly list changes inside a bundle::
4274 - briefly list changes inside a bundle::
4269
4275
4270 hg in changes.hg -T "{desc|firstline}\\n"
4276 hg in changes.hg -T "{desc|firstline}\\n"
4271
4277
4272 Returns 0 if there are incoming changes, 1 otherwise.
4278 Returns 0 if there are incoming changes, 1 otherwise.
4273 """
4279 """
4274 opts = pycompat.byteskwargs(opts)
4280 opts = pycompat.byteskwargs(opts)
4275 if opts.get(b'graph'):
4281 if opts.get(b'graph'):
4276 logcmdutil.checkunsupportedgraphflags([], opts)
4282 logcmdutil.checkunsupportedgraphflags([], opts)
4277
4283
4278 def display(other, chlist, displayer):
4284 def display(other, chlist, displayer):
4279 revdag = logcmdutil.graphrevs(other, chlist, opts)
4285 revdag = logcmdutil.graphrevs(other, chlist, opts)
4280 logcmdutil.displaygraph(
4286 logcmdutil.displaygraph(
4281 ui, repo, revdag, displayer, graphmod.asciiedges
4287 ui, repo, revdag, displayer, graphmod.asciiedges
4282 )
4288 )
4283
4289
4284 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4290 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4285 return 0
4291 return 0
4286
4292
4287 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4293 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4288
4294
4289 if opts.get(b'bookmarks'):
4295 if opts.get(b'bookmarks'):
4290 source, branches = hg.parseurl(
4296 source, branches = hg.parseurl(
4291 ui.expandpath(source), opts.get(b'branch')
4297 ui.expandpath(source), opts.get(b'branch')
4292 )
4298 )
4293 other = hg.peer(repo, opts, source)
4299 other = hg.peer(repo, opts, source)
4294 if b'bookmarks' not in other.listkeys(b'namespaces'):
4300 try:
4295 ui.warn(_(b"remote doesn't support bookmarks\n"))
4301 if b'bookmarks' not in other.listkeys(b'namespaces'):
4296 return 0
4302 ui.warn(_(b"remote doesn't support bookmarks\n"))
4297 ui.pager(b'incoming')
4303 return 0
4298 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4304 ui.pager(b'incoming')
4299 return bookmarks.incoming(ui, repo, other)
4305 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4306 return bookmarks.incoming(ui, repo, other)
4307 finally:
4308 other.close()
4300
4309
4301 repo._subtoppath = ui.expandpath(source)
4310 repo._subtoppath = ui.expandpath(source)
4302 try:
4311 try:
4303 return hg.incoming(ui, repo, source, opts)
4312 return hg.incoming(ui, repo, source, opts)
4304 finally:
4313 finally:
4305 del repo._subtoppath
4314 del repo._subtoppath
4306
4315
4307
4316
4308 @command(
4317 @command(
4309 b'init',
4318 b'init',
4310 remoteopts,
4319 remoteopts,
4311 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4320 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4312 helpcategory=command.CATEGORY_REPO_CREATION,
4321 helpcategory=command.CATEGORY_REPO_CREATION,
4313 helpbasic=True,
4322 helpbasic=True,
4314 norepo=True,
4323 norepo=True,
4315 )
4324 )
4316 def init(ui, dest=b".", **opts):
4325 def init(ui, dest=b".", **opts):
4317 """create a new repository in the given directory
4326 """create a new repository in the given directory
4318
4327
4319 Initialize a new repository in the given directory. If the given
4328 Initialize a new repository in the given directory. If the given
4320 directory does not exist, it will be created.
4329 directory does not exist, it will be created.
4321
4330
4322 If no directory is given, the current directory is used.
4331 If no directory is given, the current directory is used.
4323
4332
4324 It is possible to specify an ``ssh://`` URL as the destination.
4333 It is possible to specify an ``ssh://`` URL as the destination.
4325 See :hg:`help urls` for more information.
4334 See :hg:`help urls` for more information.
4326
4335
4327 Returns 0 on success.
4336 Returns 0 on success.
4328 """
4337 """
4329 opts = pycompat.byteskwargs(opts)
4338 opts = pycompat.byteskwargs(opts)
4330 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4339 peer = hg.peer(ui, opts, ui.expandpath(dest), create=True)
4340 peer.close()
4331
4341
4332
4342
4333 @command(
4343 @command(
4334 b'locate',
4344 b'locate',
4335 [
4345 [
4336 (
4346 (
4337 b'r',
4347 b'r',
4338 b'rev',
4348 b'rev',
4339 b'',
4349 b'',
4340 _(b'search the repository as it is in REV'),
4350 _(b'search the repository as it is in REV'),
4341 _(b'REV'),
4351 _(b'REV'),
4342 ),
4352 ),
4343 (
4353 (
4344 b'0',
4354 b'0',
4345 b'print0',
4355 b'print0',
4346 None,
4356 None,
4347 _(b'end filenames with NUL, for use with xargs'),
4357 _(b'end filenames with NUL, for use with xargs'),
4348 ),
4358 ),
4349 (
4359 (
4350 b'f',
4360 b'f',
4351 b'fullpath',
4361 b'fullpath',
4352 None,
4362 None,
4353 _(b'print complete paths from the filesystem root'),
4363 _(b'print complete paths from the filesystem root'),
4354 ),
4364 ),
4355 ]
4365 ]
4356 + walkopts,
4366 + walkopts,
4357 _(b'[OPTION]... [PATTERN]...'),
4367 _(b'[OPTION]... [PATTERN]...'),
4358 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4368 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4359 )
4369 )
4360 def locate(ui, repo, *pats, **opts):
4370 def locate(ui, repo, *pats, **opts):
4361 """locate files matching specific patterns (DEPRECATED)
4371 """locate files matching specific patterns (DEPRECATED)
4362
4372
4363 Print files under Mercurial control in the working directory whose
4373 Print files under Mercurial control in the working directory whose
4364 names match the given patterns.
4374 names match the given patterns.
4365
4375
4366 By default, this command searches all directories in the working
4376 By default, this command searches all directories in the working
4367 directory. To search just the current directory and its
4377 directory. To search just the current directory and its
4368 subdirectories, use "--include .".
4378 subdirectories, use "--include .".
4369
4379
4370 If no patterns are given to match, this command prints the names
4380 If no patterns are given to match, this command prints the names
4371 of all files under Mercurial control in the working directory.
4381 of all files under Mercurial control in the working directory.
4372
4382
4373 If you want to feed the output of this command into the "xargs"
4383 If you want to feed the output of this command into the "xargs"
4374 command, use the -0 option to both this command and "xargs". This
4384 command, use the -0 option to both this command and "xargs". This
4375 will avoid the problem of "xargs" treating single filenames that
4385 will avoid the problem of "xargs" treating single filenames that
4376 contain whitespace as multiple filenames.
4386 contain whitespace as multiple filenames.
4377
4387
4378 See :hg:`help files` for a more versatile command.
4388 See :hg:`help files` for a more versatile command.
4379
4389
4380 Returns 0 if a match is found, 1 otherwise.
4390 Returns 0 if a match is found, 1 otherwise.
4381 """
4391 """
4382 opts = pycompat.byteskwargs(opts)
4392 opts = pycompat.byteskwargs(opts)
4383 if opts.get(b'print0'):
4393 if opts.get(b'print0'):
4384 end = b'\0'
4394 end = b'\0'
4385 else:
4395 else:
4386 end = b'\n'
4396 end = b'\n'
4387 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4397 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4388
4398
4389 ret = 1
4399 ret = 1
4390 m = scmutil.match(
4400 m = scmutil.match(
4391 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4401 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4392 )
4402 )
4393
4403
4394 ui.pager(b'locate')
4404 ui.pager(b'locate')
4395 if ctx.rev() is None:
4405 if ctx.rev() is None:
4396 # When run on the working copy, "locate" includes removed files, so
4406 # When run on the working copy, "locate" includes removed files, so
4397 # we get the list of files from the dirstate.
4407 # we get the list of files from the dirstate.
4398 filesgen = sorted(repo.dirstate.matches(m))
4408 filesgen = sorted(repo.dirstate.matches(m))
4399 else:
4409 else:
4400 filesgen = ctx.matches(m)
4410 filesgen = ctx.matches(m)
4401 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4411 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4402 for abs in filesgen:
4412 for abs in filesgen:
4403 if opts.get(b'fullpath'):
4413 if opts.get(b'fullpath'):
4404 ui.write(repo.wjoin(abs), end)
4414 ui.write(repo.wjoin(abs), end)
4405 else:
4415 else:
4406 ui.write(uipathfn(abs), end)
4416 ui.write(uipathfn(abs), end)
4407 ret = 0
4417 ret = 0
4408
4418
4409 return ret
4419 return ret
4410
4420
4411
4421
4412 @command(
4422 @command(
4413 b'log|history',
4423 b'log|history',
4414 [
4424 [
4415 (
4425 (
4416 b'f',
4426 b'f',
4417 b'follow',
4427 b'follow',
4418 None,
4428 None,
4419 _(
4429 _(
4420 b'follow changeset history, or file history across copies and renames'
4430 b'follow changeset history, or file history across copies and renames'
4421 ),
4431 ),
4422 ),
4432 ),
4423 (
4433 (
4424 b'',
4434 b'',
4425 b'follow-first',
4435 b'follow-first',
4426 None,
4436 None,
4427 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4437 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4428 ),
4438 ),
4429 (
4439 (
4430 b'd',
4440 b'd',
4431 b'date',
4441 b'date',
4432 b'',
4442 b'',
4433 _(b'show revisions matching date spec'),
4443 _(b'show revisions matching date spec'),
4434 _(b'DATE'),
4444 _(b'DATE'),
4435 ),
4445 ),
4436 (b'C', b'copies', None, _(b'show copied files')),
4446 (b'C', b'copies', None, _(b'show copied files')),
4437 (
4447 (
4438 b'k',
4448 b'k',
4439 b'keyword',
4449 b'keyword',
4440 [],
4450 [],
4441 _(b'do case-insensitive search for a given text'),
4451 _(b'do case-insensitive search for a given text'),
4442 _(b'TEXT'),
4452 _(b'TEXT'),
4443 ),
4453 ),
4444 (
4454 (
4445 b'r',
4455 b'r',
4446 b'rev',
4456 b'rev',
4447 [],
4457 [],
4448 _(b'revisions to select or follow from'),
4458 _(b'revisions to select or follow from'),
4449 _(b'REV'),
4459 _(b'REV'),
4450 ),
4460 ),
4451 (
4461 (
4452 b'L',
4462 b'L',
4453 b'line-range',
4463 b'line-range',
4454 [],
4464 [],
4455 _(b'follow line range of specified file (EXPERIMENTAL)'),
4465 _(b'follow line range of specified file (EXPERIMENTAL)'),
4456 _(b'FILE,RANGE'),
4466 _(b'FILE,RANGE'),
4457 ),
4467 ),
4458 (
4468 (
4459 b'',
4469 b'',
4460 b'removed',
4470 b'removed',
4461 None,
4471 None,
4462 _(b'include revisions where files were removed'),
4472 _(b'include revisions where files were removed'),
4463 ),
4473 ),
4464 (
4474 (
4465 b'm',
4475 b'm',
4466 b'only-merges',
4476 b'only-merges',
4467 None,
4477 None,
4468 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4478 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4469 ),
4479 ),
4470 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4480 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4471 (
4481 (
4472 b'',
4482 b'',
4473 b'only-branch',
4483 b'only-branch',
4474 [],
4484 [],
4475 _(
4485 _(
4476 b'show only changesets within the given named branch (DEPRECATED)'
4486 b'show only changesets within the given named branch (DEPRECATED)'
4477 ),
4487 ),
4478 _(b'BRANCH'),
4488 _(b'BRANCH'),
4479 ),
4489 ),
4480 (
4490 (
4481 b'b',
4491 b'b',
4482 b'branch',
4492 b'branch',
4483 [],
4493 [],
4484 _(b'show changesets within the given named branch'),
4494 _(b'show changesets within the given named branch'),
4485 _(b'BRANCH'),
4495 _(b'BRANCH'),
4486 ),
4496 ),
4487 (
4497 (
4488 b'B',
4498 b'B',
4489 b'bookmark',
4499 b'bookmark',
4490 [],
4500 [],
4491 _(b"show changesets within the given bookmark"),
4501 _(b"show changesets within the given bookmark"),
4492 _(b'BOOKMARK'),
4502 _(b'BOOKMARK'),
4493 ),
4503 ),
4494 (
4504 (
4495 b'P',
4505 b'P',
4496 b'prune',
4506 b'prune',
4497 [],
4507 [],
4498 _(b'do not display revision or any of its ancestors'),
4508 _(b'do not display revision or any of its ancestors'),
4499 _(b'REV'),
4509 _(b'REV'),
4500 ),
4510 ),
4501 ]
4511 ]
4502 + logopts
4512 + logopts
4503 + walkopts,
4513 + walkopts,
4504 _(b'[OPTION]... [FILE]'),
4514 _(b'[OPTION]... [FILE]'),
4505 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4515 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4506 helpbasic=True,
4516 helpbasic=True,
4507 inferrepo=True,
4517 inferrepo=True,
4508 intents={INTENT_READONLY},
4518 intents={INTENT_READONLY},
4509 )
4519 )
4510 def log(ui, repo, *pats, **opts):
4520 def log(ui, repo, *pats, **opts):
4511 """show revision history of entire repository or files
4521 """show revision history of entire repository or files
4512
4522
4513 Print the revision history of the specified files or the entire
4523 Print the revision history of the specified files or the entire
4514 project.
4524 project.
4515
4525
4516 If no revision range is specified, the default is ``tip:0`` unless
4526 If no revision range is specified, the default is ``tip:0`` unless
4517 --follow is set.
4527 --follow is set.
4518
4528
4519 File history is shown without following rename or copy history of
4529 File history is shown without following rename or copy history of
4520 files. Use -f/--follow with a filename to follow history across
4530 files. Use -f/--follow with a filename to follow history across
4521 renames and copies. --follow without a filename will only show
4531 renames and copies. --follow without a filename will only show
4522 ancestors of the starting revisions. The starting revisions can be
4532 ancestors of the starting revisions. The starting revisions can be
4523 specified by -r/--rev, which default to the working directory parent.
4533 specified by -r/--rev, which default to the working directory parent.
4524
4534
4525 By default this command prints revision number and changeset id,
4535 By default this command prints revision number and changeset id,
4526 tags, non-trivial parents, user, date and time, and a summary for
4536 tags, non-trivial parents, user, date and time, and a summary for
4527 each commit. When the -v/--verbose switch is used, the list of
4537 each commit. When the -v/--verbose switch is used, the list of
4528 changed files and full commit message are shown.
4538 changed files and full commit message are shown.
4529
4539
4530 With --graph the revisions are shown as an ASCII art DAG with the most
4540 With --graph the revisions are shown as an ASCII art DAG with the most
4531 recent changeset at the top.
4541 recent changeset at the top.
4532 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4542 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4533 involved in an unresolved merge conflict, '_' closes a branch,
4543 involved in an unresolved merge conflict, '_' closes a branch,
4534 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4544 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4535 changeset from the lines below is a parent of the 'o' merge on the same
4545 changeset from the lines below is a parent of the 'o' merge on the same
4536 line.
4546 line.
4537 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4547 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4538 of a '|' indicates one or more revisions in a path are omitted.
4548 of a '|' indicates one or more revisions in a path are omitted.
4539
4549
4540 .. container:: verbose
4550 .. container:: verbose
4541
4551
4542 Use -L/--line-range FILE,M:N options to follow the history of lines
4552 Use -L/--line-range FILE,M:N options to follow the history of lines
4543 from M to N in FILE. With -p/--patch only diff hunks affecting
4553 from M to N in FILE. With -p/--patch only diff hunks affecting
4544 specified line range will be shown. This option requires --follow;
4554 specified line range will be shown. This option requires --follow;
4545 it can be specified multiple times. Currently, this option is not
4555 it can be specified multiple times. Currently, this option is not
4546 compatible with --graph. This option is experimental.
4556 compatible with --graph. This option is experimental.
4547
4557
4548 .. note::
4558 .. note::
4549
4559
4550 :hg:`log --patch` may generate unexpected diff output for merge
4560 :hg:`log --patch` may generate unexpected diff output for merge
4551 changesets, as it will only compare the merge changeset against
4561 changesets, as it will only compare the merge changeset against
4552 its first parent. Also, only files different from BOTH parents
4562 its first parent. Also, only files different from BOTH parents
4553 will appear in files:.
4563 will appear in files:.
4554
4564
4555 .. note::
4565 .. note::
4556
4566
4557 For performance reasons, :hg:`log FILE` may omit duplicate changes
4567 For performance reasons, :hg:`log FILE` may omit duplicate changes
4558 made on branches and will not show removals or mode changes. To
4568 made on branches and will not show removals or mode changes. To
4559 see all such changes, use the --removed switch.
4569 see all such changes, use the --removed switch.
4560
4570
4561 .. container:: verbose
4571 .. container:: verbose
4562
4572
4563 .. note::
4573 .. note::
4564
4574
4565 The history resulting from -L/--line-range options depends on diff
4575 The history resulting from -L/--line-range options depends on diff
4566 options; for instance if white-spaces are ignored, respective changes
4576 options; for instance if white-spaces are ignored, respective changes
4567 with only white-spaces in specified line range will not be listed.
4577 with only white-spaces in specified line range will not be listed.
4568
4578
4569 .. container:: verbose
4579 .. container:: verbose
4570
4580
4571 Some examples:
4581 Some examples:
4572
4582
4573 - changesets with full descriptions and file lists::
4583 - changesets with full descriptions and file lists::
4574
4584
4575 hg log -v
4585 hg log -v
4576
4586
4577 - changesets ancestral to the working directory::
4587 - changesets ancestral to the working directory::
4578
4588
4579 hg log -f
4589 hg log -f
4580
4590
4581 - last 10 commits on the current branch::
4591 - last 10 commits on the current branch::
4582
4592
4583 hg log -l 10 -b .
4593 hg log -l 10 -b .
4584
4594
4585 - changesets showing all modifications of a file, including removals::
4595 - changesets showing all modifications of a file, including removals::
4586
4596
4587 hg log --removed file.c
4597 hg log --removed file.c
4588
4598
4589 - all changesets that touch a directory, with diffs, excluding merges::
4599 - all changesets that touch a directory, with diffs, excluding merges::
4590
4600
4591 hg log -Mp lib/
4601 hg log -Mp lib/
4592
4602
4593 - all revision numbers that match a keyword::
4603 - all revision numbers that match a keyword::
4594
4604
4595 hg log -k bug --template "{rev}\\n"
4605 hg log -k bug --template "{rev}\\n"
4596
4606
4597 - the full hash identifier of the working directory parent::
4607 - the full hash identifier of the working directory parent::
4598
4608
4599 hg log -r . --template "{node}\\n"
4609 hg log -r . --template "{node}\\n"
4600
4610
4601 - list available log templates::
4611 - list available log templates::
4602
4612
4603 hg log -T list
4613 hg log -T list
4604
4614
4605 - check if a given changeset is included in a tagged release::
4615 - check if a given changeset is included in a tagged release::
4606
4616
4607 hg log -r "a21ccf and ancestor(1.9)"
4617 hg log -r "a21ccf and ancestor(1.9)"
4608
4618
4609 - find all changesets by some user in a date range::
4619 - find all changesets by some user in a date range::
4610
4620
4611 hg log -k alice -d "may 2008 to jul 2008"
4621 hg log -k alice -d "may 2008 to jul 2008"
4612
4622
4613 - summary of all changesets after the last tag::
4623 - summary of all changesets after the last tag::
4614
4624
4615 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4625 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4616
4626
4617 - changesets touching lines 13 to 23 for file.c::
4627 - changesets touching lines 13 to 23 for file.c::
4618
4628
4619 hg log -L file.c,13:23
4629 hg log -L file.c,13:23
4620
4630
4621 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4631 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4622 main.c with patch::
4632 main.c with patch::
4623
4633
4624 hg log -L file.c,13:23 -L main.c,2:6 -p
4634 hg log -L file.c,13:23 -L main.c,2:6 -p
4625
4635
4626 See :hg:`help dates` for a list of formats valid for -d/--date.
4636 See :hg:`help dates` for a list of formats valid for -d/--date.
4627
4637
4628 See :hg:`help revisions` for more about specifying and ordering
4638 See :hg:`help revisions` for more about specifying and ordering
4629 revisions.
4639 revisions.
4630
4640
4631 See :hg:`help templates` for more about pre-packaged styles and
4641 See :hg:`help templates` for more about pre-packaged styles and
4632 specifying custom templates. The default template used by the log
4642 specifying custom templates. The default template used by the log
4633 command can be customized via the ``command-templates.log`` configuration
4643 command can be customized via the ``command-templates.log`` configuration
4634 setting.
4644 setting.
4635
4645
4636 Returns 0 on success.
4646 Returns 0 on success.
4637
4647
4638 """
4648 """
4639 opts = pycompat.byteskwargs(opts)
4649 opts = pycompat.byteskwargs(opts)
4640 linerange = opts.get(b'line_range')
4650 linerange = opts.get(b'line_range')
4641
4651
4642 if linerange and not opts.get(b'follow'):
4652 if linerange and not opts.get(b'follow'):
4643 raise error.InputError(_(b'--line-range requires --follow'))
4653 raise error.InputError(_(b'--line-range requires --follow'))
4644
4654
4645 if linerange and pats:
4655 if linerange and pats:
4646 # TODO: take pats as patterns with no line-range filter
4656 # TODO: take pats as patterns with no line-range filter
4647 raise error.InputError(
4657 raise error.InputError(
4648 _(b'FILE arguments are not compatible with --line-range option')
4658 _(b'FILE arguments are not compatible with --line-range option')
4649 )
4659 )
4650
4660
4651 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4661 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4652 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4662 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4653 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4663 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4654 if linerange:
4664 if linerange:
4655 # TODO: should follow file history from logcmdutil._initialrevs(),
4665 # TODO: should follow file history from logcmdutil._initialrevs(),
4656 # then filter the result by logcmdutil._makerevset() and --limit
4666 # then filter the result by logcmdutil._makerevset() and --limit
4657 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4667 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4658
4668
4659 getcopies = None
4669 getcopies = None
4660 if opts.get(b'copies'):
4670 if opts.get(b'copies'):
4661 endrev = None
4671 endrev = None
4662 if revs:
4672 if revs:
4663 endrev = revs.max() + 1
4673 endrev = revs.max() + 1
4664 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4674 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4665
4675
4666 ui.pager(b'log')
4676 ui.pager(b'log')
4667 displayer = logcmdutil.changesetdisplayer(
4677 displayer = logcmdutil.changesetdisplayer(
4668 ui, repo, opts, differ, buffered=True
4678 ui, repo, opts, differ, buffered=True
4669 )
4679 )
4670 if opts.get(b'graph'):
4680 if opts.get(b'graph'):
4671 displayfn = logcmdutil.displaygraphrevs
4681 displayfn = logcmdutil.displaygraphrevs
4672 else:
4682 else:
4673 displayfn = logcmdutil.displayrevs
4683 displayfn = logcmdutil.displayrevs
4674 displayfn(ui, repo, revs, displayer, getcopies)
4684 displayfn(ui, repo, revs, displayer, getcopies)
4675
4685
4676
4686
4677 @command(
4687 @command(
4678 b'manifest',
4688 b'manifest',
4679 [
4689 [
4680 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4690 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4681 (b'', b'all', False, _(b"list files from all revisions")),
4691 (b'', b'all', False, _(b"list files from all revisions")),
4682 ]
4692 ]
4683 + formatteropts,
4693 + formatteropts,
4684 _(b'[-r REV]'),
4694 _(b'[-r REV]'),
4685 helpcategory=command.CATEGORY_MAINTENANCE,
4695 helpcategory=command.CATEGORY_MAINTENANCE,
4686 intents={INTENT_READONLY},
4696 intents={INTENT_READONLY},
4687 )
4697 )
4688 def manifest(ui, repo, node=None, rev=None, **opts):
4698 def manifest(ui, repo, node=None, rev=None, **opts):
4689 """output the current or given revision of the project manifest
4699 """output the current or given revision of the project manifest
4690
4700
4691 Print a list of version controlled files for the given revision.
4701 Print a list of version controlled files for the given revision.
4692 If no revision is given, the first parent of the working directory
4702 If no revision is given, the first parent of the working directory
4693 is used, or the null revision if no revision is checked out.
4703 is used, or the null revision if no revision is checked out.
4694
4704
4695 With -v, print file permissions, symlink and executable bits.
4705 With -v, print file permissions, symlink and executable bits.
4696 With --debug, print file revision hashes.
4706 With --debug, print file revision hashes.
4697
4707
4698 If option --all is specified, the list of all files from all revisions
4708 If option --all is specified, the list of all files from all revisions
4699 is printed. This includes deleted and renamed files.
4709 is printed. This includes deleted and renamed files.
4700
4710
4701 Returns 0 on success.
4711 Returns 0 on success.
4702 """
4712 """
4703 opts = pycompat.byteskwargs(opts)
4713 opts = pycompat.byteskwargs(opts)
4704 fm = ui.formatter(b'manifest', opts)
4714 fm = ui.formatter(b'manifest', opts)
4705
4715
4706 if opts.get(b'all'):
4716 if opts.get(b'all'):
4707 if rev or node:
4717 if rev or node:
4708 raise error.InputError(_(b"can't specify a revision with --all"))
4718 raise error.InputError(_(b"can't specify a revision with --all"))
4709
4719
4710 res = set()
4720 res = set()
4711 for rev in repo:
4721 for rev in repo:
4712 ctx = repo[rev]
4722 ctx = repo[rev]
4713 res |= set(ctx.files())
4723 res |= set(ctx.files())
4714
4724
4715 ui.pager(b'manifest')
4725 ui.pager(b'manifest')
4716 for f in sorted(res):
4726 for f in sorted(res):
4717 fm.startitem()
4727 fm.startitem()
4718 fm.write(b"path", b'%s\n', f)
4728 fm.write(b"path", b'%s\n', f)
4719 fm.end()
4729 fm.end()
4720 return
4730 return
4721
4731
4722 if rev and node:
4732 if rev and node:
4723 raise error.InputError(_(b"please specify just one revision"))
4733 raise error.InputError(_(b"please specify just one revision"))
4724
4734
4725 if not node:
4735 if not node:
4726 node = rev
4736 node = rev
4727
4737
4728 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4738 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4729 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4739 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4730 if node:
4740 if node:
4731 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4741 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4732 ctx = scmutil.revsingle(repo, node)
4742 ctx = scmutil.revsingle(repo, node)
4733 mf = ctx.manifest()
4743 mf = ctx.manifest()
4734 ui.pager(b'manifest')
4744 ui.pager(b'manifest')
4735 for f in ctx:
4745 for f in ctx:
4736 fm.startitem()
4746 fm.startitem()
4737 fm.context(ctx=ctx)
4747 fm.context(ctx=ctx)
4738 fl = ctx[f].flags()
4748 fl = ctx[f].flags()
4739 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4749 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4740 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4750 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4741 fm.write(b'path', b'%s\n', f)
4751 fm.write(b'path', b'%s\n', f)
4742 fm.end()
4752 fm.end()
4743
4753
4744
4754
4745 @command(
4755 @command(
4746 b'merge',
4756 b'merge',
4747 [
4757 [
4748 (
4758 (
4749 b'f',
4759 b'f',
4750 b'force',
4760 b'force',
4751 None,
4761 None,
4752 _(b'force a merge including outstanding changes (DEPRECATED)'),
4762 _(b'force a merge including outstanding changes (DEPRECATED)'),
4753 ),
4763 ),
4754 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4764 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4755 (
4765 (
4756 b'P',
4766 b'P',
4757 b'preview',
4767 b'preview',
4758 None,
4768 None,
4759 _(b'review revisions to merge (no merge is performed)'),
4769 _(b'review revisions to merge (no merge is performed)'),
4760 ),
4770 ),
4761 (b'', b'abort', None, _(b'abort the ongoing merge')),
4771 (b'', b'abort', None, _(b'abort the ongoing merge')),
4762 ]
4772 ]
4763 + mergetoolopts,
4773 + mergetoolopts,
4764 _(b'[-P] [[-r] REV]'),
4774 _(b'[-P] [[-r] REV]'),
4765 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4775 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4766 helpbasic=True,
4776 helpbasic=True,
4767 )
4777 )
4768 def merge(ui, repo, node=None, **opts):
4778 def merge(ui, repo, node=None, **opts):
4769 """merge another revision into working directory
4779 """merge another revision into working directory
4770
4780
4771 The current working directory is updated with all changes made in
4781 The current working directory is updated with all changes made in
4772 the requested revision since the last common predecessor revision.
4782 the requested revision since the last common predecessor revision.
4773
4783
4774 Files that changed between either parent are marked as changed for
4784 Files that changed between either parent are marked as changed for
4775 the next commit and a commit must be performed before any further
4785 the next commit and a commit must be performed before any further
4776 updates to the repository are allowed. The next commit will have
4786 updates to the repository are allowed. The next commit will have
4777 two parents.
4787 two parents.
4778
4788
4779 ``--tool`` can be used to specify the merge tool used for file
4789 ``--tool`` can be used to specify the merge tool used for file
4780 merges. It overrides the HGMERGE environment variable and your
4790 merges. It overrides the HGMERGE environment variable and your
4781 configuration files. See :hg:`help merge-tools` for options.
4791 configuration files. See :hg:`help merge-tools` for options.
4782
4792
4783 If no revision is specified, the working directory's parent is a
4793 If no revision is specified, the working directory's parent is a
4784 head revision, and the current branch contains exactly one other
4794 head revision, and the current branch contains exactly one other
4785 head, the other head is merged with by default. Otherwise, an
4795 head, the other head is merged with by default. Otherwise, an
4786 explicit revision with which to merge must be provided.
4796 explicit revision with which to merge must be provided.
4787
4797
4788 See :hg:`help resolve` for information on handling file conflicts.
4798 See :hg:`help resolve` for information on handling file conflicts.
4789
4799
4790 To undo an uncommitted merge, use :hg:`merge --abort` which
4800 To undo an uncommitted merge, use :hg:`merge --abort` which
4791 will check out a clean copy of the original merge parent, losing
4801 will check out a clean copy of the original merge parent, losing
4792 all changes.
4802 all changes.
4793
4803
4794 Returns 0 on success, 1 if there are unresolved files.
4804 Returns 0 on success, 1 if there are unresolved files.
4795 """
4805 """
4796
4806
4797 opts = pycompat.byteskwargs(opts)
4807 opts = pycompat.byteskwargs(opts)
4798 abort = opts.get(b'abort')
4808 abort = opts.get(b'abort')
4799 if abort and repo.dirstate.p2() == nullid:
4809 if abort and repo.dirstate.p2() == nullid:
4800 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4810 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4801 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4811 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4802 if abort:
4812 if abort:
4803 state = cmdutil.getunfinishedstate(repo)
4813 state = cmdutil.getunfinishedstate(repo)
4804 if state and state._opname != b'merge':
4814 if state and state._opname != b'merge':
4805 raise error.StateError(
4815 raise error.StateError(
4806 _(b'cannot abort merge with %s in progress') % (state._opname),
4816 _(b'cannot abort merge with %s in progress') % (state._opname),
4807 hint=state.hint(),
4817 hint=state.hint(),
4808 )
4818 )
4809 if node:
4819 if node:
4810 raise error.InputError(_(b"cannot specify a node with --abort"))
4820 raise error.InputError(_(b"cannot specify a node with --abort"))
4811 return hg.abortmerge(repo.ui, repo)
4821 return hg.abortmerge(repo.ui, repo)
4812
4822
4813 if opts.get(b'rev') and node:
4823 if opts.get(b'rev') and node:
4814 raise error.InputError(_(b"please specify just one revision"))
4824 raise error.InputError(_(b"please specify just one revision"))
4815 if not node:
4825 if not node:
4816 node = opts.get(b'rev')
4826 node = opts.get(b'rev')
4817
4827
4818 if node:
4828 if node:
4819 ctx = scmutil.revsingle(repo, node)
4829 ctx = scmutil.revsingle(repo, node)
4820 else:
4830 else:
4821 if ui.configbool(b'commands', b'merge.require-rev'):
4831 if ui.configbool(b'commands', b'merge.require-rev'):
4822 raise error.InputError(
4832 raise error.InputError(
4823 _(
4833 _(
4824 b'configuration requires specifying revision to merge '
4834 b'configuration requires specifying revision to merge '
4825 b'with'
4835 b'with'
4826 )
4836 )
4827 )
4837 )
4828 ctx = repo[destutil.destmerge(repo)]
4838 ctx = repo[destutil.destmerge(repo)]
4829
4839
4830 if ctx.node() is None:
4840 if ctx.node() is None:
4831 raise error.InputError(
4841 raise error.InputError(
4832 _(b'merging with the working copy has no effect')
4842 _(b'merging with the working copy has no effect')
4833 )
4843 )
4834
4844
4835 if opts.get(b'preview'):
4845 if opts.get(b'preview'):
4836 # find nodes that are ancestors of p2 but not of p1
4846 # find nodes that are ancestors of p2 but not of p1
4837 p1 = repo[b'.'].node()
4847 p1 = repo[b'.'].node()
4838 p2 = ctx.node()
4848 p2 = ctx.node()
4839 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4849 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4840
4850
4841 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4851 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4842 for node in nodes:
4852 for node in nodes:
4843 displayer.show(repo[node])
4853 displayer.show(repo[node])
4844 displayer.close()
4854 displayer.close()
4845 return 0
4855 return 0
4846
4856
4847 # ui.forcemerge is an internal variable, do not document
4857 # ui.forcemerge is an internal variable, do not document
4848 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4858 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4849 with ui.configoverride(overrides, b'merge'):
4859 with ui.configoverride(overrides, b'merge'):
4850 force = opts.get(b'force')
4860 force = opts.get(b'force')
4851 labels = [b'working copy', b'merge rev']
4861 labels = [b'working copy', b'merge rev']
4852 return hg.merge(ctx, force=force, labels=labels)
4862 return hg.merge(ctx, force=force, labels=labels)
4853
4863
4854
4864
4855 statemod.addunfinished(
4865 statemod.addunfinished(
4856 b'merge',
4866 b'merge',
4857 fname=None,
4867 fname=None,
4858 clearable=True,
4868 clearable=True,
4859 allowcommit=True,
4869 allowcommit=True,
4860 cmdmsg=_(b'outstanding uncommitted merge'),
4870 cmdmsg=_(b'outstanding uncommitted merge'),
4861 abortfunc=hg.abortmerge,
4871 abortfunc=hg.abortmerge,
4862 statushint=_(
4872 statushint=_(
4863 b'To continue: hg commit\nTo abort: hg merge --abort'
4873 b'To continue: hg commit\nTo abort: hg merge --abort'
4864 ),
4874 ),
4865 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4875 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4866 )
4876 )
4867
4877
4868
4878
4869 @command(
4879 @command(
4870 b'outgoing|out',
4880 b'outgoing|out',
4871 [
4881 [
4872 (
4882 (
4873 b'f',
4883 b'f',
4874 b'force',
4884 b'force',
4875 None,
4885 None,
4876 _(b'run even when the destination is unrelated'),
4886 _(b'run even when the destination is unrelated'),
4877 ),
4887 ),
4878 (
4888 (
4879 b'r',
4889 b'r',
4880 b'rev',
4890 b'rev',
4881 [],
4891 [],
4882 _(b'a changeset intended to be included in the destination'),
4892 _(b'a changeset intended to be included in the destination'),
4883 _(b'REV'),
4893 _(b'REV'),
4884 ),
4894 ),
4885 (b'n', b'newest-first', None, _(b'show newest record first')),
4895 (b'n', b'newest-first', None, _(b'show newest record first')),
4886 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4896 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4887 (
4897 (
4888 b'b',
4898 b'b',
4889 b'branch',
4899 b'branch',
4890 [],
4900 [],
4891 _(b'a specific branch you would like to push'),
4901 _(b'a specific branch you would like to push'),
4892 _(b'BRANCH'),
4902 _(b'BRANCH'),
4893 ),
4903 ),
4894 ]
4904 ]
4895 + logopts
4905 + logopts
4896 + remoteopts
4906 + remoteopts
4897 + subrepoopts,
4907 + subrepoopts,
4898 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4908 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4899 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4909 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4900 )
4910 )
4901 def outgoing(ui, repo, dest=None, **opts):
4911 def outgoing(ui, repo, dest=None, **opts):
4902 """show changesets not found in the destination
4912 """show changesets not found in the destination
4903
4913
4904 Show changesets not found in the specified destination repository
4914 Show changesets not found in the specified destination repository
4905 or the default push location. These are the changesets that would
4915 or the default push location. These are the changesets that would
4906 be pushed if a push was requested.
4916 be pushed if a push was requested.
4907
4917
4908 See pull for details of valid destination formats.
4918 See pull for details of valid destination formats.
4909
4919
4910 .. container:: verbose
4920 .. container:: verbose
4911
4921
4912 With -B/--bookmarks, the result of bookmark comparison between
4922 With -B/--bookmarks, the result of bookmark comparison between
4913 local and remote repositories is displayed. With -v/--verbose,
4923 local and remote repositories is displayed. With -v/--verbose,
4914 status is also displayed for each bookmark like below::
4924 status is also displayed for each bookmark like below::
4915
4925
4916 BM1 01234567890a added
4926 BM1 01234567890a added
4917 BM2 deleted
4927 BM2 deleted
4918 BM3 234567890abc advanced
4928 BM3 234567890abc advanced
4919 BM4 34567890abcd diverged
4929 BM4 34567890abcd diverged
4920 BM5 4567890abcde changed
4930 BM5 4567890abcde changed
4921
4931
4922 The action taken when pushing depends on the
4932 The action taken when pushing depends on the
4923 status of each bookmark:
4933 status of each bookmark:
4924
4934
4925 :``added``: push with ``-B`` will create it
4935 :``added``: push with ``-B`` will create it
4926 :``deleted``: push with ``-B`` will delete it
4936 :``deleted``: push with ``-B`` will delete it
4927 :``advanced``: push will update it
4937 :``advanced``: push will update it
4928 :``diverged``: push with ``-B`` will update it
4938 :``diverged``: push with ``-B`` will update it
4929 :``changed``: push with ``-B`` will update it
4939 :``changed``: push with ``-B`` will update it
4930
4940
4931 From the point of view of pushing behavior, bookmarks
4941 From the point of view of pushing behavior, bookmarks
4932 existing only in the remote repository are treated as
4942 existing only in the remote repository are treated as
4933 ``deleted``, even if it is in fact added remotely.
4943 ``deleted``, even if it is in fact added remotely.
4934
4944
4935 Returns 0 if there are outgoing changes, 1 otherwise.
4945 Returns 0 if there are outgoing changes, 1 otherwise.
4936 """
4946 """
4937 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4947 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4938 # style URLs, so don't overwrite dest.
4948 # style URLs, so don't overwrite dest.
4939 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4949 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4940 if not path:
4950 if not path:
4941 raise error.ConfigError(
4951 raise error.ConfigError(
4942 _(b'default repository not configured!'),
4952 _(b'default repository not configured!'),
4943 hint=_(b"see 'hg help config.paths'"),
4953 hint=_(b"see 'hg help config.paths'"),
4944 )
4954 )
4945
4955
4946 opts = pycompat.byteskwargs(opts)
4956 opts = pycompat.byteskwargs(opts)
4947 if opts.get(b'graph'):
4957 if opts.get(b'graph'):
4948 logcmdutil.checkunsupportedgraphflags([], opts)
4958 logcmdutil.checkunsupportedgraphflags([], opts)
4949 o, other = hg._outgoing(ui, repo, dest, opts)
4959 o, other = hg._outgoing(ui, repo, dest, opts)
4950 if not o:
4960 if not o:
4951 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4961 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4952 return
4962 return
4953
4963
4954 revdag = logcmdutil.graphrevs(repo, o, opts)
4964 revdag = logcmdutil.graphrevs(repo, o, opts)
4955 ui.pager(b'outgoing')
4965 ui.pager(b'outgoing')
4956 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4966 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4957 logcmdutil.displaygraph(
4967 logcmdutil.displaygraph(
4958 ui, repo, revdag, displayer, graphmod.asciiedges
4968 ui, repo, revdag, displayer, graphmod.asciiedges
4959 )
4969 )
4960 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4970 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4961 return 0
4971 return 0
4962
4972
4963 if opts.get(b'bookmarks'):
4973 if opts.get(b'bookmarks'):
4964 dest = path.pushloc or path.loc
4974 dest = path.pushloc or path.loc
4965 other = hg.peer(repo, opts, dest)
4975 other = hg.peer(repo, opts, dest)
4966 if b'bookmarks' not in other.listkeys(b'namespaces'):
4976 try:
4967 ui.warn(_(b"remote doesn't support bookmarks\n"))
4977 if b'bookmarks' not in other.listkeys(b'namespaces'):
4968 return 0
4978 ui.warn(_(b"remote doesn't support bookmarks\n"))
4969 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
4979 return 0
4970 ui.pager(b'outgoing')
4980 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
4971 return bookmarks.outgoing(ui, repo, other)
4981 ui.pager(b'outgoing')
4982 return bookmarks.outgoing(ui, repo, other)
4983 finally:
4984 other.close()
4972
4985
4973 repo._subtoppath = path.pushloc or path.loc
4986 repo._subtoppath = path.pushloc or path.loc
4974 try:
4987 try:
4975 return hg.outgoing(ui, repo, dest, opts)
4988 return hg.outgoing(ui, repo, dest, opts)
4976 finally:
4989 finally:
4977 del repo._subtoppath
4990 del repo._subtoppath
4978
4991
4979
4992
4980 @command(
4993 @command(
4981 b'parents',
4994 b'parents',
4982 [
4995 [
4983 (
4996 (
4984 b'r',
4997 b'r',
4985 b'rev',
4998 b'rev',
4986 b'',
4999 b'',
4987 _(b'show parents of the specified revision'),
5000 _(b'show parents of the specified revision'),
4988 _(b'REV'),
5001 _(b'REV'),
4989 ),
5002 ),
4990 ]
5003 ]
4991 + templateopts,
5004 + templateopts,
4992 _(b'[-r REV] [FILE]'),
5005 _(b'[-r REV] [FILE]'),
4993 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5006 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4994 inferrepo=True,
5007 inferrepo=True,
4995 )
5008 )
4996 def parents(ui, repo, file_=None, **opts):
5009 def parents(ui, repo, file_=None, **opts):
4997 """show the parents of the working directory or revision (DEPRECATED)
5010 """show the parents of the working directory or revision (DEPRECATED)
4998
5011
4999 Print the working directory's parent revisions. If a revision is
5012 Print the working directory's parent revisions. If a revision is
5000 given via -r/--rev, the parent of that revision will be printed.
5013 given via -r/--rev, the parent of that revision will be printed.
5001 If a file argument is given, the revision in which the file was
5014 If a file argument is given, the revision in which the file was
5002 last changed (before the working directory revision or the
5015 last changed (before the working directory revision or the
5003 argument to --rev if given) is printed.
5016 argument to --rev if given) is printed.
5004
5017
5005 This command is equivalent to::
5018 This command is equivalent to::
5006
5019
5007 hg log -r "p1()+p2()" or
5020 hg log -r "p1()+p2()" or
5008 hg log -r "p1(REV)+p2(REV)" or
5021 hg log -r "p1(REV)+p2(REV)" or
5009 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5022 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5010 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5023 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5011
5024
5012 See :hg:`summary` and :hg:`help revsets` for related information.
5025 See :hg:`summary` and :hg:`help revsets` for related information.
5013
5026
5014 Returns 0 on success.
5027 Returns 0 on success.
5015 """
5028 """
5016
5029
5017 opts = pycompat.byteskwargs(opts)
5030 opts = pycompat.byteskwargs(opts)
5018 rev = opts.get(b'rev')
5031 rev = opts.get(b'rev')
5019 if rev:
5032 if rev:
5020 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5033 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5021 ctx = scmutil.revsingle(repo, rev, None)
5034 ctx = scmutil.revsingle(repo, rev, None)
5022
5035
5023 if file_:
5036 if file_:
5024 m = scmutil.match(ctx, (file_,), opts)
5037 m = scmutil.match(ctx, (file_,), opts)
5025 if m.anypats() or len(m.files()) != 1:
5038 if m.anypats() or len(m.files()) != 1:
5026 raise error.InputError(_(b'can only specify an explicit filename'))
5039 raise error.InputError(_(b'can only specify an explicit filename'))
5027 file_ = m.files()[0]
5040 file_ = m.files()[0]
5028 filenodes = []
5041 filenodes = []
5029 for cp in ctx.parents():
5042 for cp in ctx.parents():
5030 if not cp:
5043 if not cp:
5031 continue
5044 continue
5032 try:
5045 try:
5033 filenodes.append(cp.filenode(file_))
5046 filenodes.append(cp.filenode(file_))
5034 except error.LookupError:
5047 except error.LookupError:
5035 pass
5048 pass
5036 if not filenodes:
5049 if not filenodes:
5037 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5050 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5038 p = []
5051 p = []
5039 for fn in filenodes:
5052 for fn in filenodes:
5040 fctx = repo.filectx(file_, fileid=fn)
5053 fctx = repo.filectx(file_, fileid=fn)
5041 p.append(fctx.node())
5054 p.append(fctx.node())
5042 else:
5055 else:
5043 p = [cp.node() for cp in ctx.parents()]
5056 p = [cp.node() for cp in ctx.parents()]
5044
5057
5045 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5058 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5046 for n in p:
5059 for n in p:
5047 if n != nullid:
5060 if n != nullid:
5048 displayer.show(repo[n])
5061 displayer.show(repo[n])
5049 displayer.close()
5062 displayer.close()
5050
5063
5051
5064
5052 @command(
5065 @command(
5053 b'paths',
5066 b'paths',
5054 formatteropts,
5067 formatteropts,
5055 _(b'[NAME]'),
5068 _(b'[NAME]'),
5056 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5069 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5057 optionalrepo=True,
5070 optionalrepo=True,
5058 intents={INTENT_READONLY},
5071 intents={INTENT_READONLY},
5059 )
5072 )
5060 def paths(ui, repo, search=None, **opts):
5073 def paths(ui, repo, search=None, **opts):
5061 """show aliases for remote repositories
5074 """show aliases for remote repositories
5062
5075
5063 Show definition of symbolic path name NAME. If no name is given,
5076 Show definition of symbolic path name NAME. If no name is given,
5064 show definition of all available names.
5077 show definition of all available names.
5065
5078
5066 Option -q/--quiet suppresses all output when searching for NAME
5079 Option -q/--quiet suppresses all output when searching for NAME
5067 and shows only the path names when listing all definitions.
5080 and shows only the path names when listing all definitions.
5068
5081
5069 Path names are defined in the [paths] section of your
5082 Path names are defined in the [paths] section of your
5070 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5083 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5071 repository, ``.hg/hgrc`` is used, too.
5084 repository, ``.hg/hgrc`` is used, too.
5072
5085
5073 The path names ``default`` and ``default-push`` have a special
5086 The path names ``default`` and ``default-push`` have a special
5074 meaning. When performing a push or pull operation, they are used
5087 meaning. When performing a push or pull operation, they are used
5075 as fallbacks if no location is specified on the command-line.
5088 as fallbacks if no location is specified on the command-line.
5076 When ``default-push`` is set, it will be used for push and
5089 When ``default-push`` is set, it will be used for push and
5077 ``default`` will be used for pull; otherwise ``default`` is used
5090 ``default`` will be used for pull; otherwise ``default`` is used
5078 as the fallback for both. When cloning a repository, the clone
5091 as the fallback for both. When cloning a repository, the clone
5079 source is written as ``default`` in ``.hg/hgrc``.
5092 source is written as ``default`` in ``.hg/hgrc``.
5080
5093
5081 .. note::
5094 .. note::
5082
5095
5083 ``default`` and ``default-push`` apply to all inbound (e.g.
5096 ``default`` and ``default-push`` apply to all inbound (e.g.
5084 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5097 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5085 and :hg:`bundle`) operations.
5098 and :hg:`bundle`) operations.
5086
5099
5087 See :hg:`help urls` for more information.
5100 See :hg:`help urls` for more information.
5088
5101
5089 .. container:: verbose
5102 .. container:: verbose
5090
5103
5091 Template:
5104 Template:
5092
5105
5093 The following keywords are supported. See also :hg:`help templates`.
5106 The following keywords are supported. See also :hg:`help templates`.
5094
5107
5095 :name: String. Symbolic name of the path alias.
5108 :name: String. Symbolic name of the path alias.
5096 :pushurl: String. URL for push operations.
5109 :pushurl: String. URL for push operations.
5097 :url: String. URL or directory path for the other operations.
5110 :url: String. URL or directory path for the other operations.
5098
5111
5099 Returns 0 on success.
5112 Returns 0 on success.
5100 """
5113 """
5101
5114
5102 opts = pycompat.byteskwargs(opts)
5115 opts = pycompat.byteskwargs(opts)
5103 ui.pager(b'paths')
5116 ui.pager(b'paths')
5104 if search:
5117 if search:
5105 pathitems = [
5118 pathitems = [
5106 (name, path)
5119 (name, path)
5107 for name, path in pycompat.iteritems(ui.paths)
5120 for name, path in pycompat.iteritems(ui.paths)
5108 if name == search
5121 if name == search
5109 ]
5122 ]
5110 else:
5123 else:
5111 pathitems = sorted(pycompat.iteritems(ui.paths))
5124 pathitems = sorted(pycompat.iteritems(ui.paths))
5112
5125
5113 fm = ui.formatter(b'paths', opts)
5126 fm = ui.formatter(b'paths', opts)
5114 if fm.isplain():
5127 if fm.isplain():
5115 hidepassword = util.hidepassword
5128 hidepassword = util.hidepassword
5116 else:
5129 else:
5117 hidepassword = bytes
5130 hidepassword = bytes
5118 if ui.quiet:
5131 if ui.quiet:
5119 namefmt = b'%s\n'
5132 namefmt = b'%s\n'
5120 else:
5133 else:
5121 namefmt = b'%s = '
5134 namefmt = b'%s = '
5122 showsubopts = not search and not ui.quiet
5135 showsubopts = not search and not ui.quiet
5123
5136
5124 for name, path in pathitems:
5137 for name, path in pathitems:
5125 fm.startitem()
5138 fm.startitem()
5126 fm.condwrite(not search, b'name', namefmt, name)
5139 fm.condwrite(not search, b'name', namefmt, name)
5127 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5140 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5128 for subopt, value in sorted(path.suboptions.items()):
5141 for subopt, value in sorted(path.suboptions.items()):
5129 assert subopt not in (b'name', b'url')
5142 assert subopt not in (b'name', b'url')
5130 if showsubopts:
5143 if showsubopts:
5131 fm.plain(b'%s:%s = ' % (name, subopt))
5144 fm.plain(b'%s:%s = ' % (name, subopt))
5132 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5145 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5133
5146
5134 fm.end()
5147 fm.end()
5135
5148
5136 if search and not pathitems:
5149 if search and not pathitems:
5137 if not ui.quiet:
5150 if not ui.quiet:
5138 ui.warn(_(b"not found!\n"))
5151 ui.warn(_(b"not found!\n"))
5139 return 1
5152 return 1
5140 else:
5153 else:
5141 return 0
5154 return 0
5142
5155
5143
5156
5144 @command(
5157 @command(
5145 b'phase',
5158 b'phase',
5146 [
5159 [
5147 (b'p', b'public', False, _(b'set changeset phase to public')),
5160 (b'p', b'public', False, _(b'set changeset phase to public')),
5148 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5161 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5149 (b's', b'secret', False, _(b'set changeset phase to secret')),
5162 (b's', b'secret', False, _(b'set changeset phase to secret')),
5150 (b'f', b'force', False, _(b'allow to move boundary backward')),
5163 (b'f', b'force', False, _(b'allow to move boundary backward')),
5151 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5164 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5152 ],
5165 ],
5153 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5166 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5154 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5167 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5155 )
5168 )
5156 def phase(ui, repo, *revs, **opts):
5169 def phase(ui, repo, *revs, **opts):
5157 """set or show the current phase name
5170 """set or show the current phase name
5158
5171
5159 With no argument, show the phase name of the current revision(s).
5172 With no argument, show the phase name of the current revision(s).
5160
5173
5161 With one of -p/--public, -d/--draft or -s/--secret, change the
5174 With one of -p/--public, -d/--draft or -s/--secret, change the
5162 phase value of the specified revisions.
5175 phase value of the specified revisions.
5163
5176
5164 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5177 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5165 lower phase to a higher phase. Phases are ordered as follows::
5178 lower phase to a higher phase. Phases are ordered as follows::
5166
5179
5167 public < draft < secret
5180 public < draft < secret
5168
5181
5169 Returns 0 on success, 1 if some phases could not be changed.
5182 Returns 0 on success, 1 if some phases could not be changed.
5170
5183
5171 (For more information about the phases concept, see :hg:`help phases`.)
5184 (For more information about the phases concept, see :hg:`help phases`.)
5172 """
5185 """
5173 opts = pycompat.byteskwargs(opts)
5186 opts = pycompat.byteskwargs(opts)
5174 # search for a unique phase argument
5187 # search for a unique phase argument
5175 targetphase = None
5188 targetphase = None
5176 for idx, name in enumerate(phases.cmdphasenames):
5189 for idx, name in enumerate(phases.cmdphasenames):
5177 if opts[name]:
5190 if opts[name]:
5178 if targetphase is not None:
5191 if targetphase is not None:
5179 raise error.InputError(_(b'only one phase can be specified'))
5192 raise error.InputError(_(b'only one phase can be specified'))
5180 targetphase = idx
5193 targetphase = idx
5181
5194
5182 # look for specified revision
5195 # look for specified revision
5183 revs = list(revs)
5196 revs = list(revs)
5184 revs.extend(opts[b'rev'])
5197 revs.extend(opts[b'rev'])
5185 if not revs:
5198 if not revs:
5186 # display both parents as the second parent phase can influence
5199 # display both parents as the second parent phase can influence
5187 # the phase of a merge commit
5200 # the phase of a merge commit
5188 revs = [c.rev() for c in repo[None].parents()]
5201 revs = [c.rev() for c in repo[None].parents()]
5189
5202
5190 revs = scmutil.revrange(repo, revs)
5203 revs = scmutil.revrange(repo, revs)
5191
5204
5192 ret = 0
5205 ret = 0
5193 if targetphase is None:
5206 if targetphase is None:
5194 # display
5207 # display
5195 for r in revs:
5208 for r in revs:
5196 ctx = repo[r]
5209 ctx = repo[r]
5197 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5210 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5198 else:
5211 else:
5199 with repo.lock(), repo.transaction(b"phase") as tr:
5212 with repo.lock(), repo.transaction(b"phase") as tr:
5200 # set phase
5213 # set phase
5201 if not revs:
5214 if not revs:
5202 raise error.InputError(_(b'empty revision set'))
5215 raise error.InputError(_(b'empty revision set'))
5203 nodes = [repo[r].node() for r in revs]
5216 nodes = [repo[r].node() for r in revs]
5204 # moving revision from public to draft may hide them
5217 # moving revision from public to draft may hide them
5205 # We have to check result on an unfiltered repository
5218 # We have to check result on an unfiltered repository
5206 unfi = repo.unfiltered()
5219 unfi = repo.unfiltered()
5207 getphase = unfi._phasecache.phase
5220 getphase = unfi._phasecache.phase
5208 olddata = [getphase(unfi, r) for r in unfi]
5221 olddata = [getphase(unfi, r) for r in unfi]
5209 phases.advanceboundary(repo, tr, targetphase, nodes)
5222 phases.advanceboundary(repo, tr, targetphase, nodes)
5210 if opts[b'force']:
5223 if opts[b'force']:
5211 phases.retractboundary(repo, tr, targetphase, nodes)
5224 phases.retractboundary(repo, tr, targetphase, nodes)
5212 getphase = unfi._phasecache.phase
5225 getphase = unfi._phasecache.phase
5213 newdata = [getphase(unfi, r) for r in unfi]
5226 newdata = [getphase(unfi, r) for r in unfi]
5214 changes = sum(newdata[r] != olddata[r] for r in unfi)
5227 changes = sum(newdata[r] != olddata[r] for r in unfi)
5215 cl = unfi.changelog
5228 cl = unfi.changelog
5216 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5229 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5217 if rejected:
5230 if rejected:
5218 ui.warn(
5231 ui.warn(
5219 _(
5232 _(
5220 b'cannot move %i changesets to a higher '
5233 b'cannot move %i changesets to a higher '
5221 b'phase, use --force\n'
5234 b'phase, use --force\n'
5222 )
5235 )
5223 % len(rejected)
5236 % len(rejected)
5224 )
5237 )
5225 ret = 1
5238 ret = 1
5226 if changes:
5239 if changes:
5227 msg = _(b'phase changed for %i changesets\n') % changes
5240 msg = _(b'phase changed for %i changesets\n') % changes
5228 if ret:
5241 if ret:
5229 ui.status(msg)
5242 ui.status(msg)
5230 else:
5243 else:
5231 ui.note(msg)
5244 ui.note(msg)
5232 else:
5245 else:
5233 ui.warn(_(b'no phases changed\n'))
5246 ui.warn(_(b'no phases changed\n'))
5234 return ret
5247 return ret
5235
5248
5236
5249
5237 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5250 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5238 """Run after a changegroup has been added via pull/unbundle
5251 """Run after a changegroup has been added via pull/unbundle
5239
5252
5240 This takes arguments below:
5253 This takes arguments below:
5241
5254
5242 :modheads: change of heads by pull/unbundle
5255 :modheads: change of heads by pull/unbundle
5243 :optupdate: updating working directory is needed or not
5256 :optupdate: updating working directory is needed or not
5244 :checkout: update destination revision (or None to default destination)
5257 :checkout: update destination revision (or None to default destination)
5245 :brev: a name, which might be a bookmark to be activated after updating
5258 :brev: a name, which might be a bookmark to be activated after updating
5246 """
5259 """
5247 if modheads == 0:
5260 if modheads == 0:
5248 return
5261 return
5249 if optupdate:
5262 if optupdate:
5250 try:
5263 try:
5251 return hg.updatetotally(ui, repo, checkout, brev)
5264 return hg.updatetotally(ui, repo, checkout, brev)
5252 except error.UpdateAbort as inst:
5265 except error.UpdateAbort as inst:
5253 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5266 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5254 hint = inst.hint
5267 hint = inst.hint
5255 raise error.UpdateAbort(msg, hint=hint)
5268 raise error.UpdateAbort(msg, hint=hint)
5256 if modheads is not None and modheads > 1:
5269 if modheads is not None and modheads > 1:
5257 currentbranchheads = len(repo.branchheads())
5270 currentbranchheads = len(repo.branchheads())
5258 if currentbranchheads == modheads:
5271 if currentbranchheads == modheads:
5259 ui.status(
5272 ui.status(
5260 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5273 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5261 )
5274 )
5262 elif currentbranchheads > 1:
5275 elif currentbranchheads > 1:
5263 ui.status(
5276 ui.status(
5264 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5277 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5265 )
5278 )
5266 else:
5279 else:
5267 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5280 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5268 elif not ui.configbool(b'commands', b'update.requiredest'):
5281 elif not ui.configbool(b'commands', b'update.requiredest'):
5269 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5282 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5270
5283
5271
5284
5272 @command(
5285 @command(
5273 b'pull',
5286 b'pull',
5274 [
5287 [
5275 (
5288 (
5276 b'u',
5289 b'u',
5277 b'update',
5290 b'update',
5278 None,
5291 None,
5279 _(b'update to new branch head if new descendants were pulled'),
5292 _(b'update to new branch head if new descendants were pulled'),
5280 ),
5293 ),
5281 (
5294 (
5282 b'f',
5295 b'f',
5283 b'force',
5296 b'force',
5284 None,
5297 None,
5285 _(b'run even when remote repository is unrelated'),
5298 _(b'run even when remote repository is unrelated'),
5286 ),
5299 ),
5287 (
5300 (
5288 b'',
5301 b'',
5289 b'confirm',
5302 b'confirm',
5290 None,
5303 None,
5291 _(b'confirm pull before applying changes'),
5304 _(b'confirm pull before applying changes'),
5292 ),
5305 ),
5293 (
5306 (
5294 b'r',
5307 b'r',
5295 b'rev',
5308 b'rev',
5296 [],
5309 [],
5297 _(b'a remote changeset intended to be added'),
5310 _(b'a remote changeset intended to be added'),
5298 _(b'REV'),
5311 _(b'REV'),
5299 ),
5312 ),
5300 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5313 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5301 (
5314 (
5302 b'b',
5315 b'b',
5303 b'branch',
5316 b'branch',
5304 [],
5317 [],
5305 _(b'a specific branch you would like to pull'),
5318 _(b'a specific branch you would like to pull'),
5306 _(b'BRANCH'),
5319 _(b'BRANCH'),
5307 ),
5320 ),
5308 ]
5321 ]
5309 + remoteopts,
5322 + remoteopts,
5310 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5323 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5311 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5324 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5312 helpbasic=True,
5325 helpbasic=True,
5313 )
5326 )
5314 def pull(ui, repo, source=b"default", **opts):
5327 def pull(ui, repo, source=b"default", **opts):
5315 """pull changes from the specified source
5328 """pull changes from the specified source
5316
5329
5317 Pull changes from a remote repository to a local one.
5330 Pull changes from a remote repository to a local one.
5318
5331
5319 This finds all changes from the repository at the specified path
5332 This finds all changes from the repository at the specified path
5320 or URL and adds them to a local repository (the current one unless
5333 or URL and adds them to a local repository (the current one unless
5321 -R is specified). By default, this does not update the copy of the
5334 -R is specified). By default, this does not update the copy of the
5322 project in the working directory.
5335 project in the working directory.
5323
5336
5324 When cloning from servers that support it, Mercurial may fetch
5337 When cloning from servers that support it, Mercurial may fetch
5325 pre-generated data. When this is done, hooks operating on incoming
5338 pre-generated data. When this is done, hooks operating on incoming
5326 changesets and changegroups may fire more than once, once for each
5339 changesets and changegroups may fire more than once, once for each
5327 pre-generated bundle and as well as for any additional remaining
5340 pre-generated bundle and as well as for any additional remaining
5328 data. See :hg:`help -e clonebundles` for more.
5341 data. See :hg:`help -e clonebundles` for more.
5329
5342
5330 Use :hg:`incoming` if you want to see what would have been added
5343 Use :hg:`incoming` if you want to see what would have been added
5331 by a pull at the time you issued this command. If you then decide
5344 by a pull at the time you issued this command. If you then decide
5332 to add those changes to the repository, you should use :hg:`pull
5345 to add those changes to the repository, you should use :hg:`pull
5333 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5346 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5334
5347
5335 If SOURCE is omitted, the 'default' path will be used.
5348 If SOURCE is omitted, the 'default' path will be used.
5336 See :hg:`help urls` for more information.
5349 See :hg:`help urls` for more information.
5337
5350
5338 Specifying bookmark as ``.`` is equivalent to specifying the active
5351 Specifying bookmark as ``.`` is equivalent to specifying the active
5339 bookmark's name.
5352 bookmark's name.
5340
5353
5341 Returns 0 on success, 1 if an update had unresolved files.
5354 Returns 0 on success, 1 if an update had unresolved files.
5342 """
5355 """
5343
5356
5344 opts = pycompat.byteskwargs(opts)
5357 opts = pycompat.byteskwargs(opts)
5345 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5358 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5346 b'update'
5359 b'update'
5347 ):
5360 ):
5348 msg = _(b'update destination required by configuration')
5361 msg = _(b'update destination required by configuration')
5349 hint = _(b'use hg pull followed by hg update DEST')
5362 hint = _(b'use hg pull followed by hg update DEST')
5350 raise error.InputError(msg, hint=hint)
5363 raise error.InputError(msg, hint=hint)
5351
5364
5352 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5365 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5353 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5366 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5354 ui.flush()
5367 ui.flush()
5355 other = hg.peer(repo, opts, source)
5368 other = hg.peer(repo, opts, source)
5356 try:
5369 try:
5357 revs, checkout = hg.addbranchrevs(
5370 revs, checkout = hg.addbranchrevs(
5358 repo, other, branches, opts.get(b'rev')
5371 repo, other, branches, opts.get(b'rev')
5359 )
5372 )
5360
5373
5361 pullopargs = {}
5374 pullopargs = {}
5362
5375
5363 nodes = None
5376 nodes = None
5364 if opts.get(b'bookmark') or revs:
5377 if opts.get(b'bookmark') or revs:
5365 # The list of bookmark used here is the same used to actually update
5378 # The list of bookmark used here is the same used to actually update
5366 # the bookmark names, to avoid the race from issue 4689 and we do
5379 # the bookmark names, to avoid the race from issue 4689 and we do
5367 # all lookup and bookmark queries in one go so they see the same
5380 # all lookup and bookmark queries in one go so they see the same
5368 # version of the server state (issue 4700).
5381 # version of the server state (issue 4700).
5369 nodes = []
5382 nodes = []
5370 fnodes = []
5383 fnodes = []
5371 revs = revs or []
5384 revs = revs or []
5372 if revs and not other.capable(b'lookup'):
5385 if revs and not other.capable(b'lookup'):
5373 err = _(
5386 err = _(
5374 b"other repository doesn't support revision lookup, "
5387 b"other repository doesn't support revision lookup, "
5375 b"so a rev cannot be specified."
5388 b"so a rev cannot be specified."
5376 )
5389 )
5377 raise error.Abort(err)
5390 raise error.Abort(err)
5378 with other.commandexecutor() as e:
5391 with other.commandexecutor() as e:
5379 fremotebookmarks = e.callcommand(
5392 fremotebookmarks = e.callcommand(
5380 b'listkeys', {b'namespace': b'bookmarks'}
5393 b'listkeys', {b'namespace': b'bookmarks'}
5381 )
5394 )
5382 for r in revs:
5395 for r in revs:
5383 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5396 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5384 remotebookmarks = fremotebookmarks.result()
5397 remotebookmarks = fremotebookmarks.result()
5385 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5398 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5386 pullopargs[b'remotebookmarks'] = remotebookmarks
5399 pullopargs[b'remotebookmarks'] = remotebookmarks
5387 for b in opts.get(b'bookmark', []):
5400 for b in opts.get(b'bookmark', []):
5388 b = repo._bookmarks.expandname(b)
5401 b = repo._bookmarks.expandname(b)
5389 if b not in remotebookmarks:
5402 if b not in remotebookmarks:
5390 raise error.InputError(
5403 raise error.InputError(
5391 _(b'remote bookmark %s not found!') % b
5404 _(b'remote bookmark %s not found!') % b
5392 )
5405 )
5393 nodes.append(remotebookmarks[b])
5406 nodes.append(remotebookmarks[b])
5394 for i, rev in enumerate(revs):
5407 for i, rev in enumerate(revs):
5395 node = fnodes[i].result()
5408 node = fnodes[i].result()
5396 nodes.append(node)
5409 nodes.append(node)
5397 if rev == checkout:
5410 if rev == checkout:
5398 checkout = node
5411 checkout = node
5399
5412
5400 wlock = util.nullcontextmanager()
5413 wlock = util.nullcontextmanager()
5401 if opts.get(b'update'):
5414 if opts.get(b'update'):
5402 wlock = repo.wlock()
5415 wlock = repo.wlock()
5403 with wlock:
5416 with wlock:
5404 pullopargs.update(opts.get(b'opargs', {}))
5417 pullopargs.update(opts.get(b'opargs', {}))
5405 modheads = exchange.pull(
5418 modheads = exchange.pull(
5406 repo,
5419 repo,
5407 other,
5420 other,
5408 heads=nodes,
5421 heads=nodes,
5409 force=opts.get(b'force'),
5422 force=opts.get(b'force'),
5410 bookmarks=opts.get(b'bookmark', ()),
5423 bookmarks=opts.get(b'bookmark', ()),
5411 opargs=pullopargs,
5424 opargs=pullopargs,
5412 confirm=opts.get(b'confirm'),
5425 confirm=opts.get(b'confirm'),
5413 ).cgresult
5426 ).cgresult
5414
5427
5415 # brev is a name, which might be a bookmark to be activated at
5428 # brev is a name, which might be a bookmark to be activated at
5416 # the end of the update. In other words, it is an explicit
5429 # the end of the update. In other words, it is an explicit
5417 # destination of the update
5430 # destination of the update
5418 brev = None
5431 brev = None
5419
5432
5420 if checkout:
5433 if checkout:
5421 checkout = repo.unfiltered().changelog.rev(checkout)
5434 checkout = repo.unfiltered().changelog.rev(checkout)
5422
5435
5423 # order below depends on implementation of
5436 # order below depends on implementation of
5424 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5437 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5425 # because 'checkout' is determined without it.
5438 # because 'checkout' is determined without it.
5426 if opts.get(b'rev'):
5439 if opts.get(b'rev'):
5427 brev = opts[b'rev'][0]
5440 brev = opts[b'rev'][0]
5428 elif opts.get(b'branch'):
5441 elif opts.get(b'branch'):
5429 brev = opts[b'branch'][0]
5442 brev = opts[b'branch'][0]
5430 else:
5443 else:
5431 brev = branches[0]
5444 brev = branches[0]
5432 repo._subtoppath = source
5445 repo._subtoppath = source
5433 try:
5446 try:
5434 ret = postincoming(
5447 ret = postincoming(
5435 ui, repo, modheads, opts.get(b'update'), checkout, brev
5448 ui, repo, modheads, opts.get(b'update'), checkout, brev
5436 )
5449 )
5437 except error.FilteredRepoLookupError as exc:
5450 except error.FilteredRepoLookupError as exc:
5438 msg = _(b'cannot update to target: %s') % exc.args[0]
5451 msg = _(b'cannot update to target: %s') % exc.args[0]
5439 exc.args = (msg,) + exc.args[1:]
5452 exc.args = (msg,) + exc.args[1:]
5440 raise
5453 raise
5441 finally:
5454 finally:
5442 del repo._subtoppath
5455 del repo._subtoppath
5443
5456
5444 finally:
5457 finally:
5445 other.close()
5458 other.close()
5446 return ret
5459 return ret
5447
5460
5448
5461
5449 @command(
5462 @command(
5450 b'purge|clean',
5463 b'purge|clean',
5451 [
5464 [
5452 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5465 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5453 (b'', b'all', None, _(b'purge ignored files too')),
5466 (b'', b'all', None, _(b'purge ignored files too')),
5454 (b'i', b'ignored', None, _(b'purge only ignored files')),
5467 (b'i', b'ignored', None, _(b'purge only ignored files')),
5455 (b'', b'dirs', None, _(b'purge empty directories')),
5468 (b'', b'dirs', None, _(b'purge empty directories')),
5456 (b'', b'files', None, _(b'purge files')),
5469 (b'', b'files', None, _(b'purge files')),
5457 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5470 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5458 (
5471 (
5459 b'0',
5472 b'0',
5460 b'print0',
5473 b'print0',
5461 None,
5474 None,
5462 _(
5475 _(
5463 b'end filenames with NUL, for use with xargs'
5476 b'end filenames with NUL, for use with xargs'
5464 b' (implies -p/--print)'
5477 b' (implies -p/--print)'
5465 ),
5478 ),
5466 ),
5479 ),
5467 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5480 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5468 ]
5481 ]
5469 + cmdutil.walkopts,
5482 + cmdutil.walkopts,
5470 _(b'hg purge [OPTION]... [DIR]...'),
5483 _(b'hg purge [OPTION]... [DIR]...'),
5471 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5484 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5472 )
5485 )
5473 def purge(ui, repo, *dirs, **opts):
5486 def purge(ui, repo, *dirs, **opts):
5474 """removes files not tracked by Mercurial
5487 """removes files not tracked by Mercurial
5475
5488
5476 Delete files not known to Mercurial. This is useful to test local
5489 Delete files not known to Mercurial. This is useful to test local
5477 and uncommitted changes in an otherwise-clean source tree.
5490 and uncommitted changes in an otherwise-clean source tree.
5478
5491
5479 This means that purge will delete the following by default:
5492 This means that purge will delete the following by default:
5480
5493
5481 - Unknown files: files marked with "?" by :hg:`status`
5494 - Unknown files: files marked with "?" by :hg:`status`
5482 - Empty directories: in fact Mercurial ignores directories unless
5495 - Empty directories: in fact Mercurial ignores directories unless
5483 they contain files under source control management
5496 they contain files under source control management
5484
5497
5485 But it will leave untouched:
5498 But it will leave untouched:
5486
5499
5487 - Modified and unmodified tracked files
5500 - Modified and unmodified tracked files
5488 - Ignored files (unless -i or --all is specified)
5501 - Ignored files (unless -i or --all is specified)
5489 - New files added to the repository (with :hg:`add`)
5502 - New files added to the repository (with :hg:`add`)
5490
5503
5491 The --files and --dirs options can be used to direct purge to delete
5504 The --files and --dirs options can be used to direct purge to delete
5492 only files, only directories, or both. If neither option is given,
5505 only files, only directories, or both. If neither option is given,
5493 both will be deleted.
5506 both will be deleted.
5494
5507
5495 If directories are given on the command line, only files in these
5508 If directories are given on the command line, only files in these
5496 directories are considered.
5509 directories are considered.
5497
5510
5498 Be careful with purge, as you could irreversibly delete some files
5511 Be careful with purge, as you could irreversibly delete some files
5499 you forgot to add to the repository. If you only want to print the
5512 you forgot to add to the repository. If you only want to print the
5500 list of files that this program would delete, use the --print
5513 list of files that this program would delete, use the --print
5501 option.
5514 option.
5502 """
5515 """
5503 opts = pycompat.byteskwargs(opts)
5516 opts = pycompat.byteskwargs(opts)
5504 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5517 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5505
5518
5506 act = not opts.get(b'print')
5519 act = not opts.get(b'print')
5507 eol = b'\n'
5520 eol = b'\n'
5508 if opts.get(b'print0'):
5521 if opts.get(b'print0'):
5509 eol = b'\0'
5522 eol = b'\0'
5510 act = False # --print0 implies --print
5523 act = False # --print0 implies --print
5511 if opts.get(b'all', False):
5524 if opts.get(b'all', False):
5512 ignored = True
5525 ignored = True
5513 unknown = True
5526 unknown = True
5514 else:
5527 else:
5515 ignored = opts.get(b'ignored', False)
5528 ignored = opts.get(b'ignored', False)
5516 unknown = not ignored
5529 unknown = not ignored
5517
5530
5518 removefiles = opts.get(b'files')
5531 removefiles = opts.get(b'files')
5519 removedirs = opts.get(b'dirs')
5532 removedirs = opts.get(b'dirs')
5520 confirm = opts.get(b'confirm')
5533 confirm = opts.get(b'confirm')
5521 if confirm is None:
5534 if confirm is None:
5522 try:
5535 try:
5523 extensions.find(b'purge')
5536 extensions.find(b'purge')
5524 confirm = False
5537 confirm = False
5525 except KeyError:
5538 except KeyError:
5526 confirm = True
5539 confirm = True
5527
5540
5528 if not removefiles and not removedirs:
5541 if not removefiles and not removedirs:
5529 removefiles = True
5542 removefiles = True
5530 removedirs = True
5543 removedirs = True
5531
5544
5532 match = scmutil.match(repo[None], dirs, opts)
5545 match = scmutil.match(repo[None], dirs, opts)
5533
5546
5534 paths = mergemod.purge(
5547 paths = mergemod.purge(
5535 repo,
5548 repo,
5536 match,
5549 match,
5537 unknown=unknown,
5550 unknown=unknown,
5538 ignored=ignored,
5551 ignored=ignored,
5539 removeemptydirs=removedirs,
5552 removeemptydirs=removedirs,
5540 removefiles=removefiles,
5553 removefiles=removefiles,
5541 abortonerror=opts.get(b'abort_on_err'),
5554 abortonerror=opts.get(b'abort_on_err'),
5542 noop=not act,
5555 noop=not act,
5543 confirm=confirm,
5556 confirm=confirm,
5544 )
5557 )
5545
5558
5546 for path in paths:
5559 for path in paths:
5547 if not act:
5560 if not act:
5548 ui.write(b'%s%s' % (path, eol))
5561 ui.write(b'%s%s' % (path, eol))
5549
5562
5550
5563
5551 @command(
5564 @command(
5552 b'push',
5565 b'push',
5553 [
5566 [
5554 (b'f', b'force', None, _(b'force push')),
5567 (b'f', b'force', None, _(b'force push')),
5555 (
5568 (
5556 b'r',
5569 b'r',
5557 b'rev',
5570 b'rev',
5558 [],
5571 [],
5559 _(b'a changeset intended to be included in the destination'),
5572 _(b'a changeset intended to be included in the destination'),
5560 _(b'REV'),
5573 _(b'REV'),
5561 ),
5574 ),
5562 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5575 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5563 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5576 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5564 (
5577 (
5565 b'b',
5578 b'b',
5566 b'branch',
5579 b'branch',
5567 [],
5580 [],
5568 _(b'a specific branch you would like to push'),
5581 _(b'a specific branch you would like to push'),
5569 _(b'BRANCH'),
5582 _(b'BRANCH'),
5570 ),
5583 ),
5571 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5584 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5572 (
5585 (
5573 b'',
5586 b'',
5574 b'pushvars',
5587 b'pushvars',
5575 [],
5588 [],
5576 _(b'variables that can be sent to server (ADVANCED)'),
5589 _(b'variables that can be sent to server (ADVANCED)'),
5577 ),
5590 ),
5578 (
5591 (
5579 b'',
5592 b'',
5580 b'publish',
5593 b'publish',
5581 False,
5594 False,
5582 _(b'push the changeset as public (EXPERIMENTAL)'),
5595 _(b'push the changeset as public (EXPERIMENTAL)'),
5583 ),
5596 ),
5584 ]
5597 ]
5585 + remoteopts,
5598 + remoteopts,
5586 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5599 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5587 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5600 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5588 helpbasic=True,
5601 helpbasic=True,
5589 )
5602 )
5590 def push(ui, repo, dest=None, **opts):
5603 def push(ui, repo, dest=None, **opts):
5591 """push changes to the specified destination
5604 """push changes to the specified destination
5592
5605
5593 Push changesets from the local repository to the specified
5606 Push changesets from the local repository to the specified
5594 destination.
5607 destination.
5595
5608
5596 This operation is symmetrical to pull: it is identical to a pull
5609 This operation is symmetrical to pull: it is identical to a pull
5597 in the destination repository from the current one.
5610 in the destination repository from the current one.
5598
5611
5599 By default, push will not allow creation of new heads at the
5612 By default, push will not allow creation of new heads at the
5600 destination, since multiple heads would make it unclear which head
5613 destination, since multiple heads would make it unclear which head
5601 to use. In this situation, it is recommended to pull and merge
5614 to use. In this situation, it is recommended to pull and merge
5602 before pushing.
5615 before pushing.
5603
5616
5604 Use --new-branch if you want to allow push to create a new named
5617 Use --new-branch if you want to allow push to create a new named
5605 branch that is not present at the destination. This allows you to
5618 branch that is not present at the destination. This allows you to
5606 only create a new branch without forcing other changes.
5619 only create a new branch without forcing other changes.
5607
5620
5608 .. note::
5621 .. note::
5609
5622
5610 Extra care should be taken with the -f/--force option,
5623 Extra care should be taken with the -f/--force option,
5611 which will push all new heads on all branches, an action which will
5624 which will push all new heads on all branches, an action which will
5612 almost always cause confusion for collaborators.
5625 almost always cause confusion for collaborators.
5613
5626
5614 If -r/--rev is used, the specified revision and all its ancestors
5627 If -r/--rev is used, the specified revision and all its ancestors
5615 will be pushed to the remote repository.
5628 will be pushed to the remote repository.
5616
5629
5617 If -B/--bookmark is used, the specified bookmarked revision, its
5630 If -B/--bookmark is used, the specified bookmarked revision, its
5618 ancestors, and the bookmark will be pushed to the remote
5631 ancestors, and the bookmark will be pushed to the remote
5619 repository. Specifying ``.`` is equivalent to specifying the active
5632 repository. Specifying ``.`` is equivalent to specifying the active
5620 bookmark's name. Use the --all-bookmarks option for pushing all
5633 bookmark's name. Use the --all-bookmarks option for pushing all
5621 current bookmarks.
5634 current bookmarks.
5622
5635
5623 Please see :hg:`help urls` for important details about ``ssh://``
5636 Please see :hg:`help urls` for important details about ``ssh://``
5624 URLs. If DESTINATION is omitted, a default path will be used.
5637 URLs. If DESTINATION is omitted, a default path will be used.
5625
5638
5626 .. container:: verbose
5639 .. container:: verbose
5627
5640
5628 The --pushvars option sends strings to the server that become
5641 The --pushvars option sends strings to the server that become
5629 environment variables prepended with ``HG_USERVAR_``. For example,
5642 environment variables prepended with ``HG_USERVAR_``. For example,
5630 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5643 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5631 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5644 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5632
5645
5633 pushvars can provide for user-overridable hooks as well as set debug
5646 pushvars can provide for user-overridable hooks as well as set debug
5634 levels. One example is having a hook that blocks commits containing
5647 levels. One example is having a hook that blocks commits containing
5635 conflict markers, but enables the user to override the hook if the file
5648 conflict markers, but enables the user to override the hook if the file
5636 is using conflict markers for testing purposes or the file format has
5649 is using conflict markers for testing purposes or the file format has
5637 strings that look like conflict markers.
5650 strings that look like conflict markers.
5638
5651
5639 By default, servers will ignore `--pushvars`. To enable it add the
5652 By default, servers will ignore `--pushvars`. To enable it add the
5640 following to your configuration file::
5653 following to your configuration file::
5641
5654
5642 [push]
5655 [push]
5643 pushvars.server = true
5656 pushvars.server = true
5644
5657
5645 Returns 0 if push was successful, 1 if nothing to push.
5658 Returns 0 if push was successful, 1 if nothing to push.
5646 """
5659 """
5647
5660
5648 opts = pycompat.byteskwargs(opts)
5661 opts = pycompat.byteskwargs(opts)
5649
5662
5650 if opts.get(b'all_bookmarks'):
5663 if opts.get(b'all_bookmarks'):
5651 cmdutil.check_incompatible_arguments(
5664 cmdutil.check_incompatible_arguments(
5652 opts,
5665 opts,
5653 b'all_bookmarks',
5666 b'all_bookmarks',
5654 [b'bookmark', b'rev'],
5667 [b'bookmark', b'rev'],
5655 )
5668 )
5656 opts[b'bookmark'] = list(repo._bookmarks)
5669 opts[b'bookmark'] = list(repo._bookmarks)
5657
5670
5658 if opts.get(b'bookmark'):
5671 if opts.get(b'bookmark'):
5659 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5672 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5660 for b in opts[b'bookmark']:
5673 for b in opts[b'bookmark']:
5661 # translate -B options to -r so changesets get pushed
5674 # translate -B options to -r so changesets get pushed
5662 b = repo._bookmarks.expandname(b)
5675 b = repo._bookmarks.expandname(b)
5663 if b in repo._bookmarks:
5676 if b in repo._bookmarks:
5664 opts.setdefault(b'rev', []).append(b)
5677 opts.setdefault(b'rev', []).append(b)
5665 else:
5678 else:
5666 # if we try to push a deleted bookmark, translate it to null
5679 # if we try to push a deleted bookmark, translate it to null
5667 # this lets simultaneous -r, -b options continue working
5680 # this lets simultaneous -r, -b options continue working
5668 opts.setdefault(b'rev', []).append(b"null")
5681 opts.setdefault(b'rev', []).append(b"null")
5669
5682
5670 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5683 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5671 if not path:
5684 if not path:
5672 raise error.ConfigError(
5685 raise error.ConfigError(
5673 _(b'default repository not configured!'),
5686 _(b'default repository not configured!'),
5674 hint=_(b"see 'hg help config.paths'"),
5687 hint=_(b"see 'hg help config.paths'"),
5675 )
5688 )
5676 dest = path.pushloc or path.loc
5689 dest = path.pushloc or path.loc
5677 branches = (path.branch, opts.get(b'branch') or [])
5690 branches = (path.branch, opts.get(b'branch') or [])
5678 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5691 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5679 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5692 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5680 other = hg.peer(repo, opts, dest)
5693 other = hg.peer(repo, opts, dest)
5681
5694
5682 if revs:
5695 try:
5683 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5696 if revs:
5684 if not revs:
5697 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5698 if not revs:
5699 raise error.InputError(
5700 _(b"specified revisions evaluate to an empty set"),
5701 hint=_(b"use different revision arguments"),
5702 )
5703 elif path.pushrev:
5704 # It doesn't make any sense to specify ancestor revisions. So limit
5705 # to DAG heads to make discovery simpler.
5706 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5707 revs = scmutil.revrange(repo, [expr])
5708 revs = [repo[rev].node() for rev in revs]
5709 if not revs:
5710 raise error.InputError(
5711 _(b'default push revset for path evaluates to an empty set')
5712 )
5713 elif ui.configbool(b'commands', b'push.require-revs'):
5685 raise error.InputError(
5714 raise error.InputError(
5686 _(b"specified revisions evaluate to an empty set"),
5715 _(b'no revisions specified to push'),
5687 hint=_(b"use different revision arguments"),
5716 hint=_(b'did you mean "hg push -r ."?'),
5688 )
5717 )
5689 elif path.pushrev:
5718
5690 # It doesn't make any sense to specify ancestor revisions. So limit
5719 repo._subtoppath = dest
5691 # to DAG heads to make discovery simpler.
5720 try:
5692 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5721 # push subrepos depth-first for coherent ordering
5693 revs = scmutil.revrange(repo, [expr])
5722 c = repo[b'.']
5694 revs = [repo[rev].node() for rev in revs]
5723 subs = c.substate # only repos that are committed
5695 if not revs:
5724 for s in sorted(subs):
5696 raise error.InputError(
5725 result = c.sub(s).push(opts)
5697 _(b'default push revset for path evaluates to an empty set')
5726 if result == 0:
5698 )
5727 return not result
5699 elif ui.configbool(b'commands', b'push.require-revs'):
5728 finally:
5700 raise error.InputError(
5729 del repo._subtoppath
5701 _(b'no revisions specified to push'),
5730
5702 hint=_(b'did you mean "hg push -r ."?'),
5731 opargs = dict(
5732 opts.get(b'opargs', {})
5733 ) # copy opargs since we may mutate it
5734 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5735
5736 pushop = exchange.push(
5737 repo,
5738 other,
5739 opts.get(b'force'),
5740 revs=revs,
5741 newbranch=opts.get(b'new_branch'),
5742 bookmarks=opts.get(b'bookmark', ()),
5743 publish=opts.get(b'publish'),
5744 opargs=opargs,
5703 )
5745 )
5704
5746
5705 repo._subtoppath = dest
5747 result = not pushop.cgresult
5706 try:
5748
5707 # push subrepos depth-first for coherent ordering
5749 if pushop.bkresult is not None:
5708 c = repo[b'.']
5750 if pushop.bkresult == 2:
5709 subs = c.substate # only repos that are committed
5751 result = 2
5710 for s in sorted(subs):
5752 elif not result and pushop.bkresult:
5711 result = c.sub(s).push(opts)
5753 result = 2
5712 if result == 0:
5713 return not result
5714 finally:
5754 finally:
5715 del repo._subtoppath
5755 other.close()
5716
5717 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5718 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5719
5720 pushop = exchange.push(
5721 repo,
5722 other,
5723 opts.get(b'force'),
5724 revs=revs,
5725 newbranch=opts.get(b'new_branch'),
5726 bookmarks=opts.get(b'bookmark', ()),
5727 publish=opts.get(b'publish'),
5728 opargs=opargs,
5729 )
5730
5731 result = not pushop.cgresult
5732
5733 if pushop.bkresult is not None:
5734 if pushop.bkresult == 2:
5735 result = 2
5736 elif not result and pushop.bkresult:
5737 result = 2
5738
5739 return result
5756 return result
5740
5757
5741
5758
5742 @command(
5759 @command(
5743 b'recover',
5760 b'recover',
5744 [
5761 [
5745 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5762 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5746 ],
5763 ],
5747 helpcategory=command.CATEGORY_MAINTENANCE,
5764 helpcategory=command.CATEGORY_MAINTENANCE,
5748 )
5765 )
5749 def recover(ui, repo, **opts):
5766 def recover(ui, repo, **opts):
5750 """roll back an interrupted transaction
5767 """roll back an interrupted transaction
5751
5768
5752 Recover from an interrupted commit or pull.
5769 Recover from an interrupted commit or pull.
5753
5770
5754 This command tries to fix the repository status after an
5771 This command tries to fix the repository status after an
5755 interrupted operation. It should only be necessary when Mercurial
5772 interrupted operation. It should only be necessary when Mercurial
5756 suggests it.
5773 suggests it.
5757
5774
5758 Returns 0 if successful, 1 if nothing to recover or verify fails.
5775 Returns 0 if successful, 1 if nothing to recover or verify fails.
5759 """
5776 """
5760 ret = repo.recover()
5777 ret = repo.recover()
5761 if ret:
5778 if ret:
5762 if opts['verify']:
5779 if opts['verify']:
5763 return hg.verify(repo)
5780 return hg.verify(repo)
5764 else:
5781 else:
5765 msg = _(
5782 msg = _(
5766 b"(verify step skipped, run `hg verify` to check your "
5783 b"(verify step skipped, run `hg verify` to check your "
5767 b"repository content)\n"
5784 b"repository content)\n"
5768 )
5785 )
5769 ui.warn(msg)
5786 ui.warn(msg)
5770 return 0
5787 return 0
5771 return 1
5788 return 1
5772
5789
5773
5790
5774 @command(
5791 @command(
5775 b'remove|rm',
5792 b'remove|rm',
5776 [
5793 [
5777 (b'A', b'after', None, _(b'record delete for missing files')),
5794 (b'A', b'after', None, _(b'record delete for missing files')),
5778 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5795 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5779 ]
5796 ]
5780 + subrepoopts
5797 + subrepoopts
5781 + walkopts
5798 + walkopts
5782 + dryrunopts,
5799 + dryrunopts,
5783 _(b'[OPTION]... FILE...'),
5800 _(b'[OPTION]... FILE...'),
5784 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5801 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5785 helpbasic=True,
5802 helpbasic=True,
5786 inferrepo=True,
5803 inferrepo=True,
5787 )
5804 )
5788 def remove(ui, repo, *pats, **opts):
5805 def remove(ui, repo, *pats, **opts):
5789 """remove the specified files on the next commit
5806 """remove the specified files on the next commit
5790
5807
5791 Schedule the indicated files for removal from the current branch.
5808 Schedule the indicated files for removal from the current branch.
5792
5809
5793 This command schedules the files to be removed at the next commit.
5810 This command schedules the files to be removed at the next commit.
5794 To undo a remove before that, see :hg:`revert`. To undo added
5811 To undo a remove before that, see :hg:`revert`. To undo added
5795 files, see :hg:`forget`.
5812 files, see :hg:`forget`.
5796
5813
5797 .. container:: verbose
5814 .. container:: verbose
5798
5815
5799 -A/--after can be used to remove only files that have already
5816 -A/--after can be used to remove only files that have already
5800 been deleted, -f/--force can be used to force deletion, and -Af
5817 been deleted, -f/--force can be used to force deletion, and -Af
5801 can be used to remove files from the next revision without
5818 can be used to remove files from the next revision without
5802 deleting them from the working directory.
5819 deleting them from the working directory.
5803
5820
5804 The following table details the behavior of remove for different
5821 The following table details the behavior of remove for different
5805 file states (columns) and option combinations (rows). The file
5822 file states (columns) and option combinations (rows). The file
5806 states are Added [A], Clean [C], Modified [M] and Missing [!]
5823 states are Added [A], Clean [C], Modified [M] and Missing [!]
5807 (as reported by :hg:`status`). The actions are Warn, Remove
5824 (as reported by :hg:`status`). The actions are Warn, Remove
5808 (from branch) and Delete (from disk):
5825 (from branch) and Delete (from disk):
5809
5826
5810 ========= == == == ==
5827 ========= == == == ==
5811 opt/state A C M !
5828 opt/state A C M !
5812 ========= == == == ==
5829 ========= == == == ==
5813 none W RD W R
5830 none W RD W R
5814 -f R RD RD R
5831 -f R RD RD R
5815 -A W W W R
5832 -A W W W R
5816 -Af R R R R
5833 -Af R R R R
5817 ========= == == == ==
5834 ========= == == == ==
5818
5835
5819 .. note::
5836 .. note::
5820
5837
5821 :hg:`remove` never deletes files in Added [A] state from the
5838 :hg:`remove` never deletes files in Added [A] state from the
5822 working directory, not even if ``--force`` is specified.
5839 working directory, not even if ``--force`` is specified.
5823
5840
5824 Returns 0 on success, 1 if any warnings encountered.
5841 Returns 0 on success, 1 if any warnings encountered.
5825 """
5842 """
5826
5843
5827 opts = pycompat.byteskwargs(opts)
5844 opts = pycompat.byteskwargs(opts)
5828 after, force = opts.get(b'after'), opts.get(b'force')
5845 after, force = opts.get(b'after'), opts.get(b'force')
5829 dryrun = opts.get(b'dry_run')
5846 dryrun = opts.get(b'dry_run')
5830 if not pats and not after:
5847 if not pats and not after:
5831 raise error.InputError(_(b'no files specified'))
5848 raise error.InputError(_(b'no files specified'))
5832
5849
5833 m = scmutil.match(repo[None], pats, opts)
5850 m = scmutil.match(repo[None], pats, opts)
5834 subrepos = opts.get(b'subrepos')
5851 subrepos = opts.get(b'subrepos')
5835 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5852 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5836 return cmdutil.remove(
5853 return cmdutil.remove(
5837 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5854 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5838 )
5855 )
5839
5856
5840
5857
5841 @command(
5858 @command(
5842 b'rename|move|mv',
5859 b'rename|move|mv',
5843 [
5860 [
5844 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5861 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5845 (
5862 (
5846 b'',
5863 b'',
5847 b'at-rev',
5864 b'at-rev',
5848 b'',
5865 b'',
5849 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5866 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5850 _(b'REV'),
5867 _(b'REV'),
5851 ),
5868 ),
5852 (
5869 (
5853 b'f',
5870 b'f',
5854 b'force',
5871 b'force',
5855 None,
5872 None,
5856 _(b'forcibly move over an existing managed file'),
5873 _(b'forcibly move over an existing managed file'),
5857 ),
5874 ),
5858 ]
5875 ]
5859 + walkopts
5876 + walkopts
5860 + dryrunopts,
5877 + dryrunopts,
5861 _(b'[OPTION]... SOURCE... DEST'),
5878 _(b'[OPTION]... SOURCE... DEST'),
5862 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5879 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5863 )
5880 )
5864 def rename(ui, repo, *pats, **opts):
5881 def rename(ui, repo, *pats, **opts):
5865 """rename files; equivalent of copy + remove
5882 """rename files; equivalent of copy + remove
5866
5883
5867 Mark dest as copies of sources; mark sources for deletion. If dest
5884 Mark dest as copies of sources; mark sources for deletion. If dest
5868 is a directory, copies are put in that directory. If dest is a
5885 is a directory, copies are put in that directory. If dest is a
5869 file, there can only be one source.
5886 file, there can only be one source.
5870
5887
5871 By default, this command copies the contents of files as they
5888 By default, this command copies the contents of files as they
5872 exist in the working directory. If invoked with -A/--after, the
5889 exist in the working directory. If invoked with -A/--after, the
5873 operation is recorded, but no copying is performed.
5890 operation is recorded, but no copying is performed.
5874
5891
5875 This command takes effect at the next commit. To undo a rename
5892 This command takes effect at the next commit. To undo a rename
5876 before that, see :hg:`revert`.
5893 before that, see :hg:`revert`.
5877
5894
5878 Returns 0 on success, 1 if errors are encountered.
5895 Returns 0 on success, 1 if errors are encountered.
5879 """
5896 """
5880 opts = pycompat.byteskwargs(opts)
5897 opts = pycompat.byteskwargs(opts)
5881 with repo.wlock():
5898 with repo.wlock():
5882 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5899 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5883
5900
5884
5901
5885 @command(
5902 @command(
5886 b'resolve',
5903 b'resolve',
5887 [
5904 [
5888 (b'a', b'all', None, _(b'select all unresolved files')),
5905 (b'a', b'all', None, _(b'select all unresolved files')),
5889 (b'l', b'list', None, _(b'list state of files needing merge')),
5906 (b'l', b'list', None, _(b'list state of files needing merge')),
5890 (b'm', b'mark', None, _(b'mark files as resolved')),
5907 (b'm', b'mark', None, _(b'mark files as resolved')),
5891 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5908 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5892 (b'n', b'no-status', None, _(b'hide status prefix')),
5909 (b'n', b'no-status', None, _(b'hide status prefix')),
5893 (b'', b're-merge', None, _(b're-merge files')),
5910 (b'', b're-merge', None, _(b're-merge files')),
5894 ]
5911 ]
5895 + mergetoolopts
5912 + mergetoolopts
5896 + walkopts
5913 + walkopts
5897 + formatteropts,
5914 + formatteropts,
5898 _(b'[OPTION]... [FILE]...'),
5915 _(b'[OPTION]... [FILE]...'),
5899 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5916 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5900 inferrepo=True,
5917 inferrepo=True,
5901 )
5918 )
5902 def resolve(ui, repo, *pats, **opts):
5919 def resolve(ui, repo, *pats, **opts):
5903 """redo merges or set/view the merge status of files
5920 """redo merges or set/view the merge status of files
5904
5921
5905 Merges with unresolved conflicts are often the result of
5922 Merges with unresolved conflicts are often the result of
5906 non-interactive merging using the ``internal:merge`` configuration
5923 non-interactive merging using the ``internal:merge`` configuration
5907 setting, or a command-line merge tool like ``diff3``. The resolve
5924 setting, or a command-line merge tool like ``diff3``. The resolve
5908 command is used to manage the files involved in a merge, after
5925 command is used to manage the files involved in a merge, after
5909 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5926 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5910 working directory must have two parents). See :hg:`help
5927 working directory must have two parents). See :hg:`help
5911 merge-tools` for information on configuring merge tools.
5928 merge-tools` for information on configuring merge tools.
5912
5929
5913 The resolve command can be used in the following ways:
5930 The resolve command can be used in the following ways:
5914
5931
5915 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5932 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5916 the specified files, discarding any previous merge attempts. Re-merging
5933 the specified files, discarding any previous merge attempts. Re-merging
5917 is not performed for files already marked as resolved. Use ``--all/-a``
5934 is not performed for files already marked as resolved. Use ``--all/-a``
5918 to select all unresolved files. ``--tool`` can be used to specify
5935 to select all unresolved files. ``--tool`` can be used to specify
5919 the merge tool used for the given files. It overrides the HGMERGE
5936 the merge tool used for the given files. It overrides the HGMERGE
5920 environment variable and your configuration files. Previous file
5937 environment variable and your configuration files. Previous file
5921 contents are saved with a ``.orig`` suffix.
5938 contents are saved with a ``.orig`` suffix.
5922
5939
5923 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5940 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5924 (e.g. after having manually fixed-up the files). The default is
5941 (e.g. after having manually fixed-up the files). The default is
5925 to mark all unresolved files.
5942 to mark all unresolved files.
5926
5943
5927 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5944 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5928 default is to mark all resolved files.
5945 default is to mark all resolved files.
5929
5946
5930 - :hg:`resolve -l`: list files which had or still have conflicts.
5947 - :hg:`resolve -l`: list files which had or still have conflicts.
5931 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5948 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5932 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5949 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5933 the list. See :hg:`help filesets` for details.
5950 the list. See :hg:`help filesets` for details.
5934
5951
5935 .. note::
5952 .. note::
5936
5953
5937 Mercurial will not let you commit files with unresolved merge
5954 Mercurial will not let you commit files with unresolved merge
5938 conflicts. You must use :hg:`resolve -m ...` before you can
5955 conflicts. You must use :hg:`resolve -m ...` before you can
5939 commit after a conflicting merge.
5956 commit after a conflicting merge.
5940
5957
5941 .. container:: verbose
5958 .. container:: verbose
5942
5959
5943 Template:
5960 Template:
5944
5961
5945 The following keywords are supported in addition to the common template
5962 The following keywords are supported in addition to the common template
5946 keywords and functions. See also :hg:`help templates`.
5963 keywords and functions. See also :hg:`help templates`.
5947
5964
5948 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5965 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5949 :path: String. Repository-absolute path of the file.
5966 :path: String. Repository-absolute path of the file.
5950
5967
5951 Returns 0 on success, 1 if any files fail a resolve attempt.
5968 Returns 0 on success, 1 if any files fail a resolve attempt.
5952 """
5969 """
5953
5970
5954 opts = pycompat.byteskwargs(opts)
5971 opts = pycompat.byteskwargs(opts)
5955 confirm = ui.configbool(b'commands', b'resolve.confirm')
5972 confirm = ui.configbool(b'commands', b'resolve.confirm')
5956 flaglist = b'all mark unmark list no_status re_merge'.split()
5973 flaglist = b'all mark unmark list no_status re_merge'.split()
5957 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5974 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5958
5975
5959 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5976 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5960 if actioncount > 1:
5977 if actioncount > 1:
5961 raise error.InputError(_(b"too many actions specified"))
5978 raise error.InputError(_(b"too many actions specified"))
5962 elif actioncount == 0 and ui.configbool(
5979 elif actioncount == 0 and ui.configbool(
5963 b'commands', b'resolve.explicit-re-merge'
5980 b'commands', b'resolve.explicit-re-merge'
5964 ):
5981 ):
5965 hint = _(b'use --mark, --unmark, --list or --re-merge')
5982 hint = _(b'use --mark, --unmark, --list or --re-merge')
5966 raise error.InputError(_(b'no action specified'), hint=hint)
5983 raise error.InputError(_(b'no action specified'), hint=hint)
5967 if pats and all:
5984 if pats and all:
5968 raise error.InputError(_(b"can't specify --all and patterns"))
5985 raise error.InputError(_(b"can't specify --all and patterns"))
5969 if not (all or pats or show or mark or unmark):
5986 if not (all or pats or show or mark or unmark):
5970 raise error.InputError(
5987 raise error.InputError(
5971 _(b'no files or directories specified'),
5988 _(b'no files or directories specified'),
5972 hint=b'use --all to re-merge all unresolved files',
5989 hint=b'use --all to re-merge all unresolved files',
5973 )
5990 )
5974
5991
5975 if confirm:
5992 if confirm:
5976 if all:
5993 if all:
5977 if ui.promptchoice(
5994 if ui.promptchoice(
5978 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5995 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5979 ):
5996 ):
5980 raise error.CanceledError(_(b'user quit'))
5997 raise error.CanceledError(_(b'user quit'))
5981 if mark and not pats:
5998 if mark and not pats:
5982 if ui.promptchoice(
5999 if ui.promptchoice(
5983 _(
6000 _(
5984 b'mark all unresolved files as resolved (yn)?'
6001 b'mark all unresolved files as resolved (yn)?'
5985 b'$$ &Yes $$ &No'
6002 b'$$ &Yes $$ &No'
5986 )
6003 )
5987 ):
6004 ):
5988 raise error.CanceledError(_(b'user quit'))
6005 raise error.CanceledError(_(b'user quit'))
5989 if unmark and not pats:
6006 if unmark and not pats:
5990 if ui.promptchoice(
6007 if ui.promptchoice(
5991 _(
6008 _(
5992 b'mark all resolved files as unresolved (yn)?'
6009 b'mark all resolved files as unresolved (yn)?'
5993 b'$$ &Yes $$ &No'
6010 b'$$ &Yes $$ &No'
5994 )
6011 )
5995 ):
6012 ):
5996 raise error.CanceledError(_(b'user quit'))
6013 raise error.CanceledError(_(b'user quit'))
5997
6014
5998 uipathfn = scmutil.getuipathfn(repo)
6015 uipathfn = scmutil.getuipathfn(repo)
5999
6016
6000 if show:
6017 if show:
6001 ui.pager(b'resolve')
6018 ui.pager(b'resolve')
6002 fm = ui.formatter(b'resolve', opts)
6019 fm = ui.formatter(b'resolve', opts)
6003 ms = mergestatemod.mergestate.read(repo)
6020 ms = mergestatemod.mergestate.read(repo)
6004 wctx = repo[None]
6021 wctx = repo[None]
6005 m = scmutil.match(wctx, pats, opts)
6022 m = scmutil.match(wctx, pats, opts)
6006
6023
6007 # Labels and keys based on merge state. Unresolved path conflicts show
6024 # Labels and keys based on merge state. Unresolved path conflicts show
6008 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6025 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6009 # resolved conflicts.
6026 # resolved conflicts.
6010 mergestateinfo = {
6027 mergestateinfo = {
6011 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6028 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6012 b'resolve.unresolved',
6029 b'resolve.unresolved',
6013 b'U',
6030 b'U',
6014 ),
6031 ),
6015 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6032 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6016 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6033 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6017 b'resolve.unresolved',
6034 b'resolve.unresolved',
6018 b'P',
6035 b'P',
6019 ),
6036 ),
6020 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6037 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6021 b'resolve.resolved',
6038 b'resolve.resolved',
6022 b'R',
6039 b'R',
6023 ),
6040 ),
6024 }
6041 }
6025
6042
6026 for f in ms:
6043 for f in ms:
6027 if not m(f):
6044 if not m(f):
6028 continue
6045 continue
6029
6046
6030 label, key = mergestateinfo[ms[f]]
6047 label, key = mergestateinfo[ms[f]]
6031 fm.startitem()
6048 fm.startitem()
6032 fm.context(ctx=wctx)
6049 fm.context(ctx=wctx)
6033 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6050 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6034 fm.data(path=f)
6051 fm.data(path=f)
6035 fm.plain(b'%s\n' % uipathfn(f), label=label)
6052 fm.plain(b'%s\n' % uipathfn(f), label=label)
6036 fm.end()
6053 fm.end()
6037 return 0
6054 return 0
6038
6055
6039 with repo.wlock():
6056 with repo.wlock():
6040 ms = mergestatemod.mergestate.read(repo)
6057 ms = mergestatemod.mergestate.read(repo)
6041
6058
6042 if not (ms.active() or repo.dirstate.p2() != nullid):
6059 if not (ms.active() or repo.dirstate.p2() != nullid):
6043 raise error.StateError(
6060 raise error.StateError(
6044 _(b'resolve command not applicable when not merging')
6061 _(b'resolve command not applicable when not merging')
6045 )
6062 )
6046
6063
6047 wctx = repo[None]
6064 wctx = repo[None]
6048 m = scmutil.match(wctx, pats, opts)
6065 m = scmutil.match(wctx, pats, opts)
6049 ret = 0
6066 ret = 0
6050 didwork = False
6067 didwork = False
6051
6068
6052 tocomplete = []
6069 tocomplete = []
6053 hasconflictmarkers = []
6070 hasconflictmarkers = []
6054 if mark:
6071 if mark:
6055 markcheck = ui.config(b'commands', b'resolve.mark-check')
6072 markcheck = ui.config(b'commands', b'resolve.mark-check')
6056 if markcheck not in [b'warn', b'abort']:
6073 if markcheck not in [b'warn', b'abort']:
6057 # Treat all invalid / unrecognized values as 'none'.
6074 # Treat all invalid / unrecognized values as 'none'.
6058 markcheck = False
6075 markcheck = False
6059 for f in ms:
6076 for f in ms:
6060 if not m(f):
6077 if not m(f):
6061 continue
6078 continue
6062
6079
6063 didwork = True
6080 didwork = True
6064
6081
6065 # path conflicts must be resolved manually
6082 # path conflicts must be resolved manually
6066 if ms[f] in (
6083 if ms[f] in (
6067 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6084 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6068 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6085 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6069 ):
6086 ):
6070 if mark:
6087 if mark:
6071 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6088 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6072 elif unmark:
6089 elif unmark:
6073 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6090 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6074 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6091 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6075 ui.warn(
6092 ui.warn(
6076 _(b'%s: path conflict must be resolved manually\n')
6093 _(b'%s: path conflict must be resolved manually\n')
6077 % uipathfn(f)
6094 % uipathfn(f)
6078 )
6095 )
6079 continue
6096 continue
6080
6097
6081 if mark:
6098 if mark:
6082 if markcheck:
6099 if markcheck:
6083 fdata = repo.wvfs.tryread(f)
6100 fdata = repo.wvfs.tryread(f)
6084 if (
6101 if (
6085 filemerge.hasconflictmarkers(fdata)
6102 filemerge.hasconflictmarkers(fdata)
6086 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6103 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6087 ):
6104 ):
6088 hasconflictmarkers.append(f)
6105 hasconflictmarkers.append(f)
6089 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6106 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6090 elif unmark:
6107 elif unmark:
6091 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6108 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6092 else:
6109 else:
6093 # backup pre-resolve (merge uses .orig for its own purposes)
6110 # backup pre-resolve (merge uses .orig for its own purposes)
6094 a = repo.wjoin(f)
6111 a = repo.wjoin(f)
6095 try:
6112 try:
6096 util.copyfile(a, a + b".resolve")
6113 util.copyfile(a, a + b".resolve")
6097 except (IOError, OSError) as inst:
6114 except (IOError, OSError) as inst:
6098 if inst.errno != errno.ENOENT:
6115 if inst.errno != errno.ENOENT:
6099 raise
6116 raise
6100
6117
6101 try:
6118 try:
6102 # preresolve file
6119 # preresolve file
6103 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6120 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6104 with ui.configoverride(overrides, b'resolve'):
6121 with ui.configoverride(overrides, b'resolve'):
6105 complete, r = ms.preresolve(f, wctx)
6122 complete, r = ms.preresolve(f, wctx)
6106 if not complete:
6123 if not complete:
6107 tocomplete.append(f)
6124 tocomplete.append(f)
6108 elif r:
6125 elif r:
6109 ret = 1
6126 ret = 1
6110 finally:
6127 finally:
6111 ms.commit()
6128 ms.commit()
6112
6129
6113 # replace filemerge's .orig file with our resolve file, but only
6130 # replace filemerge's .orig file with our resolve file, but only
6114 # for merges that are complete
6131 # for merges that are complete
6115 if complete:
6132 if complete:
6116 try:
6133 try:
6117 util.rename(
6134 util.rename(
6118 a + b".resolve", scmutil.backuppath(ui, repo, f)
6135 a + b".resolve", scmutil.backuppath(ui, repo, f)
6119 )
6136 )
6120 except OSError as inst:
6137 except OSError as inst:
6121 if inst.errno != errno.ENOENT:
6138 if inst.errno != errno.ENOENT:
6122 raise
6139 raise
6123
6140
6124 if hasconflictmarkers:
6141 if hasconflictmarkers:
6125 ui.warn(
6142 ui.warn(
6126 _(
6143 _(
6127 b'warning: the following files still have conflict '
6144 b'warning: the following files still have conflict '
6128 b'markers:\n'
6145 b'markers:\n'
6129 )
6146 )
6130 + b''.join(
6147 + b''.join(
6131 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6148 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6132 )
6149 )
6133 )
6150 )
6134 if markcheck == b'abort' and not all and not pats:
6151 if markcheck == b'abort' and not all and not pats:
6135 raise error.StateError(
6152 raise error.StateError(
6136 _(b'conflict markers detected'),
6153 _(b'conflict markers detected'),
6137 hint=_(b'use --all to mark anyway'),
6154 hint=_(b'use --all to mark anyway'),
6138 )
6155 )
6139
6156
6140 for f in tocomplete:
6157 for f in tocomplete:
6141 try:
6158 try:
6142 # resolve file
6159 # resolve file
6143 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6160 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6144 with ui.configoverride(overrides, b'resolve'):
6161 with ui.configoverride(overrides, b'resolve'):
6145 r = ms.resolve(f, wctx)
6162 r = ms.resolve(f, wctx)
6146 if r:
6163 if r:
6147 ret = 1
6164 ret = 1
6148 finally:
6165 finally:
6149 ms.commit()
6166 ms.commit()
6150
6167
6151 # replace filemerge's .orig file with our resolve file
6168 # replace filemerge's .orig file with our resolve file
6152 a = repo.wjoin(f)
6169 a = repo.wjoin(f)
6153 try:
6170 try:
6154 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6171 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6155 except OSError as inst:
6172 except OSError as inst:
6156 if inst.errno != errno.ENOENT:
6173 if inst.errno != errno.ENOENT:
6157 raise
6174 raise
6158
6175
6159 ms.commit()
6176 ms.commit()
6160 branchmerge = repo.dirstate.p2() != nullid
6177 branchmerge = repo.dirstate.p2() != nullid
6161 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6178 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6162
6179
6163 if not didwork and pats:
6180 if not didwork and pats:
6164 hint = None
6181 hint = None
6165 if not any([p for p in pats if p.find(b':') >= 0]):
6182 if not any([p for p in pats if p.find(b':') >= 0]):
6166 pats = [b'path:%s' % p for p in pats]
6183 pats = [b'path:%s' % p for p in pats]
6167 m = scmutil.match(wctx, pats, opts)
6184 m = scmutil.match(wctx, pats, opts)
6168 for f in ms:
6185 for f in ms:
6169 if not m(f):
6186 if not m(f):
6170 continue
6187 continue
6171
6188
6172 def flag(o):
6189 def flag(o):
6173 if o == b're_merge':
6190 if o == b're_merge':
6174 return b'--re-merge '
6191 return b'--re-merge '
6175 return b'-%s ' % o[0:1]
6192 return b'-%s ' % o[0:1]
6176
6193
6177 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6194 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6178 hint = _(b"(try: hg resolve %s%s)\n") % (
6195 hint = _(b"(try: hg resolve %s%s)\n") % (
6179 flags,
6196 flags,
6180 b' '.join(pats),
6197 b' '.join(pats),
6181 )
6198 )
6182 break
6199 break
6183 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6200 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6184 if hint:
6201 if hint:
6185 ui.warn(hint)
6202 ui.warn(hint)
6186
6203
6187 unresolvedf = ms.unresolvedcount()
6204 unresolvedf = ms.unresolvedcount()
6188 if not unresolvedf:
6205 if not unresolvedf:
6189 ui.status(_(b'(no more unresolved files)\n'))
6206 ui.status(_(b'(no more unresolved files)\n'))
6190 cmdutil.checkafterresolved(repo)
6207 cmdutil.checkafterresolved(repo)
6191
6208
6192 return ret
6209 return ret
6193
6210
6194
6211
6195 @command(
6212 @command(
6196 b'revert',
6213 b'revert',
6197 [
6214 [
6198 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6215 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6199 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6216 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6200 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6217 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6201 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6218 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6202 (b'i', b'interactive', None, _(b'interactively select the changes')),
6219 (b'i', b'interactive', None, _(b'interactively select the changes')),
6203 ]
6220 ]
6204 + walkopts
6221 + walkopts
6205 + dryrunopts,
6222 + dryrunopts,
6206 _(b'[OPTION]... [-r REV] [NAME]...'),
6223 _(b'[OPTION]... [-r REV] [NAME]...'),
6207 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6224 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6208 )
6225 )
6209 def revert(ui, repo, *pats, **opts):
6226 def revert(ui, repo, *pats, **opts):
6210 """restore files to their checkout state
6227 """restore files to their checkout state
6211
6228
6212 .. note::
6229 .. note::
6213
6230
6214 To check out earlier revisions, you should use :hg:`update REV`.
6231 To check out earlier revisions, you should use :hg:`update REV`.
6215 To cancel an uncommitted merge (and lose your changes),
6232 To cancel an uncommitted merge (and lose your changes),
6216 use :hg:`merge --abort`.
6233 use :hg:`merge --abort`.
6217
6234
6218 With no revision specified, revert the specified files or directories
6235 With no revision specified, revert the specified files or directories
6219 to the contents they had in the parent of the working directory.
6236 to the contents they had in the parent of the working directory.
6220 This restores the contents of files to an unmodified
6237 This restores the contents of files to an unmodified
6221 state and unschedules adds, removes, copies, and renames. If the
6238 state and unschedules adds, removes, copies, and renames. If the
6222 working directory has two parents, you must explicitly specify a
6239 working directory has two parents, you must explicitly specify a
6223 revision.
6240 revision.
6224
6241
6225 Using the -r/--rev or -d/--date options, revert the given files or
6242 Using the -r/--rev or -d/--date options, revert the given files or
6226 directories to their states as of a specific revision. Because
6243 directories to their states as of a specific revision. Because
6227 revert does not change the working directory parents, this will
6244 revert does not change the working directory parents, this will
6228 cause these files to appear modified. This can be helpful to "back
6245 cause these files to appear modified. This can be helpful to "back
6229 out" some or all of an earlier change. See :hg:`backout` for a
6246 out" some or all of an earlier change. See :hg:`backout` for a
6230 related method.
6247 related method.
6231
6248
6232 Modified files are saved with a .orig suffix before reverting.
6249 Modified files are saved with a .orig suffix before reverting.
6233 To disable these backups, use --no-backup. It is possible to store
6250 To disable these backups, use --no-backup. It is possible to store
6234 the backup files in a custom directory relative to the root of the
6251 the backup files in a custom directory relative to the root of the
6235 repository by setting the ``ui.origbackuppath`` configuration
6252 repository by setting the ``ui.origbackuppath`` configuration
6236 option.
6253 option.
6237
6254
6238 See :hg:`help dates` for a list of formats valid for -d/--date.
6255 See :hg:`help dates` for a list of formats valid for -d/--date.
6239
6256
6240 See :hg:`help backout` for a way to reverse the effect of an
6257 See :hg:`help backout` for a way to reverse the effect of an
6241 earlier changeset.
6258 earlier changeset.
6242
6259
6243 Returns 0 on success.
6260 Returns 0 on success.
6244 """
6261 """
6245
6262
6246 opts = pycompat.byteskwargs(opts)
6263 opts = pycompat.byteskwargs(opts)
6247 if opts.get(b"date"):
6264 if opts.get(b"date"):
6248 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6265 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6249 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6266 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6250
6267
6251 parent, p2 = repo.dirstate.parents()
6268 parent, p2 = repo.dirstate.parents()
6252 if not opts.get(b'rev') and p2 != nullid:
6269 if not opts.get(b'rev') and p2 != nullid:
6253 # revert after merge is a trap for new users (issue2915)
6270 # revert after merge is a trap for new users (issue2915)
6254 raise error.InputError(
6271 raise error.InputError(
6255 _(b'uncommitted merge with no revision specified'),
6272 _(b'uncommitted merge with no revision specified'),
6256 hint=_(b"use 'hg update' or see 'hg help revert'"),
6273 hint=_(b"use 'hg update' or see 'hg help revert'"),
6257 )
6274 )
6258
6275
6259 rev = opts.get(b'rev')
6276 rev = opts.get(b'rev')
6260 if rev:
6277 if rev:
6261 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6278 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6262 ctx = scmutil.revsingle(repo, rev)
6279 ctx = scmutil.revsingle(repo, rev)
6263
6280
6264 if not (
6281 if not (
6265 pats
6282 pats
6266 or opts.get(b'include')
6283 or opts.get(b'include')
6267 or opts.get(b'exclude')
6284 or opts.get(b'exclude')
6268 or opts.get(b'all')
6285 or opts.get(b'all')
6269 or opts.get(b'interactive')
6286 or opts.get(b'interactive')
6270 ):
6287 ):
6271 msg = _(b"no files or directories specified")
6288 msg = _(b"no files or directories specified")
6272 if p2 != nullid:
6289 if p2 != nullid:
6273 hint = _(
6290 hint = _(
6274 b"uncommitted merge, use --all to discard all changes,"
6291 b"uncommitted merge, use --all to discard all changes,"
6275 b" or 'hg update -C .' to abort the merge"
6292 b" or 'hg update -C .' to abort the merge"
6276 )
6293 )
6277 raise error.InputError(msg, hint=hint)
6294 raise error.InputError(msg, hint=hint)
6278 dirty = any(repo.status())
6295 dirty = any(repo.status())
6279 node = ctx.node()
6296 node = ctx.node()
6280 if node != parent:
6297 if node != parent:
6281 if dirty:
6298 if dirty:
6282 hint = (
6299 hint = (
6283 _(
6300 _(
6284 b"uncommitted changes, use --all to discard all"
6301 b"uncommitted changes, use --all to discard all"
6285 b" changes, or 'hg update %d' to update"
6302 b" changes, or 'hg update %d' to update"
6286 )
6303 )
6287 % ctx.rev()
6304 % ctx.rev()
6288 )
6305 )
6289 else:
6306 else:
6290 hint = (
6307 hint = (
6291 _(
6308 _(
6292 b"use --all to revert all files,"
6309 b"use --all to revert all files,"
6293 b" or 'hg update %d' to update"
6310 b" or 'hg update %d' to update"
6294 )
6311 )
6295 % ctx.rev()
6312 % ctx.rev()
6296 )
6313 )
6297 elif dirty:
6314 elif dirty:
6298 hint = _(b"uncommitted changes, use --all to discard all changes")
6315 hint = _(b"uncommitted changes, use --all to discard all changes")
6299 else:
6316 else:
6300 hint = _(b"use --all to revert all files")
6317 hint = _(b"use --all to revert all files")
6301 raise error.InputError(msg, hint=hint)
6318 raise error.InputError(msg, hint=hint)
6302
6319
6303 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6320 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6304
6321
6305
6322
6306 @command(
6323 @command(
6307 b'rollback',
6324 b'rollback',
6308 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6325 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6309 helpcategory=command.CATEGORY_MAINTENANCE,
6326 helpcategory=command.CATEGORY_MAINTENANCE,
6310 )
6327 )
6311 def rollback(ui, repo, **opts):
6328 def rollback(ui, repo, **opts):
6312 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6329 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6313
6330
6314 Please use :hg:`commit --amend` instead of rollback to correct
6331 Please use :hg:`commit --amend` instead of rollback to correct
6315 mistakes in the last commit.
6332 mistakes in the last commit.
6316
6333
6317 This command should be used with care. There is only one level of
6334 This command should be used with care. There is only one level of
6318 rollback, and there is no way to undo a rollback. It will also
6335 rollback, and there is no way to undo a rollback. It will also
6319 restore the dirstate at the time of the last transaction, losing
6336 restore the dirstate at the time of the last transaction, losing
6320 any dirstate changes since that time. This command does not alter
6337 any dirstate changes since that time. This command does not alter
6321 the working directory.
6338 the working directory.
6322
6339
6323 Transactions are used to encapsulate the effects of all commands
6340 Transactions are used to encapsulate the effects of all commands
6324 that create new changesets or propagate existing changesets into a
6341 that create new changesets or propagate existing changesets into a
6325 repository.
6342 repository.
6326
6343
6327 .. container:: verbose
6344 .. container:: verbose
6328
6345
6329 For example, the following commands are transactional, and their
6346 For example, the following commands are transactional, and their
6330 effects can be rolled back:
6347 effects can be rolled back:
6331
6348
6332 - commit
6349 - commit
6333 - import
6350 - import
6334 - pull
6351 - pull
6335 - push (with this repository as the destination)
6352 - push (with this repository as the destination)
6336 - unbundle
6353 - unbundle
6337
6354
6338 To avoid permanent data loss, rollback will refuse to rollback a
6355 To avoid permanent data loss, rollback will refuse to rollback a
6339 commit transaction if it isn't checked out. Use --force to
6356 commit transaction if it isn't checked out. Use --force to
6340 override this protection.
6357 override this protection.
6341
6358
6342 The rollback command can be entirely disabled by setting the
6359 The rollback command can be entirely disabled by setting the
6343 ``ui.rollback`` configuration setting to false. If you're here
6360 ``ui.rollback`` configuration setting to false. If you're here
6344 because you want to use rollback and it's disabled, you can
6361 because you want to use rollback and it's disabled, you can
6345 re-enable the command by setting ``ui.rollback`` to true.
6362 re-enable the command by setting ``ui.rollback`` to true.
6346
6363
6347 This command is not intended for use on public repositories. Once
6364 This command is not intended for use on public repositories. Once
6348 changes are visible for pull by other users, rolling a transaction
6365 changes are visible for pull by other users, rolling a transaction
6349 back locally is ineffective (someone else may already have pulled
6366 back locally is ineffective (someone else may already have pulled
6350 the changes). Furthermore, a race is possible with readers of the
6367 the changes). Furthermore, a race is possible with readers of the
6351 repository; for example an in-progress pull from the repository
6368 repository; for example an in-progress pull from the repository
6352 may fail if a rollback is performed.
6369 may fail if a rollback is performed.
6353
6370
6354 Returns 0 on success, 1 if no rollback data is available.
6371 Returns 0 on success, 1 if no rollback data is available.
6355 """
6372 """
6356 if not ui.configbool(b'ui', b'rollback'):
6373 if not ui.configbool(b'ui', b'rollback'):
6357 raise error.Abort(
6374 raise error.Abort(
6358 _(b'rollback is disabled because it is unsafe'),
6375 _(b'rollback is disabled because it is unsafe'),
6359 hint=b'see `hg help -v rollback` for information',
6376 hint=b'see `hg help -v rollback` for information',
6360 )
6377 )
6361 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6378 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6362
6379
6363
6380
6364 @command(
6381 @command(
6365 b'root',
6382 b'root',
6366 [] + formatteropts,
6383 [] + formatteropts,
6367 intents={INTENT_READONLY},
6384 intents={INTENT_READONLY},
6368 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6385 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6369 )
6386 )
6370 def root(ui, repo, **opts):
6387 def root(ui, repo, **opts):
6371 """print the root (top) of the current working directory
6388 """print the root (top) of the current working directory
6372
6389
6373 Print the root directory of the current repository.
6390 Print the root directory of the current repository.
6374
6391
6375 .. container:: verbose
6392 .. container:: verbose
6376
6393
6377 Template:
6394 Template:
6378
6395
6379 The following keywords are supported in addition to the common template
6396 The following keywords are supported in addition to the common template
6380 keywords and functions. See also :hg:`help templates`.
6397 keywords and functions. See also :hg:`help templates`.
6381
6398
6382 :hgpath: String. Path to the .hg directory.
6399 :hgpath: String. Path to the .hg directory.
6383 :storepath: String. Path to the directory holding versioned data.
6400 :storepath: String. Path to the directory holding versioned data.
6384
6401
6385 Returns 0 on success.
6402 Returns 0 on success.
6386 """
6403 """
6387 opts = pycompat.byteskwargs(opts)
6404 opts = pycompat.byteskwargs(opts)
6388 with ui.formatter(b'root', opts) as fm:
6405 with ui.formatter(b'root', opts) as fm:
6389 fm.startitem()
6406 fm.startitem()
6390 fm.write(b'reporoot', b'%s\n', repo.root)
6407 fm.write(b'reporoot', b'%s\n', repo.root)
6391 fm.data(hgpath=repo.path, storepath=repo.spath)
6408 fm.data(hgpath=repo.path, storepath=repo.spath)
6392
6409
6393
6410
6394 @command(
6411 @command(
6395 b'serve',
6412 b'serve',
6396 [
6413 [
6397 (
6414 (
6398 b'A',
6415 b'A',
6399 b'accesslog',
6416 b'accesslog',
6400 b'',
6417 b'',
6401 _(b'name of access log file to write to'),
6418 _(b'name of access log file to write to'),
6402 _(b'FILE'),
6419 _(b'FILE'),
6403 ),
6420 ),
6404 (b'd', b'daemon', None, _(b'run server in background')),
6421 (b'd', b'daemon', None, _(b'run server in background')),
6405 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6422 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6406 (
6423 (
6407 b'E',
6424 b'E',
6408 b'errorlog',
6425 b'errorlog',
6409 b'',
6426 b'',
6410 _(b'name of error log file to write to'),
6427 _(b'name of error log file to write to'),
6411 _(b'FILE'),
6428 _(b'FILE'),
6412 ),
6429 ),
6413 # use string type, then we can check if something was passed
6430 # use string type, then we can check if something was passed
6414 (
6431 (
6415 b'p',
6432 b'p',
6416 b'port',
6433 b'port',
6417 b'',
6434 b'',
6418 _(b'port to listen on (default: 8000)'),
6435 _(b'port to listen on (default: 8000)'),
6419 _(b'PORT'),
6436 _(b'PORT'),
6420 ),
6437 ),
6421 (
6438 (
6422 b'a',
6439 b'a',
6423 b'address',
6440 b'address',
6424 b'',
6441 b'',
6425 _(b'address to listen on (default: all interfaces)'),
6442 _(b'address to listen on (default: all interfaces)'),
6426 _(b'ADDR'),
6443 _(b'ADDR'),
6427 ),
6444 ),
6428 (
6445 (
6429 b'',
6446 b'',
6430 b'prefix',
6447 b'prefix',
6431 b'',
6448 b'',
6432 _(b'prefix path to serve from (default: server root)'),
6449 _(b'prefix path to serve from (default: server root)'),
6433 _(b'PREFIX'),
6450 _(b'PREFIX'),
6434 ),
6451 ),
6435 (
6452 (
6436 b'n',
6453 b'n',
6437 b'name',
6454 b'name',
6438 b'',
6455 b'',
6439 _(b'name to show in web pages (default: working directory)'),
6456 _(b'name to show in web pages (default: working directory)'),
6440 _(b'NAME'),
6457 _(b'NAME'),
6441 ),
6458 ),
6442 (
6459 (
6443 b'',
6460 b'',
6444 b'web-conf',
6461 b'web-conf',
6445 b'',
6462 b'',
6446 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6463 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6447 _(b'FILE'),
6464 _(b'FILE'),
6448 ),
6465 ),
6449 (
6466 (
6450 b'',
6467 b'',
6451 b'webdir-conf',
6468 b'webdir-conf',
6452 b'',
6469 b'',
6453 _(b'name of the hgweb config file (DEPRECATED)'),
6470 _(b'name of the hgweb config file (DEPRECATED)'),
6454 _(b'FILE'),
6471 _(b'FILE'),
6455 ),
6472 ),
6456 (
6473 (
6457 b'',
6474 b'',
6458 b'pid-file',
6475 b'pid-file',
6459 b'',
6476 b'',
6460 _(b'name of file to write process ID to'),
6477 _(b'name of file to write process ID to'),
6461 _(b'FILE'),
6478 _(b'FILE'),
6462 ),
6479 ),
6463 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6480 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6464 (
6481 (
6465 b'',
6482 b'',
6466 b'cmdserver',
6483 b'cmdserver',
6467 b'',
6484 b'',
6468 _(b'for remote clients (ADVANCED)'),
6485 _(b'for remote clients (ADVANCED)'),
6469 _(b'MODE'),
6486 _(b'MODE'),
6470 ),
6487 ),
6471 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6488 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6472 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6489 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6473 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6490 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6474 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6491 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6475 (b'', b'print-url', None, _(b'start and print only the URL')),
6492 (b'', b'print-url', None, _(b'start and print only the URL')),
6476 ]
6493 ]
6477 + subrepoopts,
6494 + subrepoopts,
6478 _(b'[OPTION]...'),
6495 _(b'[OPTION]...'),
6479 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6496 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6480 helpbasic=True,
6497 helpbasic=True,
6481 optionalrepo=True,
6498 optionalrepo=True,
6482 )
6499 )
6483 def serve(ui, repo, **opts):
6500 def serve(ui, repo, **opts):
6484 """start stand-alone webserver
6501 """start stand-alone webserver
6485
6502
6486 Start a local HTTP repository browser and pull server. You can use
6503 Start a local HTTP repository browser and pull server. You can use
6487 this for ad-hoc sharing and browsing of repositories. It is
6504 this for ad-hoc sharing and browsing of repositories. It is
6488 recommended to use a real web server to serve a repository for
6505 recommended to use a real web server to serve a repository for
6489 longer periods of time.
6506 longer periods of time.
6490
6507
6491 Please note that the server does not implement access control.
6508 Please note that the server does not implement access control.
6492 This means that, by default, anybody can read from the server and
6509 This means that, by default, anybody can read from the server and
6493 nobody can write to it by default. Set the ``web.allow-push``
6510 nobody can write to it by default. Set the ``web.allow-push``
6494 option to ``*`` to allow everybody to push to the server. You
6511 option to ``*`` to allow everybody to push to the server. You
6495 should use a real web server if you need to authenticate users.
6512 should use a real web server if you need to authenticate users.
6496
6513
6497 By default, the server logs accesses to stdout and errors to
6514 By default, the server logs accesses to stdout and errors to
6498 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6515 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6499 files.
6516 files.
6500
6517
6501 To have the server choose a free port number to listen on, specify
6518 To have the server choose a free port number to listen on, specify
6502 a port number of 0; in this case, the server will print the port
6519 a port number of 0; in this case, the server will print the port
6503 number it uses.
6520 number it uses.
6504
6521
6505 Returns 0 on success.
6522 Returns 0 on success.
6506 """
6523 """
6507
6524
6508 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6525 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6509 opts = pycompat.byteskwargs(opts)
6526 opts = pycompat.byteskwargs(opts)
6510 if opts[b"print_url"] and ui.verbose:
6527 if opts[b"print_url"] and ui.verbose:
6511 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6528 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6512
6529
6513 if opts[b"stdio"]:
6530 if opts[b"stdio"]:
6514 if repo is None:
6531 if repo is None:
6515 raise error.RepoError(
6532 raise error.RepoError(
6516 _(b"there is no Mercurial repository here (.hg not found)")
6533 _(b"there is no Mercurial repository here (.hg not found)")
6517 )
6534 )
6518 s = wireprotoserver.sshserver(ui, repo)
6535 s = wireprotoserver.sshserver(ui, repo)
6519 s.serve_forever()
6536 s.serve_forever()
6520 return
6537 return
6521
6538
6522 service = server.createservice(ui, repo, opts)
6539 service = server.createservice(ui, repo, opts)
6523 return server.runservice(opts, initfn=service.init, runfn=service.run)
6540 return server.runservice(opts, initfn=service.init, runfn=service.run)
6524
6541
6525
6542
6526 @command(
6543 @command(
6527 b'shelve',
6544 b'shelve',
6528 [
6545 [
6529 (
6546 (
6530 b'A',
6547 b'A',
6531 b'addremove',
6548 b'addremove',
6532 None,
6549 None,
6533 _(b'mark new/missing files as added/removed before shelving'),
6550 _(b'mark new/missing files as added/removed before shelving'),
6534 ),
6551 ),
6535 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6552 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6536 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6553 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6537 (
6554 (
6538 b'',
6555 b'',
6539 b'date',
6556 b'date',
6540 b'',
6557 b'',
6541 _(b'shelve with the specified commit date'),
6558 _(b'shelve with the specified commit date'),
6542 _(b'DATE'),
6559 _(b'DATE'),
6543 ),
6560 ),
6544 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6561 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6545 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6562 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6546 (
6563 (
6547 b'k',
6564 b'k',
6548 b'keep',
6565 b'keep',
6549 False,
6566 False,
6550 _(b'shelve, but keep changes in the working directory'),
6567 _(b'shelve, but keep changes in the working directory'),
6551 ),
6568 ),
6552 (b'l', b'list', None, _(b'list current shelves')),
6569 (b'l', b'list', None, _(b'list current shelves')),
6553 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6570 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6554 (
6571 (
6555 b'n',
6572 b'n',
6556 b'name',
6573 b'name',
6557 b'',
6574 b'',
6558 _(b'use the given name for the shelved commit'),
6575 _(b'use the given name for the shelved commit'),
6559 _(b'NAME'),
6576 _(b'NAME'),
6560 ),
6577 ),
6561 (
6578 (
6562 b'p',
6579 b'p',
6563 b'patch',
6580 b'patch',
6564 None,
6581 None,
6565 _(
6582 _(
6566 b'output patches for changes (provide the names of the shelved '
6583 b'output patches for changes (provide the names of the shelved '
6567 b'changes as positional arguments)'
6584 b'changes as positional arguments)'
6568 ),
6585 ),
6569 ),
6586 ),
6570 (b'i', b'interactive', None, _(b'interactive mode')),
6587 (b'i', b'interactive', None, _(b'interactive mode')),
6571 (
6588 (
6572 b'',
6589 b'',
6573 b'stat',
6590 b'stat',
6574 None,
6591 None,
6575 _(
6592 _(
6576 b'output diffstat-style summary of changes (provide the names of '
6593 b'output diffstat-style summary of changes (provide the names of '
6577 b'the shelved changes as positional arguments)'
6594 b'the shelved changes as positional arguments)'
6578 ),
6595 ),
6579 ),
6596 ),
6580 ]
6597 ]
6581 + cmdutil.walkopts,
6598 + cmdutil.walkopts,
6582 _(b'hg shelve [OPTION]... [FILE]...'),
6599 _(b'hg shelve [OPTION]... [FILE]...'),
6583 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6600 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6584 )
6601 )
6585 def shelve(ui, repo, *pats, **opts):
6602 def shelve(ui, repo, *pats, **opts):
6586 """save and set aside changes from the working directory
6603 """save and set aside changes from the working directory
6587
6604
6588 Shelving takes files that "hg status" reports as not clean, saves
6605 Shelving takes files that "hg status" reports as not clean, saves
6589 the modifications to a bundle (a shelved change), and reverts the
6606 the modifications to a bundle (a shelved change), and reverts the
6590 files so that their state in the working directory becomes clean.
6607 files so that their state in the working directory becomes clean.
6591
6608
6592 To restore these changes to the working directory, using "hg
6609 To restore these changes to the working directory, using "hg
6593 unshelve"; this will work even if you switch to a different
6610 unshelve"; this will work even if you switch to a different
6594 commit.
6611 commit.
6595
6612
6596 When no files are specified, "hg shelve" saves all not-clean
6613 When no files are specified, "hg shelve" saves all not-clean
6597 files. If specific files or directories are named, only changes to
6614 files. If specific files or directories are named, only changes to
6598 those files are shelved.
6615 those files are shelved.
6599
6616
6600 In bare shelve (when no files are specified, without interactive,
6617 In bare shelve (when no files are specified, without interactive,
6601 include and exclude option), shelving remembers information if the
6618 include and exclude option), shelving remembers information if the
6602 working directory was on newly created branch, in other words working
6619 working directory was on newly created branch, in other words working
6603 directory was on different branch than its first parent. In this
6620 directory was on different branch than its first parent. In this
6604 situation unshelving restores branch information to the working directory.
6621 situation unshelving restores branch information to the working directory.
6605
6622
6606 Each shelved change has a name that makes it easier to find later.
6623 Each shelved change has a name that makes it easier to find later.
6607 The name of a shelved change defaults to being based on the active
6624 The name of a shelved change defaults to being based on the active
6608 bookmark, or if there is no active bookmark, the current named
6625 bookmark, or if there is no active bookmark, the current named
6609 branch. To specify a different name, use ``--name``.
6626 branch. To specify a different name, use ``--name``.
6610
6627
6611 To see a list of existing shelved changes, use the ``--list``
6628 To see a list of existing shelved changes, use the ``--list``
6612 option. For each shelved change, this will print its name, age,
6629 option. For each shelved change, this will print its name, age,
6613 and description; use ``--patch`` or ``--stat`` for more details.
6630 and description; use ``--patch`` or ``--stat`` for more details.
6614
6631
6615 To delete specific shelved changes, use ``--delete``. To delete
6632 To delete specific shelved changes, use ``--delete``. To delete
6616 all shelved changes, use ``--cleanup``.
6633 all shelved changes, use ``--cleanup``.
6617 """
6634 """
6618 opts = pycompat.byteskwargs(opts)
6635 opts = pycompat.byteskwargs(opts)
6619 allowables = [
6636 allowables = [
6620 (b'addremove', {b'create'}), # 'create' is pseudo action
6637 (b'addremove', {b'create'}), # 'create' is pseudo action
6621 (b'unknown', {b'create'}),
6638 (b'unknown', {b'create'}),
6622 (b'cleanup', {b'cleanup'}),
6639 (b'cleanup', {b'cleanup'}),
6623 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6640 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6624 (b'delete', {b'delete'}),
6641 (b'delete', {b'delete'}),
6625 (b'edit', {b'create'}),
6642 (b'edit', {b'create'}),
6626 (b'keep', {b'create'}),
6643 (b'keep', {b'create'}),
6627 (b'list', {b'list'}),
6644 (b'list', {b'list'}),
6628 (b'message', {b'create'}),
6645 (b'message', {b'create'}),
6629 (b'name', {b'create'}),
6646 (b'name', {b'create'}),
6630 (b'patch', {b'patch', b'list'}),
6647 (b'patch', {b'patch', b'list'}),
6631 (b'stat', {b'stat', b'list'}),
6648 (b'stat', {b'stat', b'list'}),
6632 ]
6649 ]
6633
6650
6634 def checkopt(opt):
6651 def checkopt(opt):
6635 if opts.get(opt):
6652 if opts.get(opt):
6636 for i, allowable in allowables:
6653 for i, allowable in allowables:
6637 if opts[i] and opt not in allowable:
6654 if opts[i] and opt not in allowable:
6638 raise error.InputError(
6655 raise error.InputError(
6639 _(
6656 _(
6640 b"options '--%s' and '--%s' may not be "
6657 b"options '--%s' and '--%s' may not be "
6641 b"used together"
6658 b"used together"
6642 )
6659 )
6643 % (opt, i)
6660 % (opt, i)
6644 )
6661 )
6645 return True
6662 return True
6646
6663
6647 if checkopt(b'cleanup'):
6664 if checkopt(b'cleanup'):
6648 if pats:
6665 if pats:
6649 raise error.InputError(
6666 raise error.InputError(
6650 _(b"cannot specify names when using '--cleanup'")
6667 _(b"cannot specify names when using '--cleanup'")
6651 )
6668 )
6652 return shelvemod.cleanupcmd(ui, repo)
6669 return shelvemod.cleanupcmd(ui, repo)
6653 elif checkopt(b'delete'):
6670 elif checkopt(b'delete'):
6654 return shelvemod.deletecmd(ui, repo, pats)
6671 return shelvemod.deletecmd(ui, repo, pats)
6655 elif checkopt(b'list'):
6672 elif checkopt(b'list'):
6656 return shelvemod.listcmd(ui, repo, pats, opts)
6673 return shelvemod.listcmd(ui, repo, pats, opts)
6657 elif checkopt(b'patch') or checkopt(b'stat'):
6674 elif checkopt(b'patch') or checkopt(b'stat'):
6658 return shelvemod.patchcmds(ui, repo, pats, opts)
6675 return shelvemod.patchcmds(ui, repo, pats, opts)
6659 else:
6676 else:
6660 return shelvemod.createcmd(ui, repo, pats, opts)
6677 return shelvemod.createcmd(ui, repo, pats, opts)
6661
6678
6662
6679
6663 _NOTTERSE = b'nothing'
6680 _NOTTERSE = b'nothing'
6664
6681
6665
6682
6666 @command(
6683 @command(
6667 b'status|st',
6684 b'status|st',
6668 [
6685 [
6669 (b'A', b'all', None, _(b'show status of all files')),
6686 (b'A', b'all', None, _(b'show status of all files')),
6670 (b'm', b'modified', None, _(b'show only modified files')),
6687 (b'm', b'modified', None, _(b'show only modified files')),
6671 (b'a', b'added', None, _(b'show only added files')),
6688 (b'a', b'added', None, _(b'show only added files')),
6672 (b'r', b'removed', None, _(b'show only removed files')),
6689 (b'r', b'removed', None, _(b'show only removed files')),
6673 (b'd', b'deleted', None, _(b'show only missing files')),
6690 (b'd', b'deleted', None, _(b'show only missing files')),
6674 (b'c', b'clean', None, _(b'show only files without changes')),
6691 (b'c', b'clean', None, _(b'show only files without changes')),
6675 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6692 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6676 (b'i', b'ignored', None, _(b'show only ignored files')),
6693 (b'i', b'ignored', None, _(b'show only ignored files')),
6677 (b'n', b'no-status', None, _(b'hide status prefix')),
6694 (b'n', b'no-status', None, _(b'hide status prefix')),
6678 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6695 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6679 (
6696 (
6680 b'C',
6697 b'C',
6681 b'copies',
6698 b'copies',
6682 None,
6699 None,
6683 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6700 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6684 ),
6701 ),
6685 (
6702 (
6686 b'0',
6703 b'0',
6687 b'print0',
6704 b'print0',
6688 None,
6705 None,
6689 _(b'end filenames with NUL, for use with xargs'),
6706 _(b'end filenames with NUL, for use with xargs'),
6690 ),
6707 ),
6691 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6708 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6692 (
6709 (
6693 b'',
6710 b'',
6694 b'change',
6711 b'change',
6695 b'',
6712 b'',
6696 _(b'list the changed files of a revision'),
6713 _(b'list the changed files of a revision'),
6697 _(b'REV'),
6714 _(b'REV'),
6698 ),
6715 ),
6699 ]
6716 ]
6700 + walkopts
6717 + walkopts
6701 + subrepoopts
6718 + subrepoopts
6702 + formatteropts,
6719 + formatteropts,
6703 _(b'[OPTION]... [FILE]...'),
6720 _(b'[OPTION]... [FILE]...'),
6704 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6721 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6705 helpbasic=True,
6722 helpbasic=True,
6706 inferrepo=True,
6723 inferrepo=True,
6707 intents={INTENT_READONLY},
6724 intents={INTENT_READONLY},
6708 )
6725 )
6709 def status(ui, repo, *pats, **opts):
6726 def status(ui, repo, *pats, **opts):
6710 """show changed files in the working directory
6727 """show changed files in the working directory
6711
6728
6712 Show status of files in the repository. If names are given, only
6729 Show status of files in the repository. If names are given, only
6713 files that match are shown. Files that are clean or ignored or
6730 files that match are shown. Files that are clean or ignored or
6714 the source of a copy/move operation, are not listed unless
6731 the source of a copy/move operation, are not listed unless
6715 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6732 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6716 Unless options described with "show only ..." are given, the
6733 Unless options described with "show only ..." are given, the
6717 options -mardu are used.
6734 options -mardu are used.
6718
6735
6719 Option -q/--quiet hides untracked (unknown and ignored) files
6736 Option -q/--quiet hides untracked (unknown and ignored) files
6720 unless explicitly requested with -u/--unknown or -i/--ignored.
6737 unless explicitly requested with -u/--unknown or -i/--ignored.
6721
6738
6722 .. note::
6739 .. note::
6723
6740
6724 :hg:`status` may appear to disagree with diff if permissions have
6741 :hg:`status` may appear to disagree with diff if permissions have
6725 changed or a merge has occurred. The standard diff format does
6742 changed or a merge has occurred. The standard diff format does
6726 not report permission changes and diff only reports changes
6743 not report permission changes and diff only reports changes
6727 relative to one merge parent.
6744 relative to one merge parent.
6728
6745
6729 If one revision is given, it is used as the base revision.
6746 If one revision is given, it is used as the base revision.
6730 If two revisions are given, the differences between them are
6747 If two revisions are given, the differences between them are
6731 shown. The --change option can also be used as a shortcut to list
6748 shown. The --change option can also be used as a shortcut to list
6732 the changed files of a revision from its first parent.
6749 the changed files of a revision from its first parent.
6733
6750
6734 The codes used to show the status of files are::
6751 The codes used to show the status of files are::
6735
6752
6736 M = modified
6753 M = modified
6737 A = added
6754 A = added
6738 R = removed
6755 R = removed
6739 C = clean
6756 C = clean
6740 ! = missing (deleted by non-hg command, but still tracked)
6757 ! = missing (deleted by non-hg command, but still tracked)
6741 ? = not tracked
6758 ? = not tracked
6742 I = ignored
6759 I = ignored
6743 = origin of the previous file (with --copies)
6760 = origin of the previous file (with --copies)
6744
6761
6745 .. container:: verbose
6762 .. container:: verbose
6746
6763
6747 The -t/--terse option abbreviates the output by showing only the directory
6764 The -t/--terse option abbreviates the output by showing only the directory
6748 name if all the files in it share the same status. The option takes an
6765 name if all the files in it share the same status. The option takes an
6749 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6766 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6750 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6767 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6751 for 'ignored' and 'c' for clean.
6768 for 'ignored' and 'c' for clean.
6752
6769
6753 It abbreviates only those statuses which are passed. Note that clean and
6770 It abbreviates only those statuses which are passed. Note that clean and
6754 ignored files are not displayed with '--terse ic' unless the -c/--clean
6771 ignored files are not displayed with '--terse ic' unless the -c/--clean
6755 and -i/--ignored options are also used.
6772 and -i/--ignored options are also used.
6756
6773
6757 The -v/--verbose option shows information when the repository is in an
6774 The -v/--verbose option shows information when the repository is in an
6758 unfinished merge, shelve, rebase state etc. You can have this behavior
6775 unfinished merge, shelve, rebase state etc. You can have this behavior
6759 turned on by default by enabling the ``commands.status.verbose`` option.
6776 turned on by default by enabling the ``commands.status.verbose`` option.
6760
6777
6761 You can skip displaying some of these states by setting
6778 You can skip displaying some of these states by setting
6762 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6779 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6763 'histedit', 'merge', 'rebase', or 'unshelve'.
6780 'histedit', 'merge', 'rebase', or 'unshelve'.
6764
6781
6765 Template:
6782 Template:
6766
6783
6767 The following keywords are supported in addition to the common template
6784 The following keywords are supported in addition to the common template
6768 keywords and functions. See also :hg:`help templates`.
6785 keywords and functions. See also :hg:`help templates`.
6769
6786
6770 :path: String. Repository-absolute path of the file.
6787 :path: String. Repository-absolute path of the file.
6771 :source: String. Repository-absolute path of the file originated from.
6788 :source: String. Repository-absolute path of the file originated from.
6772 Available if ``--copies`` is specified.
6789 Available if ``--copies`` is specified.
6773 :status: String. Character denoting file's status.
6790 :status: String. Character denoting file's status.
6774
6791
6775 Examples:
6792 Examples:
6776
6793
6777 - show changes in the working directory relative to a
6794 - show changes in the working directory relative to a
6778 changeset::
6795 changeset::
6779
6796
6780 hg status --rev 9353
6797 hg status --rev 9353
6781
6798
6782 - show changes in the working directory relative to the
6799 - show changes in the working directory relative to the
6783 current directory (see :hg:`help patterns` for more information)::
6800 current directory (see :hg:`help patterns` for more information)::
6784
6801
6785 hg status re:
6802 hg status re:
6786
6803
6787 - show all changes including copies in an existing changeset::
6804 - show all changes including copies in an existing changeset::
6788
6805
6789 hg status --copies --change 9353
6806 hg status --copies --change 9353
6790
6807
6791 - get a NUL separated list of added files, suitable for xargs::
6808 - get a NUL separated list of added files, suitable for xargs::
6792
6809
6793 hg status -an0
6810 hg status -an0
6794
6811
6795 - show more information about the repository status, abbreviating
6812 - show more information about the repository status, abbreviating
6796 added, removed, modified, deleted, and untracked paths::
6813 added, removed, modified, deleted, and untracked paths::
6797
6814
6798 hg status -v -t mardu
6815 hg status -v -t mardu
6799
6816
6800 Returns 0 on success.
6817 Returns 0 on success.
6801
6818
6802 """
6819 """
6803
6820
6804 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6821 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6805 opts = pycompat.byteskwargs(opts)
6822 opts = pycompat.byteskwargs(opts)
6806 revs = opts.get(b'rev')
6823 revs = opts.get(b'rev')
6807 change = opts.get(b'change')
6824 change = opts.get(b'change')
6808 terse = opts.get(b'terse')
6825 terse = opts.get(b'terse')
6809 if terse is _NOTTERSE:
6826 if terse is _NOTTERSE:
6810 if revs:
6827 if revs:
6811 terse = b''
6828 terse = b''
6812 else:
6829 else:
6813 terse = ui.config(b'commands', b'status.terse')
6830 terse = ui.config(b'commands', b'status.terse')
6814
6831
6815 if revs and terse:
6832 if revs and terse:
6816 msg = _(b'cannot use --terse with --rev')
6833 msg = _(b'cannot use --terse with --rev')
6817 raise error.InputError(msg)
6834 raise error.InputError(msg)
6818 elif change:
6835 elif change:
6819 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6836 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6820 ctx2 = scmutil.revsingle(repo, change, None)
6837 ctx2 = scmutil.revsingle(repo, change, None)
6821 ctx1 = ctx2.p1()
6838 ctx1 = ctx2.p1()
6822 else:
6839 else:
6823 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6840 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6824 ctx1, ctx2 = scmutil.revpair(repo, revs)
6841 ctx1, ctx2 = scmutil.revpair(repo, revs)
6825
6842
6826 forcerelativevalue = None
6843 forcerelativevalue = None
6827 if ui.hasconfig(b'commands', b'status.relative'):
6844 if ui.hasconfig(b'commands', b'status.relative'):
6828 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6845 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6829 uipathfn = scmutil.getuipathfn(
6846 uipathfn = scmutil.getuipathfn(
6830 repo,
6847 repo,
6831 legacyrelativevalue=bool(pats),
6848 legacyrelativevalue=bool(pats),
6832 forcerelativevalue=forcerelativevalue,
6849 forcerelativevalue=forcerelativevalue,
6833 )
6850 )
6834
6851
6835 if opts.get(b'print0'):
6852 if opts.get(b'print0'):
6836 end = b'\0'
6853 end = b'\0'
6837 else:
6854 else:
6838 end = b'\n'
6855 end = b'\n'
6839 states = b'modified added removed deleted unknown ignored clean'.split()
6856 states = b'modified added removed deleted unknown ignored clean'.split()
6840 show = [k for k in states if opts.get(k)]
6857 show = [k for k in states if opts.get(k)]
6841 if opts.get(b'all'):
6858 if opts.get(b'all'):
6842 show += ui.quiet and (states[:4] + [b'clean']) or states
6859 show += ui.quiet and (states[:4] + [b'clean']) or states
6843
6860
6844 if not show:
6861 if not show:
6845 if ui.quiet:
6862 if ui.quiet:
6846 show = states[:4]
6863 show = states[:4]
6847 else:
6864 else:
6848 show = states[:5]
6865 show = states[:5]
6849
6866
6850 m = scmutil.match(ctx2, pats, opts)
6867 m = scmutil.match(ctx2, pats, opts)
6851 if terse:
6868 if terse:
6852 # we need to compute clean and unknown to terse
6869 # we need to compute clean and unknown to terse
6853 stat = repo.status(
6870 stat = repo.status(
6854 ctx1.node(),
6871 ctx1.node(),
6855 ctx2.node(),
6872 ctx2.node(),
6856 m,
6873 m,
6857 b'ignored' in show or b'i' in terse,
6874 b'ignored' in show or b'i' in terse,
6858 clean=True,
6875 clean=True,
6859 unknown=True,
6876 unknown=True,
6860 listsubrepos=opts.get(b'subrepos'),
6877 listsubrepos=opts.get(b'subrepos'),
6861 )
6878 )
6862
6879
6863 stat = cmdutil.tersedir(stat, terse)
6880 stat = cmdutil.tersedir(stat, terse)
6864 else:
6881 else:
6865 stat = repo.status(
6882 stat = repo.status(
6866 ctx1.node(),
6883 ctx1.node(),
6867 ctx2.node(),
6884 ctx2.node(),
6868 m,
6885 m,
6869 b'ignored' in show,
6886 b'ignored' in show,
6870 b'clean' in show,
6887 b'clean' in show,
6871 b'unknown' in show,
6888 b'unknown' in show,
6872 opts.get(b'subrepos'),
6889 opts.get(b'subrepos'),
6873 )
6890 )
6874
6891
6875 changestates = zip(
6892 changestates = zip(
6876 states,
6893 states,
6877 pycompat.iterbytestr(b'MAR!?IC'),
6894 pycompat.iterbytestr(b'MAR!?IC'),
6878 [getattr(stat, s.decode('utf8')) for s in states],
6895 [getattr(stat, s.decode('utf8')) for s in states],
6879 )
6896 )
6880
6897
6881 copy = {}
6898 copy = {}
6882 if (
6899 if (
6883 opts.get(b'all')
6900 opts.get(b'all')
6884 or opts.get(b'copies')
6901 or opts.get(b'copies')
6885 or ui.configbool(b'ui', b'statuscopies')
6902 or ui.configbool(b'ui', b'statuscopies')
6886 ) and not opts.get(b'no_status'):
6903 ) and not opts.get(b'no_status'):
6887 copy = copies.pathcopies(ctx1, ctx2, m)
6904 copy = copies.pathcopies(ctx1, ctx2, m)
6888
6905
6889 morestatus = None
6906 morestatus = None
6890 if (
6907 if (
6891 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6908 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6892 and not ui.plain()
6909 and not ui.plain()
6893 and not opts.get(b'print0')
6910 and not opts.get(b'print0')
6894 ):
6911 ):
6895 morestatus = cmdutil.readmorestatus(repo)
6912 morestatus = cmdutil.readmorestatus(repo)
6896
6913
6897 ui.pager(b'status')
6914 ui.pager(b'status')
6898 fm = ui.formatter(b'status', opts)
6915 fm = ui.formatter(b'status', opts)
6899 fmt = b'%s' + end
6916 fmt = b'%s' + end
6900 showchar = not opts.get(b'no_status')
6917 showchar = not opts.get(b'no_status')
6901
6918
6902 for state, char, files in changestates:
6919 for state, char, files in changestates:
6903 if state in show:
6920 if state in show:
6904 label = b'status.' + state
6921 label = b'status.' + state
6905 for f in files:
6922 for f in files:
6906 fm.startitem()
6923 fm.startitem()
6907 fm.context(ctx=ctx2)
6924 fm.context(ctx=ctx2)
6908 fm.data(itemtype=b'file', path=f)
6925 fm.data(itemtype=b'file', path=f)
6909 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6926 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6910 fm.plain(fmt % uipathfn(f), label=label)
6927 fm.plain(fmt % uipathfn(f), label=label)
6911 if f in copy:
6928 if f in copy:
6912 fm.data(source=copy[f])
6929 fm.data(source=copy[f])
6913 fm.plain(
6930 fm.plain(
6914 (b' %s' + end) % uipathfn(copy[f]),
6931 (b' %s' + end) % uipathfn(copy[f]),
6915 label=b'status.copied',
6932 label=b'status.copied',
6916 )
6933 )
6917 if morestatus:
6934 if morestatus:
6918 morestatus.formatfile(f, fm)
6935 morestatus.formatfile(f, fm)
6919
6936
6920 if morestatus:
6937 if morestatus:
6921 morestatus.formatfooter(fm)
6938 morestatus.formatfooter(fm)
6922 fm.end()
6939 fm.end()
6923
6940
6924
6941
6925 @command(
6942 @command(
6926 b'summary|sum',
6943 b'summary|sum',
6927 [(b'', b'remote', None, _(b'check for push and pull'))],
6944 [(b'', b'remote', None, _(b'check for push and pull'))],
6928 b'[--remote]',
6945 b'[--remote]',
6929 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6946 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6930 helpbasic=True,
6947 helpbasic=True,
6931 intents={INTENT_READONLY},
6948 intents={INTENT_READONLY},
6932 )
6949 )
6933 def summary(ui, repo, **opts):
6950 def summary(ui, repo, **opts):
6934 """summarize working directory state
6951 """summarize working directory state
6935
6952
6936 This generates a brief summary of the working directory state,
6953 This generates a brief summary of the working directory state,
6937 including parents, branch, commit status, phase and available updates.
6954 including parents, branch, commit status, phase and available updates.
6938
6955
6939 With the --remote option, this will check the default paths for
6956 With the --remote option, this will check the default paths for
6940 incoming and outgoing changes. This can be time-consuming.
6957 incoming and outgoing changes. This can be time-consuming.
6941
6958
6942 Returns 0 on success.
6959 Returns 0 on success.
6943 """
6960 """
6944
6961
6945 opts = pycompat.byteskwargs(opts)
6962 opts = pycompat.byteskwargs(opts)
6946 ui.pager(b'summary')
6963 ui.pager(b'summary')
6947 ctx = repo[None]
6964 ctx = repo[None]
6948 parents = ctx.parents()
6965 parents = ctx.parents()
6949 pnode = parents[0].node()
6966 pnode = parents[0].node()
6950 marks = []
6967 marks = []
6951
6968
6952 try:
6969 try:
6953 ms = mergestatemod.mergestate.read(repo)
6970 ms = mergestatemod.mergestate.read(repo)
6954 except error.UnsupportedMergeRecords as e:
6971 except error.UnsupportedMergeRecords as e:
6955 s = b' '.join(e.recordtypes)
6972 s = b' '.join(e.recordtypes)
6956 ui.warn(
6973 ui.warn(
6957 _(b'warning: merge state has unsupported record types: %s\n') % s
6974 _(b'warning: merge state has unsupported record types: %s\n') % s
6958 )
6975 )
6959 unresolved = []
6976 unresolved = []
6960 else:
6977 else:
6961 unresolved = list(ms.unresolved())
6978 unresolved = list(ms.unresolved())
6962
6979
6963 for p in parents:
6980 for p in parents:
6964 # label with log.changeset (instead of log.parent) since this
6981 # label with log.changeset (instead of log.parent) since this
6965 # shows a working directory parent *changeset*:
6982 # shows a working directory parent *changeset*:
6966 # i18n: column positioning for "hg summary"
6983 # i18n: column positioning for "hg summary"
6967 ui.write(
6984 ui.write(
6968 _(b'parent: %d:%s ') % (p.rev(), p),
6985 _(b'parent: %d:%s ') % (p.rev(), p),
6969 label=logcmdutil.changesetlabels(p),
6986 label=logcmdutil.changesetlabels(p),
6970 )
6987 )
6971 ui.write(b' '.join(p.tags()), label=b'log.tag')
6988 ui.write(b' '.join(p.tags()), label=b'log.tag')
6972 if p.bookmarks():
6989 if p.bookmarks():
6973 marks.extend(p.bookmarks())
6990 marks.extend(p.bookmarks())
6974 if p.rev() == -1:
6991 if p.rev() == -1:
6975 if not len(repo):
6992 if not len(repo):
6976 ui.write(_(b' (empty repository)'))
6993 ui.write(_(b' (empty repository)'))
6977 else:
6994 else:
6978 ui.write(_(b' (no revision checked out)'))
6995 ui.write(_(b' (no revision checked out)'))
6979 if p.obsolete():
6996 if p.obsolete():
6980 ui.write(_(b' (obsolete)'))
6997 ui.write(_(b' (obsolete)'))
6981 if p.isunstable():
6998 if p.isunstable():
6982 instabilities = (
6999 instabilities = (
6983 ui.label(instability, b'trouble.%s' % instability)
7000 ui.label(instability, b'trouble.%s' % instability)
6984 for instability in p.instabilities()
7001 for instability in p.instabilities()
6985 )
7002 )
6986 ui.write(b' (' + b', '.join(instabilities) + b')')
7003 ui.write(b' (' + b', '.join(instabilities) + b')')
6987 ui.write(b'\n')
7004 ui.write(b'\n')
6988 if p.description():
7005 if p.description():
6989 ui.status(
7006 ui.status(
6990 b' ' + p.description().splitlines()[0].strip() + b'\n',
7007 b' ' + p.description().splitlines()[0].strip() + b'\n',
6991 label=b'log.summary',
7008 label=b'log.summary',
6992 )
7009 )
6993
7010
6994 branch = ctx.branch()
7011 branch = ctx.branch()
6995 bheads = repo.branchheads(branch)
7012 bheads = repo.branchheads(branch)
6996 # i18n: column positioning for "hg summary"
7013 # i18n: column positioning for "hg summary"
6997 m = _(b'branch: %s\n') % branch
7014 m = _(b'branch: %s\n') % branch
6998 if branch != b'default':
7015 if branch != b'default':
6999 ui.write(m, label=b'log.branch')
7016 ui.write(m, label=b'log.branch')
7000 else:
7017 else:
7001 ui.status(m, label=b'log.branch')
7018 ui.status(m, label=b'log.branch')
7002
7019
7003 if marks:
7020 if marks:
7004 active = repo._activebookmark
7021 active = repo._activebookmark
7005 # i18n: column positioning for "hg summary"
7022 # i18n: column positioning for "hg summary"
7006 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7023 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7007 if active is not None:
7024 if active is not None:
7008 if active in marks:
7025 if active in marks:
7009 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7026 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7010 marks.remove(active)
7027 marks.remove(active)
7011 else:
7028 else:
7012 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7029 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7013 for m in marks:
7030 for m in marks:
7014 ui.write(b' ' + m, label=b'log.bookmark')
7031 ui.write(b' ' + m, label=b'log.bookmark')
7015 ui.write(b'\n', label=b'log.bookmark')
7032 ui.write(b'\n', label=b'log.bookmark')
7016
7033
7017 status = repo.status(unknown=True)
7034 status = repo.status(unknown=True)
7018
7035
7019 c = repo.dirstate.copies()
7036 c = repo.dirstate.copies()
7020 copied, renamed = [], []
7037 copied, renamed = [], []
7021 for d, s in pycompat.iteritems(c):
7038 for d, s in pycompat.iteritems(c):
7022 if s in status.removed:
7039 if s in status.removed:
7023 status.removed.remove(s)
7040 status.removed.remove(s)
7024 renamed.append(d)
7041 renamed.append(d)
7025 else:
7042 else:
7026 copied.append(d)
7043 copied.append(d)
7027 if d in status.added:
7044 if d in status.added:
7028 status.added.remove(d)
7045 status.added.remove(d)
7029
7046
7030 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7047 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7031
7048
7032 labels = [
7049 labels = [
7033 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7050 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7034 (ui.label(_(b'%d added'), b'status.added'), status.added),
7051 (ui.label(_(b'%d added'), b'status.added'), status.added),
7035 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7052 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7036 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7053 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7037 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7054 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7038 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7055 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7039 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7056 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7040 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7057 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7041 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7058 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7042 ]
7059 ]
7043 t = []
7060 t = []
7044 for l, s in labels:
7061 for l, s in labels:
7045 if s:
7062 if s:
7046 t.append(l % len(s))
7063 t.append(l % len(s))
7047
7064
7048 t = b', '.join(t)
7065 t = b', '.join(t)
7049 cleanworkdir = False
7066 cleanworkdir = False
7050
7067
7051 if repo.vfs.exists(b'graftstate'):
7068 if repo.vfs.exists(b'graftstate'):
7052 t += _(b' (graft in progress)')
7069 t += _(b' (graft in progress)')
7053 if repo.vfs.exists(b'updatestate'):
7070 if repo.vfs.exists(b'updatestate'):
7054 t += _(b' (interrupted update)')
7071 t += _(b' (interrupted update)')
7055 elif len(parents) > 1:
7072 elif len(parents) > 1:
7056 t += _(b' (merge)')
7073 t += _(b' (merge)')
7057 elif branch != parents[0].branch():
7074 elif branch != parents[0].branch():
7058 t += _(b' (new branch)')
7075 t += _(b' (new branch)')
7059 elif parents[0].closesbranch() and pnode in repo.branchheads(
7076 elif parents[0].closesbranch() and pnode in repo.branchheads(
7060 branch, closed=True
7077 branch, closed=True
7061 ):
7078 ):
7062 t += _(b' (head closed)')
7079 t += _(b' (head closed)')
7063 elif not (
7080 elif not (
7064 status.modified
7081 status.modified
7065 or status.added
7082 or status.added
7066 or status.removed
7083 or status.removed
7067 or renamed
7084 or renamed
7068 or copied
7085 or copied
7069 or subs
7086 or subs
7070 ):
7087 ):
7071 t += _(b' (clean)')
7088 t += _(b' (clean)')
7072 cleanworkdir = True
7089 cleanworkdir = True
7073 elif pnode not in bheads:
7090 elif pnode not in bheads:
7074 t += _(b' (new branch head)')
7091 t += _(b' (new branch head)')
7075
7092
7076 if parents:
7093 if parents:
7077 pendingphase = max(p.phase() for p in parents)
7094 pendingphase = max(p.phase() for p in parents)
7078 else:
7095 else:
7079 pendingphase = phases.public
7096 pendingphase = phases.public
7080
7097
7081 if pendingphase > phases.newcommitphase(ui):
7098 if pendingphase > phases.newcommitphase(ui):
7082 t += b' (%s)' % phases.phasenames[pendingphase]
7099 t += b' (%s)' % phases.phasenames[pendingphase]
7083
7100
7084 if cleanworkdir:
7101 if cleanworkdir:
7085 # i18n: column positioning for "hg summary"
7102 # i18n: column positioning for "hg summary"
7086 ui.status(_(b'commit: %s\n') % t.strip())
7103 ui.status(_(b'commit: %s\n') % t.strip())
7087 else:
7104 else:
7088 # i18n: column positioning for "hg summary"
7105 # i18n: column positioning for "hg summary"
7089 ui.write(_(b'commit: %s\n') % t.strip())
7106 ui.write(_(b'commit: %s\n') % t.strip())
7090
7107
7091 # all ancestors of branch heads - all ancestors of parent = new csets
7108 # all ancestors of branch heads - all ancestors of parent = new csets
7092 new = len(
7109 new = len(
7093 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7110 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7094 )
7111 )
7095
7112
7096 if new == 0:
7113 if new == 0:
7097 # i18n: column positioning for "hg summary"
7114 # i18n: column positioning for "hg summary"
7098 ui.status(_(b'update: (current)\n'))
7115 ui.status(_(b'update: (current)\n'))
7099 elif pnode not in bheads:
7116 elif pnode not in bheads:
7100 # i18n: column positioning for "hg summary"
7117 # i18n: column positioning for "hg summary"
7101 ui.write(_(b'update: %d new changesets (update)\n') % new)
7118 ui.write(_(b'update: %d new changesets (update)\n') % new)
7102 else:
7119 else:
7103 # i18n: column positioning for "hg summary"
7120 # i18n: column positioning for "hg summary"
7104 ui.write(
7121 ui.write(
7105 _(b'update: %d new changesets, %d branch heads (merge)\n')
7122 _(b'update: %d new changesets, %d branch heads (merge)\n')
7106 % (new, len(bheads))
7123 % (new, len(bheads))
7107 )
7124 )
7108
7125
7109 t = []
7126 t = []
7110 draft = len(repo.revs(b'draft()'))
7127 draft = len(repo.revs(b'draft()'))
7111 if draft:
7128 if draft:
7112 t.append(_(b'%d draft') % draft)
7129 t.append(_(b'%d draft') % draft)
7113 secret = len(repo.revs(b'secret()'))
7130 secret = len(repo.revs(b'secret()'))
7114 if secret:
7131 if secret:
7115 t.append(_(b'%d secret') % secret)
7132 t.append(_(b'%d secret') % secret)
7116
7133
7117 if draft or secret:
7134 if draft or secret:
7118 ui.status(_(b'phases: %s\n') % b', '.join(t))
7135 ui.status(_(b'phases: %s\n') % b', '.join(t))
7119
7136
7120 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7137 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7121 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7138 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7122 numtrouble = len(repo.revs(trouble + b"()"))
7139 numtrouble = len(repo.revs(trouble + b"()"))
7123 # We write all the possibilities to ease translation
7140 # We write all the possibilities to ease translation
7124 troublemsg = {
7141 troublemsg = {
7125 b"orphan": _(b"orphan: %d changesets"),
7142 b"orphan": _(b"orphan: %d changesets"),
7126 b"contentdivergent": _(b"content-divergent: %d changesets"),
7143 b"contentdivergent": _(b"content-divergent: %d changesets"),
7127 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7144 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7128 }
7145 }
7129 if numtrouble > 0:
7146 if numtrouble > 0:
7130 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7147 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7131
7148
7132 cmdutil.summaryhooks(ui, repo)
7149 cmdutil.summaryhooks(ui, repo)
7133
7150
7134 if opts.get(b'remote'):
7151 if opts.get(b'remote'):
7135 needsincoming, needsoutgoing = True, True
7152 needsincoming, needsoutgoing = True, True
7136 else:
7153 else:
7137 needsincoming, needsoutgoing = False, False
7154 needsincoming, needsoutgoing = False, False
7138 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7155 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7139 if i:
7156 if i:
7140 needsincoming = True
7157 needsincoming = True
7141 if o:
7158 if o:
7142 needsoutgoing = True
7159 needsoutgoing = True
7143 if not needsincoming and not needsoutgoing:
7160 if not needsincoming and not needsoutgoing:
7144 return
7161 return
7145
7162
7146 def getincoming():
7163 def getincoming():
7147 source, branches = hg.parseurl(ui.expandpath(b'default'))
7164 source, branches = hg.parseurl(ui.expandpath(b'default'))
7148 sbranch = branches[0]
7165 sbranch = branches[0]
7149 try:
7166 try:
7150 other = hg.peer(repo, {}, source)
7167 other = hg.peer(repo, {}, source)
7151 except error.RepoError:
7168 except error.RepoError:
7152 if opts.get(b'remote'):
7169 if opts.get(b'remote'):
7153 raise
7170 raise
7154 return source, sbranch, None, None, None
7171 return source, sbranch, None, None, None
7155 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7172 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7156 if revs:
7173 if revs:
7157 revs = [other.lookup(rev) for rev in revs]
7174 revs = [other.lookup(rev) for rev in revs]
7158 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7175 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7159 repo.ui.pushbuffer()
7176 repo.ui.pushbuffer()
7160 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7177 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7161 repo.ui.popbuffer()
7178 repo.ui.popbuffer()
7162 return source, sbranch, other, commoninc, commoninc[1]
7179 return source, sbranch, other, commoninc, commoninc[1]
7163
7180
7164 if needsincoming:
7181 if needsincoming:
7165 source, sbranch, sother, commoninc, incoming = getincoming()
7182 source, sbranch, sother, commoninc, incoming = getincoming()
7166 else:
7183 else:
7167 source = sbranch = sother = commoninc = incoming = None
7184 source = sbranch = sother = commoninc = incoming = None
7168
7185
7169 def getoutgoing():
7186 def getoutgoing():
7170 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7187 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7171 dbranch = branches[0]
7188 dbranch = branches[0]
7172 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7189 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7173 if source != dest:
7190 if source != dest:
7174 try:
7191 try:
7175 dother = hg.peer(repo, {}, dest)
7192 dother = hg.peer(repo, {}, dest)
7176 except error.RepoError:
7193 except error.RepoError:
7177 if opts.get(b'remote'):
7194 if opts.get(b'remote'):
7178 raise
7195 raise
7179 return dest, dbranch, None, None
7196 return dest, dbranch, None, None
7180 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7197 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7181 elif sother is None:
7198 elif sother is None:
7182 # there is no explicit destination peer, but source one is invalid
7199 # there is no explicit destination peer, but source one is invalid
7183 return dest, dbranch, None, None
7200 return dest, dbranch, None, None
7184 else:
7201 else:
7185 dother = sother
7202 dother = sother
7186 if source != dest or (sbranch is not None and sbranch != dbranch):
7203 if source != dest or (sbranch is not None and sbranch != dbranch):
7187 common = None
7204 common = None
7188 else:
7205 else:
7189 common = commoninc
7206 common = commoninc
7190 if revs:
7207 if revs:
7191 revs = [repo.lookup(rev) for rev in revs]
7208 revs = [repo.lookup(rev) for rev in revs]
7192 repo.ui.pushbuffer()
7209 repo.ui.pushbuffer()
7193 outgoing = discovery.findcommonoutgoing(
7210 outgoing = discovery.findcommonoutgoing(
7194 repo, dother, onlyheads=revs, commoninc=common
7211 repo, dother, onlyheads=revs, commoninc=common
7195 )
7212 )
7196 repo.ui.popbuffer()
7213 repo.ui.popbuffer()
7197 return dest, dbranch, dother, outgoing
7214 return dest, dbranch, dother, outgoing
7198
7215
7199 if needsoutgoing:
7216 if needsoutgoing:
7200 dest, dbranch, dother, outgoing = getoutgoing()
7217 dest, dbranch, dother, outgoing = getoutgoing()
7201 else:
7218 else:
7202 dest = dbranch = dother = outgoing = None
7219 dest = dbranch = dother = outgoing = None
7203
7220
7204 if opts.get(b'remote'):
7221 if opts.get(b'remote'):
7205 t = []
7222 t = []
7206 if incoming:
7223 if incoming:
7207 t.append(_(b'1 or more incoming'))
7224 t.append(_(b'1 or more incoming'))
7208 o = outgoing.missing
7225 o = outgoing.missing
7209 if o:
7226 if o:
7210 t.append(_(b'%d outgoing') % len(o))
7227 t.append(_(b'%d outgoing') % len(o))
7211 other = dother or sother
7228 other = dother or sother
7212 if b'bookmarks' in other.listkeys(b'namespaces'):
7229 if b'bookmarks' in other.listkeys(b'namespaces'):
7213 counts = bookmarks.summary(repo, other)
7230 counts = bookmarks.summary(repo, other)
7214 if counts[0] > 0:
7231 if counts[0] > 0:
7215 t.append(_(b'%d incoming bookmarks') % counts[0])
7232 t.append(_(b'%d incoming bookmarks') % counts[0])
7216 if counts[1] > 0:
7233 if counts[1] > 0:
7217 t.append(_(b'%d outgoing bookmarks') % counts[1])
7234 t.append(_(b'%d outgoing bookmarks') % counts[1])
7218
7235
7219 if t:
7236 if t:
7220 # i18n: column positioning for "hg summary"
7237 # i18n: column positioning for "hg summary"
7221 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7238 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7222 else:
7239 else:
7223 # i18n: column positioning for "hg summary"
7240 # i18n: column positioning for "hg summary"
7224 ui.status(_(b'remote: (synced)\n'))
7241 ui.status(_(b'remote: (synced)\n'))
7225
7242
7226 cmdutil.summaryremotehooks(
7243 cmdutil.summaryremotehooks(
7227 ui,
7244 ui,
7228 repo,
7245 repo,
7229 opts,
7246 opts,
7230 (
7247 (
7231 (source, sbranch, sother, commoninc),
7248 (source, sbranch, sother, commoninc),
7232 (dest, dbranch, dother, outgoing),
7249 (dest, dbranch, dother, outgoing),
7233 ),
7250 ),
7234 )
7251 )
7235
7252
7236
7253
7237 @command(
7254 @command(
7238 b'tag',
7255 b'tag',
7239 [
7256 [
7240 (b'f', b'force', None, _(b'force tag')),
7257 (b'f', b'force', None, _(b'force tag')),
7241 (b'l', b'local', None, _(b'make the tag local')),
7258 (b'l', b'local', None, _(b'make the tag local')),
7242 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7259 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7243 (b'', b'remove', None, _(b'remove a tag')),
7260 (b'', b'remove', None, _(b'remove a tag')),
7244 # -l/--local is already there, commitopts cannot be used
7261 # -l/--local is already there, commitopts cannot be used
7245 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7262 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7246 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7263 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7247 ]
7264 ]
7248 + commitopts2,
7265 + commitopts2,
7249 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7266 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7250 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7267 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7251 )
7268 )
7252 def tag(ui, repo, name1, *names, **opts):
7269 def tag(ui, repo, name1, *names, **opts):
7253 """add one or more tags for the current or given revision
7270 """add one or more tags for the current or given revision
7254
7271
7255 Name a particular revision using <name>.
7272 Name a particular revision using <name>.
7256
7273
7257 Tags are used to name particular revisions of the repository and are
7274 Tags are used to name particular revisions of the repository and are
7258 very useful to compare different revisions, to go back to significant
7275 very useful to compare different revisions, to go back to significant
7259 earlier versions or to mark branch points as releases, etc. Changing
7276 earlier versions or to mark branch points as releases, etc. Changing
7260 an existing tag is normally disallowed; use -f/--force to override.
7277 an existing tag is normally disallowed; use -f/--force to override.
7261
7278
7262 If no revision is given, the parent of the working directory is
7279 If no revision is given, the parent of the working directory is
7263 used.
7280 used.
7264
7281
7265 To facilitate version control, distribution, and merging of tags,
7282 To facilitate version control, distribution, and merging of tags,
7266 they are stored as a file named ".hgtags" which is managed similarly
7283 they are stored as a file named ".hgtags" which is managed similarly
7267 to other project files and can be hand-edited if necessary. This
7284 to other project files and can be hand-edited if necessary. This
7268 also means that tagging creates a new commit. The file
7285 also means that tagging creates a new commit. The file
7269 ".hg/localtags" is used for local tags (not shared among
7286 ".hg/localtags" is used for local tags (not shared among
7270 repositories).
7287 repositories).
7271
7288
7272 Tag commits are usually made at the head of a branch. If the parent
7289 Tag commits are usually made at the head of a branch. If the parent
7273 of the working directory is not a branch head, :hg:`tag` aborts; use
7290 of the working directory is not a branch head, :hg:`tag` aborts; use
7274 -f/--force to force the tag commit to be based on a non-head
7291 -f/--force to force the tag commit to be based on a non-head
7275 changeset.
7292 changeset.
7276
7293
7277 See :hg:`help dates` for a list of formats valid for -d/--date.
7294 See :hg:`help dates` for a list of formats valid for -d/--date.
7278
7295
7279 Since tag names have priority over branch names during revision
7296 Since tag names have priority over branch names during revision
7280 lookup, using an existing branch name as a tag name is discouraged.
7297 lookup, using an existing branch name as a tag name is discouraged.
7281
7298
7282 Returns 0 on success.
7299 Returns 0 on success.
7283 """
7300 """
7284 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7301 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7285 opts = pycompat.byteskwargs(opts)
7302 opts = pycompat.byteskwargs(opts)
7286 with repo.wlock(), repo.lock():
7303 with repo.wlock(), repo.lock():
7287 rev_ = b"."
7304 rev_ = b"."
7288 names = [t.strip() for t in (name1,) + names]
7305 names = [t.strip() for t in (name1,) + names]
7289 if len(names) != len(set(names)):
7306 if len(names) != len(set(names)):
7290 raise error.InputError(_(b'tag names must be unique'))
7307 raise error.InputError(_(b'tag names must be unique'))
7291 for n in names:
7308 for n in names:
7292 scmutil.checknewlabel(repo, n, b'tag')
7309 scmutil.checknewlabel(repo, n, b'tag')
7293 if not n:
7310 if not n:
7294 raise error.InputError(
7311 raise error.InputError(
7295 _(b'tag names cannot consist entirely of whitespace')
7312 _(b'tag names cannot consist entirely of whitespace')
7296 )
7313 )
7297 if opts.get(b'rev'):
7314 if opts.get(b'rev'):
7298 rev_ = opts[b'rev']
7315 rev_ = opts[b'rev']
7299 message = opts.get(b'message')
7316 message = opts.get(b'message')
7300 if opts.get(b'remove'):
7317 if opts.get(b'remove'):
7301 if opts.get(b'local'):
7318 if opts.get(b'local'):
7302 expectedtype = b'local'
7319 expectedtype = b'local'
7303 else:
7320 else:
7304 expectedtype = b'global'
7321 expectedtype = b'global'
7305
7322
7306 for n in names:
7323 for n in names:
7307 if repo.tagtype(n) == b'global':
7324 if repo.tagtype(n) == b'global':
7308 alltags = tagsmod.findglobaltags(ui, repo)
7325 alltags = tagsmod.findglobaltags(ui, repo)
7309 if alltags[n][0] == nullid:
7326 if alltags[n][0] == nullid:
7310 raise error.InputError(
7327 raise error.InputError(
7311 _(b"tag '%s' is already removed") % n
7328 _(b"tag '%s' is already removed") % n
7312 )
7329 )
7313 if not repo.tagtype(n):
7330 if not repo.tagtype(n):
7314 raise error.InputError(_(b"tag '%s' does not exist") % n)
7331 raise error.InputError(_(b"tag '%s' does not exist") % n)
7315 if repo.tagtype(n) != expectedtype:
7332 if repo.tagtype(n) != expectedtype:
7316 if expectedtype == b'global':
7333 if expectedtype == b'global':
7317 raise error.InputError(
7334 raise error.InputError(
7318 _(b"tag '%s' is not a global tag") % n
7335 _(b"tag '%s' is not a global tag") % n
7319 )
7336 )
7320 else:
7337 else:
7321 raise error.InputError(
7338 raise error.InputError(
7322 _(b"tag '%s' is not a local tag") % n
7339 _(b"tag '%s' is not a local tag") % n
7323 )
7340 )
7324 rev_ = b'null'
7341 rev_ = b'null'
7325 if not message:
7342 if not message:
7326 # we don't translate commit messages
7343 # we don't translate commit messages
7327 message = b'Removed tag %s' % b', '.join(names)
7344 message = b'Removed tag %s' % b', '.join(names)
7328 elif not opts.get(b'force'):
7345 elif not opts.get(b'force'):
7329 for n in names:
7346 for n in names:
7330 if n in repo.tags():
7347 if n in repo.tags():
7331 raise error.InputError(
7348 raise error.InputError(
7332 _(b"tag '%s' already exists (use -f to force)") % n
7349 _(b"tag '%s' already exists (use -f to force)") % n
7333 )
7350 )
7334 if not opts.get(b'local'):
7351 if not opts.get(b'local'):
7335 p1, p2 = repo.dirstate.parents()
7352 p1, p2 = repo.dirstate.parents()
7336 if p2 != nullid:
7353 if p2 != nullid:
7337 raise error.StateError(_(b'uncommitted merge'))
7354 raise error.StateError(_(b'uncommitted merge'))
7338 bheads = repo.branchheads()
7355 bheads = repo.branchheads()
7339 if not opts.get(b'force') and bheads and p1 not in bheads:
7356 if not opts.get(b'force') and bheads and p1 not in bheads:
7340 raise error.InputError(
7357 raise error.InputError(
7341 _(
7358 _(
7342 b'working directory is not at a branch head '
7359 b'working directory is not at a branch head '
7343 b'(use -f to force)'
7360 b'(use -f to force)'
7344 )
7361 )
7345 )
7362 )
7346 node = scmutil.revsingle(repo, rev_).node()
7363 node = scmutil.revsingle(repo, rev_).node()
7347
7364
7348 if not message:
7365 if not message:
7349 # we don't translate commit messages
7366 # we don't translate commit messages
7350 message = b'Added tag %s for changeset %s' % (
7367 message = b'Added tag %s for changeset %s' % (
7351 b', '.join(names),
7368 b', '.join(names),
7352 short(node),
7369 short(node),
7353 )
7370 )
7354
7371
7355 date = opts.get(b'date')
7372 date = opts.get(b'date')
7356 if date:
7373 if date:
7357 date = dateutil.parsedate(date)
7374 date = dateutil.parsedate(date)
7358
7375
7359 if opts.get(b'remove'):
7376 if opts.get(b'remove'):
7360 editform = b'tag.remove'
7377 editform = b'tag.remove'
7361 else:
7378 else:
7362 editform = b'tag.add'
7379 editform = b'tag.add'
7363 editor = cmdutil.getcommiteditor(
7380 editor = cmdutil.getcommiteditor(
7364 editform=editform, **pycompat.strkwargs(opts)
7381 editform=editform, **pycompat.strkwargs(opts)
7365 )
7382 )
7366
7383
7367 # don't allow tagging the null rev
7384 # don't allow tagging the null rev
7368 if (
7385 if (
7369 not opts.get(b'remove')
7386 not opts.get(b'remove')
7370 and scmutil.revsingle(repo, rev_).rev() == nullrev
7387 and scmutil.revsingle(repo, rev_).rev() == nullrev
7371 ):
7388 ):
7372 raise error.InputError(_(b"cannot tag null revision"))
7389 raise error.InputError(_(b"cannot tag null revision"))
7373
7390
7374 tagsmod.tag(
7391 tagsmod.tag(
7375 repo,
7392 repo,
7376 names,
7393 names,
7377 node,
7394 node,
7378 message,
7395 message,
7379 opts.get(b'local'),
7396 opts.get(b'local'),
7380 opts.get(b'user'),
7397 opts.get(b'user'),
7381 date,
7398 date,
7382 editor=editor,
7399 editor=editor,
7383 )
7400 )
7384
7401
7385
7402
7386 @command(
7403 @command(
7387 b'tags',
7404 b'tags',
7388 formatteropts,
7405 formatteropts,
7389 b'',
7406 b'',
7390 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7407 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7391 intents={INTENT_READONLY},
7408 intents={INTENT_READONLY},
7392 )
7409 )
7393 def tags(ui, repo, **opts):
7410 def tags(ui, repo, **opts):
7394 """list repository tags
7411 """list repository tags
7395
7412
7396 This lists both regular and local tags. When the -v/--verbose
7413 This lists both regular and local tags. When the -v/--verbose
7397 switch is used, a third column "local" is printed for local tags.
7414 switch is used, a third column "local" is printed for local tags.
7398 When the -q/--quiet switch is used, only the tag name is printed.
7415 When the -q/--quiet switch is used, only the tag name is printed.
7399
7416
7400 .. container:: verbose
7417 .. container:: verbose
7401
7418
7402 Template:
7419 Template:
7403
7420
7404 The following keywords are supported in addition to the common template
7421 The following keywords are supported in addition to the common template
7405 keywords and functions such as ``{tag}``. See also
7422 keywords and functions such as ``{tag}``. See also
7406 :hg:`help templates`.
7423 :hg:`help templates`.
7407
7424
7408 :type: String. ``local`` for local tags.
7425 :type: String. ``local`` for local tags.
7409
7426
7410 Returns 0 on success.
7427 Returns 0 on success.
7411 """
7428 """
7412
7429
7413 opts = pycompat.byteskwargs(opts)
7430 opts = pycompat.byteskwargs(opts)
7414 ui.pager(b'tags')
7431 ui.pager(b'tags')
7415 fm = ui.formatter(b'tags', opts)
7432 fm = ui.formatter(b'tags', opts)
7416 hexfunc = fm.hexfunc
7433 hexfunc = fm.hexfunc
7417
7434
7418 for t, n in reversed(repo.tagslist()):
7435 for t, n in reversed(repo.tagslist()):
7419 hn = hexfunc(n)
7436 hn = hexfunc(n)
7420 label = b'tags.normal'
7437 label = b'tags.normal'
7421 tagtype = repo.tagtype(t)
7438 tagtype = repo.tagtype(t)
7422 if not tagtype or tagtype == b'global':
7439 if not tagtype or tagtype == b'global':
7423 tagtype = b''
7440 tagtype = b''
7424 else:
7441 else:
7425 label = b'tags.' + tagtype
7442 label = b'tags.' + tagtype
7426
7443
7427 fm.startitem()
7444 fm.startitem()
7428 fm.context(repo=repo)
7445 fm.context(repo=repo)
7429 fm.write(b'tag', b'%s', t, label=label)
7446 fm.write(b'tag', b'%s', t, label=label)
7430 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7447 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7431 fm.condwrite(
7448 fm.condwrite(
7432 not ui.quiet,
7449 not ui.quiet,
7433 b'rev node',
7450 b'rev node',
7434 fmt,
7451 fmt,
7435 repo.changelog.rev(n),
7452 repo.changelog.rev(n),
7436 hn,
7453 hn,
7437 label=label,
7454 label=label,
7438 )
7455 )
7439 fm.condwrite(
7456 fm.condwrite(
7440 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7457 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7441 )
7458 )
7442 fm.plain(b'\n')
7459 fm.plain(b'\n')
7443 fm.end()
7460 fm.end()
7444
7461
7445
7462
7446 @command(
7463 @command(
7447 b'tip',
7464 b'tip',
7448 [
7465 [
7449 (b'p', b'patch', None, _(b'show patch')),
7466 (b'p', b'patch', None, _(b'show patch')),
7450 (b'g', b'git', None, _(b'use git extended diff format')),
7467 (b'g', b'git', None, _(b'use git extended diff format')),
7451 ]
7468 ]
7452 + templateopts,
7469 + templateopts,
7453 _(b'[-p] [-g]'),
7470 _(b'[-p] [-g]'),
7454 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7471 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7455 )
7472 )
7456 def tip(ui, repo, **opts):
7473 def tip(ui, repo, **opts):
7457 """show the tip revision (DEPRECATED)
7474 """show the tip revision (DEPRECATED)
7458
7475
7459 The tip revision (usually just called the tip) is the changeset
7476 The tip revision (usually just called the tip) is the changeset
7460 most recently added to the repository (and therefore the most
7477 most recently added to the repository (and therefore the most
7461 recently changed head).
7478 recently changed head).
7462
7479
7463 If you have just made a commit, that commit will be the tip. If
7480 If you have just made a commit, that commit will be the tip. If
7464 you have just pulled changes from another repository, the tip of
7481 you have just pulled changes from another repository, the tip of
7465 that repository becomes the current tip. The "tip" tag is special
7482 that repository becomes the current tip. The "tip" tag is special
7466 and cannot be renamed or assigned to a different changeset.
7483 and cannot be renamed or assigned to a different changeset.
7467
7484
7468 This command is deprecated, please use :hg:`heads` instead.
7485 This command is deprecated, please use :hg:`heads` instead.
7469
7486
7470 Returns 0 on success.
7487 Returns 0 on success.
7471 """
7488 """
7472 opts = pycompat.byteskwargs(opts)
7489 opts = pycompat.byteskwargs(opts)
7473 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7490 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7474 displayer.show(repo[b'tip'])
7491 displayer.show(repo[b'tip'])
7475 displayer.close()
7492 displayer.close()
7476
7493
7477
7494
7478 @command(
7495 @command(
7479 b'unbundle',
7496 b'unbundle',
7480 [
7497 [
7481 (
7498 (
7482 b'u',
7499 b'u',
7483 b'update',
7500 b'update',
7484 None,
7501 None,
7485 _(b'update to new branch head if changesets were unbundled'),
7502 _(b'update to new branch head if changesets were unbundled'),
7486 )
7503 )
7487 ],
7504 ],
7488 _(b'[-u] FILE...'),
7505 _(b'[-u] FILE...'),
7489 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7506 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7490 )
7507 )
7491 def unbundle(ui, repo, fname1, *fnames, **opts):
7508 def unbundle(ui, repo, fname1, *fnames, **opts):
7492 """apply one or more bundle files
7509 """apply one or more bundle files
7493
7510
7494 Apply one or more bundle files generated by :hg:`bundle`.
7511 Apply one or more bundle files generated by :hg:`bundle`.
7495
7512
7496 Returns 0 on success, 1 if an update has unresolved files.
7513 Returns 0 on success, 1 if an update has unresolved files.
7497 """
7514 """
7498 fnames = (fname1,) + fnames
7515 fnames = (fname1,) + fnames
7499
7516
7500 with repo.lock():
7517 with repo.lock():
7501 for fname in fnames:
7518 for fname in fnames:
7502 f = hg.openpath(ui, fname)
7519 f = hg.openpath(ui, fname)
7503 gen = exchange.readbundle(ui, f, fname)
7520 gen = exchange.readbundle(ui, f, fname)
7504 if isinstance(gen, streamclone.streamcloneapplier):
7521 if isinstance(gen, streamclone.streamcloneapplier):
7505 raise error.InputError(
7522 raise error.InputError(
7506 _(
7523 _(
7507 b'packed bundles cannot be applied with '
7524 b'packed bundles cannot be applied with '
7508 b'"hg unbundle"'
7525 b'"hg unbundle"'
7509 ),
7526 ),
7510 hint=_(b'use "hg debugapplystreamclonebundle"'),
7527 hint=_(b'use "hg debugapplystreamclonebundle"'),
7511 )
7528 )
7512 url = b'bundle:' + fname
7529 url = b'bundle:' + fname
7513 try:
7530 try:
7514 txnname = b'unbundle'
7531 txnname = b'unbundle'
7515 if not isinstance(gen, bundle2.unbundle20):
7532 if not isinstance(gen, bundle2.unbundle20):
7516 txnname = b'unbundle\n%s' % util.hidepassword(url)
7533 txnname = b'unbundle\n%s' % util.hidepassword(url)
7517 with repo.transaction(txnname) as tr:
7534 with repo.transaction(txnname) as tr:
7518 op = bundle2.applybundle(
7535 op = bundle2.applybundle(
7519 repo, gen, tr, source=b'unbundle', url=url
7536 repo, gen, tr, source=b'unbundle', url=url
7520 )
7537 )
7521 except error.BundleUnknownFeatureError as exc:
7538 except error.BundleUnknownFeatureError as exc:
7522 raise error.Abort(
7539 raise error.Abort(
7523 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7540 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7524 hint=_(
7541 hint=_(
7525 b"see https://mercurial-scm.org/"
7542 b"see https://mercurial-scm.org/"
7526 b"wiki/BundleFeature for more "
7543 b"wiki/BundleFeature for more "
7527 b"information"
7544 b"information"
7528 ),
7545 ),
7529 )
7546 )
7530 modheads = bundle2.combinechangegroupresults(op)
7547 modheads = bundle2.combinechangegroupresults(op)
7531
7548
7532 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7549 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7533
7550
7534
7551
7535 @command(
7552 @command(
7536 b'unshelve',
7553 b'unshelve',
7537 [
7554 [
7538 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7555 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7539 (
7556 (
7540 b'c',
7557 b'c',
7541 b'continue',
7558 b'continue',
7542 None,
7559 None,
7543 _(b'continue an incomplete unshelve operation'),
7560 _(b'continue an incomplete unshelve operation'),
7544 ),
7561 ),
7545 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7562 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7546 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7563 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7547 (
7564 (
7548 b'n',
7565 b'n',
7549 b'name',
7566 b'name',
7550 b'',
7567 b'',
7551 _(b'restore shelved change with given name'),
7568 _(b'restore shelved change with given name'),
7552 _(b'NAME'),
7569 _(b'NAME'),
7553 ),
7570 ),
7554 (b't', b'tool', b'', _(b'specify merge tool')),
7571 (b't', b'tool', b'', _(b'specify merge tool')),
7555 (
7572 (
7556 b'',
7573 b'',
7557 b'date',
7574 b'date',
7558 b'',
7575 b'',
7559 _(b'set date for temporary commits (DEPRECATED)'),
7576 _(b'set date for temporary commits (DEPRECATED)'),
7560 _(b'DATE'),
7577 _(b'DATE'),
7561 ),
7578 ),
7562 ],
7579 ],
7563 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7580 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7564 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7581 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7565 )
7582 )
7566 def unshelve(ui, repo, *shelved, **opts):
7583 def unshelve(ui, repo, *shelved, **opts):
7567 """restore a shelved change to the working directory
7584 """restore a shelved change to the working directory
7568
7585
7569 This command accepts an optional name of a shelved change to
7586 This command accepts an optional name of a shelved change to
7570 restore. If none is given, the most recent shelved change is used.
7587 restore. If none is given, the most recent shelved change is used.
7571
7588
7572 If a shelved change is applied successfully, the bundle that
7589 If a shelved change is applied successfully, the bundle that
7573 contains the shelved changes is moved to a backup location
7590 contains the shelved changes is moved to a backup location
7574 (.hg/shelve-backup).
7591 (.hg/shelve-backup).
7575
7592
7576 Since you can restore a shelved change on top of an arbitrary
7593 Since you can restore a shelved change on top of an arbitrary
7577 commit, it is possible that unshelving will result in a conflict
7594 commit, it is possible that unshelving will result in a conflict
7578 between your changes and the commits you are unshelving onto. If
7595 between your changes and the commits you are unshelving onto. If
7579 this occurs, you must resolve the conflict, then use
7596 this occurs, you must resolve the conflict, then use
7580 ``--continue`` to complete the unshelve operation. (The bundle
7597 ``--continue`` to complete the unshelve operation. (The bundle
7581 will not be moved until you successfully complete the unshelve.)
7598 will not be moved until you successfully complete the unshelve.)
7582
7599
7583 (Alternatively, you can use ``--abort`` to abandon an unshelve
7600 (Alternatively, you can use ``--abort`` to abandon an unshelve
7584 that causes a conflict. This reverts the unshelved changes, and
7601 that causes a conflict. This reverts the unshelved changes, and
7585 leaves the bundle in place.)
7602 leaves the bundle in place.)
7586
7603
7587 If bare shelved change (without interactive, include and exclude
7604 If bare shelved change (without interactive, include and exclude
7588 option) was done on newly created branch it would restore branch
7605 option) was done on newly created branch it would restore branch
7589 information to the working directory.
7606 information to the working directory.
7590
7607
7591 After a successful unshelve, the shelved changes are stored in a
7608 After a successful unshelve, the shelved changes are stored in a
7592 backup directory. Only the N most recent backups are kept. N
7609 backup directory. Only the N most recent backups are kept. N
7593 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7610 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7594 configuration option.
7611 configuration option.
7595
7612
7596 .. container:: verbose
7613 .. container:: verbose
7597
7614
7598 Timestamp in seconds is used to decide order of backups. More
7615 Timestamp in seconds is used to decide order of backups. More
7599 than ``maxbackups`` backups are kept, if same timestamp
7616 than ``maxbackups`` backups are kept, if same timestamp
7600 prevents from deciding exact order of them, for safety.
7617 prevents from deciding exact order of them, for safety.
7601
7618
7602 Selected changes can be unshelved with ``--interactive`` flag.
7619 Selected changes can be unshelved with ``--interactive`` flag.
7603 The working directory is updated with the selected changes, and
7620 The working directory is updated with the selected changes, and
7604 only the unselected changes remain shelved.
7621 only the unselected changes remain shelved.
7605 Note: The whole shelve is applied to working directory first before
7622 Note: The whole shelve is applied to working directory first before
7606 running interactively. So, this will bring up all the conflicts between
7623 running interactively. So, this will bring up all the conflicts between
7607 working directory and the shelve, irrespective of which changes will be
7624 working directory and the shelve, irrespective of which changes will be
7608 unshelved.
7625 unshelved.
7609 """
7626 """
7610 with repo.wlock():
7627 with repo.wlock():
7611 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7628 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7612
7629
7613
7630
7614 statemod.addunfinished(
7631 statemod.addunfinished(
7615 b'unshelve',
7632 b'unshelve',
7616 fname=b'shelvedstate',
7633 fname=b'shelvedstate',
7617 continueflag=True,
7634 continueflag=True,
7618 abortfunc=shelvemod.hgabortunshelve,
7635 abortfunc=shelvemod.hgabortunshelve,
7619 continuefunc=shelvemod.hgcontinueunshelve,
7636 continuefunc=shelvemod.hgcontinueunshelve,
7620 cmdmsg=_(b'unshelve already in progress'),
7637 cmdmsg=_(b'unshelve already in progress'),
7621 )
7638 )
7622
7639
7623
7640
7624 @command(
7641 @command(
7625 b'update|up|checkout|co',
7642 b'update|up|checkout|co',
7626 [
7643 [
7627 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7644 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7628 (b'c', b'check', None, _(b'require clean working directory')),
7645 (b'c', b'check', None, _(b'require clean working directory')),
7629 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7646 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7630 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7647 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7631 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7648 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7632 ]
7649 ]
7633 + mergetoolopts,
7650 + mergetoolopts,
7634 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7651 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7635 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7652 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7636 helpbasic=True,
7653 helpbasic=True,
7637 )
7654 )
7638 def update(ui, repo, node=None, **opts):
7655 def update(ui, repo, node=None, **opts):
7639 """update working directory (or switch revisions)
7656 """update working directory (or switch revisions)
7640
7657
7641 Update the repository's working directory to the specified
7658 Update the repository's working directory to the specified
7642 changeset. If no changeset is specified, update to the tip of the
7659 changeset. If no changeset is specified, update to the tip of the
7643 current named branch and move the active bookmark (see :hg:`help
7660 current named branch and move the active bookmark (see :hg:`help
7644 bookmarks`).
7661 bookmarks`).
7645
7662
7646 Update sets the working directory's parent revision to the specified
7663 Update sets the working directory's parent revision to the specified
7647 changeset (see :hg:`help parents`).
7664 changeset (see :hg:`help parents`).
7648
7665
7649 If the changeset is not a descendant or ancestor of the working
7666 If the changeset is not a descendant or ancestor of the working
7650 directory's parent and there are uncommitted changes, the update is
7667 directory's parent and there are uncommitted changes, the update is
7651 aborted. With the -c/--check option, the working directory is checked
7668 aborted. With the -c/--check option, the working directory is checked
7652 for uncommitted changes; if none are found, the working directory is
7669 for uncommitted changes; if none are found, the working directory is
7653 updated to the specified changeset.
7670 updated to the specified changeset.
7654
7671
7655 .. container:: verbose
7672 .. container:: verbose
7656
7673
7657 The -C/--clean, -c/--check, and -m/--merge options control what
7674 The -C/--clean, -c/--check, and -m/--merge options control what
7658 happens if the working directory contains uncommitted changes.
7675 happens if the working directory contains uncommitted changes.
7659 At most of one of them can be specified.
7676 At most of one of them can be specified.
7660
7677
7661 1. If no option is specified, and if
7678 1. If no option is specified, and if
7662 the requested changeset is an ancestor or descendant of
7679 the requested changeset is an ancestor or descendant of
7663 the working directory's parent, the uncommitted changes
7680 the working directory's parent, the uncommitted changes
7664 are merged into the requested changeset and the merged
7681 are merged into the requested changeset and the merged
7665 result is left uncommitted. If the requested changeset is
7682 result is left uncommitted. If the requested changeset is
7666 not an ancestor or descendant (that is, it is on another
7683 not an ancestor or descendant (that is, it is on another
7667 branch), the update is aborted and the uncommitted changes
7684 branch), the update is aborted and the uncommitted changes
7668 are preserved.
7685 are preserved.
7669
7686
7670 2. With the -m/--merge option, the update is allowed even if the
7687 2. With the -m/--merge option, the update is allowed even if the
7671 requested changeset is not an ancestor or descendant of
7688 requested changeset is not an ancestor or descendant of
7672 the working directory's parent.
7689 the working directory's parent.
7673
7690
7674 3. With the -c/--check option, the update is aborted and the
7691 3. With the -c/--check option, the update is aborted and the
7675 uncommitted changes are preserved.
7692 uncommitted changes are preserved.
7676
7693
7677 4. With the -C/--clean option, uncommitted changes are discarded and
7694 4. With the -C/--clean option, uncommitted changes are discarded and
7678 the working directory is updated to the requested changeset.
7695 the working directory is updated to the requested changeset.
7679
7696
7680 To cancel an uncommitted merge (and lose your changes), use
7697 To cancel an uncommitted merge (and lose your changes), use
7681 :hg:`merge --abort`.
7698 :hg:`merge --abort`.
7682
7699
7683 Use null as the changeset to remove the working directory (like
7700 Use null as the changeset to remove the working directory (like
7684 :hg:`clone -U`).
7701 :hg:`clone -U`).
7685
7702
7686 If you want to revert just one file to an older revision, use
7703 If you want to revert just one file to an older revision, use
7687 :hg:`revert [-r REV] NAME`.
7704 :hg:`revert [-r REV] NAME`.
7688
7705
7689 See :hg:`help dates` for a list of formats valid for -d/--date.
7706 See :hg:`help dates` for a list of formats valid for -d/--date.
7690
7707
7691 Returns 0 on success, 1 if there are unresolved files.
7708 Returns 0 on success, 1 if there are unresolved files.
7692 """
7709 """
7693 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7710 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7694 rev = opts.get('rev')
7711 rev = opts.get('rev')
7695 date = opts.get('date')
7712 date = opts.get('date')
7696 clean = opts.get('clean')
7713 clean = opts.get('clean')
7697 check = opts.get('check')
7714 check = opts.get('check')
7698 merge = opts.get('merge')
7715 merge = opts.get('merge')
7699 if rev and node:
7716 if rev and node:
7700 raise error.InputError(_(b"please specify just one revision"))
7717 raise error.InputError(_(b"please specify just one revision"))
7701
7718
7702 if ui.configbool(b'commands', b'update.requiredest'):
7719 if ui.configbool(b'commands', b'update.requiredest'):
7703 if not node and not rev and not date:
7720 if not node and not rev and not date:
7704 raise error.InputError(
7721 raise error.InputError(
7705 _(b'you must specify a destination'),
7722 _(b'you must specify a destination'),
7706 hint=_(b'for example: hg update ".::"'),
7723 hint=_(b'for example: hg update ".::"'),
7707 )
7724 )
7708
7725
7709 if rev is None or rev == b'':
7726 if rev is None or rev == b'':
7710 rev = node
7727 rev = node
7711
7728
7712 if date and rev is not None:
7729 if date and rev is not None:
7713 raise error.InputError(_(b"you can't specify a revision and a date"))
7730 raise error.InputError(_(b"you can't specify a revision and a date"))
7714
7731
7715 updatecheck = None
7732 updatecheck = None
7716 if check:
7733 if check:
7717 updatecheck = b'abort'
7734 updatecheck = b'abort'
7718 elif merge:
7735 elif merge:
7719 updatecheck = b'none'
7736 updatecheck = b'none'
7720
7737
7721 with repo.wlock():
7738 with repo.wlock():
7722 cmdutil.clearunfinished(repo)
7739 cmdutil.clearunfinished(repo)
7723 if date:
7740 if date:
7724 rev = cmdutil.finddate(ui, repo, date)
7741 rev = cmdutil.finddate(ui, repo, date)
7725
7742
7726 # if we defined a bookmark, we have to remember the original name
7743 # if we defined a bookmark, we have to remember the original name
7727 brev = rev
7744 brev = rev
7728 if rev:
7745 if rev:
7729 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7746 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7730 ctx = scmutil.revsingle(repo, rev, default=None)
7747 ctx = scmutil.revsingle(repo, rev, default=None)
7731 rev = ctx.rev()
7748 rev = ctx.rev()
7732 hidden = ctx.hidden()
7749 hidden = ctx.hidden()
7733 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7750 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7734 with ui.configoverride(overrides, b'update'):
7751 with ui.configoverride(overrides, b'update'):
7735 ret = hg.updatetotally(
7752 ret = hg.updatetotally(
7736 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7753 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7737 )
7754 )
7738 if hidden:
7755 if hidden:
7739 ctxstr = ctx.hex()[:12]
7756 ctxstr = ctx.hex()[:12]
7740 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7757 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7741
7758
7742 if ctx.obsolete():
7759 if ctx.obsolete():
7743 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7760 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7744 ui.warn(b"(%s)\n" % obsfatemsg)
7761 ui.warn(b"(%s)\n" % obsfatemsg)
7745 return ret
7762 return ret
7746
7763
7747
7764
7748 @command(
7765 @command(
7749 b'verify',
7766 b'verify',
7750 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7767 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7751 helpcategory=command.CATEGORY_MAINTENANCE,
7768 helpcategory=command.CATEGORY_MAINTENANCE,
7752 )
7769 )
7753 def verify(ui, repo, **opts):
7770 def verify(ui, repo, **opts):
7754 """verify the integrity of the repository
7771 """verify the integrity of the repository
7755
7772
7756 Verify the integrity of the current repository.
7773 Verify the integrity of the current repository.
7757
7774
7758 This will perform an extensive check of the repository's
7775 This will perform an extensive check of the repository's
7759 integrity, validating the hashes and checksums of each entry in
7776 integrity, validating the hashes and checksums of each entry in
7760 the changelog, manifest, and tracked files, as well as the
7777 the changelog, manifest, and tracked files, as well as the
7761 integrity of their crosslinks and indices.
7778 integrity of their crosslinks and indices.
7762
7779
7763 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7780 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7764 for more information about recovery from corruption of the
7781 for more information about recovery from corruption of the
7765 repository.
7782 repository.
7766
7783
7767 Returns 0 on success, 1 if errors are encountered.
7784 Returns 0 on success, 1 if errors are encountered.
7768 """
7785 """
7769 opts = pycompat.byteskwargs(opts)
7786 opts = pycompat.byteskwargs(opts)
7770
7787
7771 level = None
7788 level = None
7772 if opts[b'full']:
7789 if opts[b'full']:
7773 level = verifymod.VERIFY_FULL
7790 level = verifymod.VERIFY_FULL
7774 return hg.verify(repo, level)
7791 return hg.verify(repo, level)
7775
7792
7776
7793
7777 @command(
7794 @command(
7778 b'version',
7795 b'version',
7779 [] + formatteropts,
7796 [] + formatteropts,
7780 helpcategory=command.CATEGORY_HELP,
7797 helpcategory=command.CATEGORY_HELP,
7781 norepo=True,
7798 norepo=True,
7782 intents={INTENT_READONLY},
7799 intents={INTENT_READONLY},
7783 )
7800 )
7784 def version_(ui, **opts):
7801 def version_(ui, **opts):
7785 """output version and copyright information
7802 """output version and copyright information
7786
7803
7787 .. container:: verbose
7804 .. container:: verbose
7788
7805
7789 Template:
7806 Template:
7790
7807
7791 The following keywords are supported. See also :hg:`help templates`.
7808 The following keywords are supported. See also :hg:`help templates`.
7792
7809
7793 :extensions: List of extensions.
7810 :extensions: List of extensions.
7794 :ver: String. Version number.
7811 :ver: String. Version number.
7795
7812
7796 And each entry of ``{extensions}`` provides the following sub-keywords
7813 And each entry of ``{extensions}`` provides the following sub-keywords
7797 in addition to ``{ver}``.
7814 in addition to ``{ver}``.
7798
7815
7799 :bundled: Boolean. True if included in the release.
7816 :bundled: Boolean. True if included in the release.
7800 :name: String. Extension name.
7817 :name: String. Extension name.
7801 """
7818 """
7802 opts = pycompat.byteskwargs(opts)
7819 opts = pycompat.byteskwargs(opts)
7803 if ui.verbose:
7820 if ui.verbose:
7804 ui.pager(b'version')
7821 ui.pager(b'version')
7805 fm = ui.formatter(b"version", opts)
7822 fm = ui.formatter(b"version", opts)
7806 fm.startitem()
7823 fm.startitem()
7807 fm.write(
7824 fm.write(
7808 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7825 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7809 )
7826 )
7810 license = _(
7827 license = _(
7811 b"(see https://mercurial-scm.org for more information)\n"
7828 b"(see https://mercurial-scm.org for more information)\n"
7812 b"\nCopyright (C) 2005-2021 Matt Mackall and others\n"
7829 b"\nCopyright (C) 2005-2021 Matt Mackall and others\n"
7813 b"This is free software; see the source for copying conditions. "
7830 b"This is free software; see the source for copying conditions. "
7814 b"There is NO\nwarranty; "
7831 b"There is NO\nwarranty; "
7815 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7832 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7816 )
7833 )
7817 if not ui.quiet:
7834 if not ui.quiet:
7818 fm.plain(license)
7835 fm.plain(license)
7819
7836
7820 if ui.verbose:
7837 if ui.verbose:
7821 fm.plain(_(b"\nEnabled extensions:\n\n"))
7838 fm.plain(_(b"\nEnabled extensions:\n\n"))
7822 # format names and versions into columns
7839 # format names and versions into columns
7823 names = []
7840 names = []
7824 vers = []
7841 vers = []
7825 isinternals = []
7842 isinternals = []
7826 for name, module in sorted(extensions.extensions()):
7843 for name, module in sorted(extensions.extensions()):
7827 names.append(name)
7844 names.append(name)
7828 vers.append(extensions.moduleversion(module) or None)
7845 vers.append(extensions.moduleversion(module) or None)
7829 isinternals.append(extensions.ismoduleinternal(module))
7846 isinternals.append(extensions.ismoduleinternal(module))
7830 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7847 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7831 if names:
7848 if names:
7832 namefmt = b" %%-%ds " % max(len(n) for n in names)
7849 namefmt = b" %%-%ds " % max(len(n) for n in names)
7833 places = [_(b"external"), _(b"internal")]
7850 places = [_(b"external"), _(b"internal")]
7834 for n, v, p in zip(names, vers, isinternals):
7851 for n, v, p in zip(names, vers, isinternals):
7835 fn.startitem()
7852 fn.startitem()
7836 fn.condwrite(ui.verbose, b"name", namefmt, n)
7853 fn.condwrite(ui.verbose, b"name", namefmt, n)
7837 if ui.verbose:
7854 if ui.verbose:
7838 fn.plain(b"%s " % places[p])
7855 fn.plain(b"%s " % places[p])
7839 fn.data(bundled=p)
7856 fn.data(bundled=p)
7840 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7857 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7841 if ui.verbose:
7858 if ui.verbose:
7842 fn.plain(b"\n")
7859 fn.plain(b"\n")
7843 fn.end()
7860 fn.end()
7844 fm.end()
7861 fm.end()
7845
7862
7846
7863
7847 def loadcmdtable(ui, name, cmdtable):
7864 def loadcmdtable(ui, name, cmdtable):
7848 """Load command functions from specified cmdtable"""
7865 """Load command functions from specified cmdtable"""
7849 overrides = [cmd for cmd in cmdtable if cmd in table]
7866 overrides = [cmd for cmd in cmdtable if cmd in table]
7850 if overrides:
7867 if overrides:
7851 ui.warn(
7868 ui.warn(
7852 _(b"extension '%s' overrides commands: %s\n")
7869 _(b"extension '%s' overrides commands: %s\n")
7853 % (name, b" ".join(overrides))
7870 % (name, b" ".join(overrides))
7854 )
7871 )
7855 table.update(cmdtable)
7872 table.update(cmdtable)
@@ -1,4758 +1,4773 b''
1 # debugcommands.py - command processing for debug* commands
1 # debugcommands.py - command processing for debug* commands
2 #
2 #
3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2016 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 codecs
10 import codecs
11 import collections
11 import collections
12 import difflib
12 import difflib
13 import errno
13 import errno
14 import glob
14 import glob
15 import operator
15 import operator
16 import os
16 import os
17 import platform
17 import platform
18 import random
18 import random
19 import re
19 import re
20 import socket
20 import socket
21 import ssl
21 import ssl
22 import stat
22 import stat
23 import string
23 import string
24 import subprocess
24 import subprocess
25 import sys
25 import sys
26 import time
26 import time
27
27
28 from .i18n import _
28 from .i18n import _
29 from .node import (
29 from .node import (
30 bin,
30 bin,
31 hex,
31 hex,
32 nullid,
32 nullid,
33 nullrev,
33 nullrev,
34 short,
34 short,
35 )
35 )
36 from .pycompat import (
36 from .pycompat import (
37 getattr,
37 getattr,
38 open,
38 open,
39 )
39 )
40 from . import (
40 from . import (
41 bundle2,
41 bundle2,
42 bundlerepo,
42 bundlerepo,
43 changegroup,
43 changegroup,
44 cmdutil,
44 cmdutil,
45 color,
45 color,
46 context,
46 context,
47 copies,
47 copies,
48 dagparser,
48 dagparser,
49 encoding,
49 encoding,
50 error,
50 error,
51 exchange,
51 exchange,
52 extensions,
52 extensions,
53 filemerge,
53 filemerge,
54 filesetlang,
54 filesetlang,
55 formatter,
55 formatter,
56 hg,
56 hg,
57 httppeer,
57 httppeer,
58 localrepo,
58 localrepo,
59 lock as lockmod,
59 lock as lockmod,
60 logcmdutil,
60 logcmdutil,
61 mergestate as mergestatemod,
61 mergestate as mergestatemod,
62 metadata,
62 metadata,
63 obsolete,
63 obsolete,
64 obsutil,
64 obsutil,
65 pathutil,
65 pathutil,
66 phases,
66 phases,
67 policy,
67 policy,
68 pvec,
68 pvec,
69 pycompat,
69 pycompat,
70 registrar,
70 registrar,
71 repair,
71 repair,
72 repoview,
72 repoview,
73 revlog,
73 revlog,
74 revset,
74 revset,
75 revsetlang,
75 revsetlang,
76 scmutil,
76 scmutil,
77 setdiscovery,
77 setdiscovery,
78 simplemerge,
78 simplemerge,
79 sshpeer,
79 sshpeer,
80 sslutil,
80 sslutil,
81 streamclone,
81 streamclone,
82 strip,
82 strip,
83 tags as tagsmod,
83 tags as tagsmod,
84 templater,
84 templater,
85 treediscovery,
85 treediscovery,
86 upgrade,
86 upgrade,
87 url as urlmod,
87 url as urlmod,
88 util,
88 util,
89 vfs as vfsmod,
89 vfs as vfsmod,
90 wireprotoframing,
90 wireprotoframing,
91 wireprotoserver,
91 wireprotoserver,
92 wireprotov2peer,
92 wireprotov2peer,
93 )
93 )
94 from .utils import (
94 from .utils import (
95 cborutil,
95 cborutil,
96 compression,
96 compression,
97 dateutil,
97 dateutil,
98 procutil,
98 procutil,
99 stringutil,
99 stringutil,
100 )
100 )
101
101
102 from .revlogutils import (
102 from .revlogutils import (
103 deltas as deltautil,
103 deltas as deltautil,
104 nodemap,
104 nodemap,
105 sidedata,
105 sidedata,
106 )
106 )
107
107
108 release = lockmod.release
108 release = lockmod.release
109
109
110 table = {}
110 table = {}
111 table.update(strip.command._table)
111 table.update(strip.command._table)
112 command = registrar.command(table)
112 command = registrar.command(table)
113
113
114
114
115 @command(b'debugancestor', [], _(b'[INDEX] REV1 REV2'), optionalrepo=True)
115 @command(b'debugancestor', [], _(b'[INDEX] REV1 REV2'), optionalrepo=True)
116 def debugancestor(ui, repo, *args):
116 def debugancestor(ui, repo, *args):
117 """find the ancestor revision of two revisions in a given index"""
117 """find the ancestor revision of two revisions in a given index"""
118 if len(args) == 3:
118 if len(args) == 3:
119 index, rev1, rev2 = args
119 index, rev1, rev2 = args
120 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), index)
120 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), index)
121 lookup = r.lookup
121 lookup = r.lookup
122 elif len(args) == 2:
122 elif len(args) == 2:
123 if not repo:
123 if not repo:
124 raise error.Abort(
124 raise error.Abort(
125 _(b'there is no Mercurial repository here (.hg not found)')
125 _(b'there is no Mercurial repository here (.hg not found)')
126 )
126 )
127 rev1, rev2 = args
127 rev1, rev2 = args
128 r = repo.changelog
128 r = repo.changelog
129 lookup = repo.lookup
129 lookup = repo.lookup
130 else:
130 else:
131 raise error.Abort(_(b'either two or three arguments required'))
131 raise error.Abort(_(b'either two or three arguments required'))
132 a = r.ancestor(lookup(rev1), lookup(rev2))
132 a = r.ancestor(lookup(rev1), lookup(rev2))
133 ui.write(b'%d:%s\n' % (r.rev(a), hex(a)))
133 ui.write(b'%d:%s\n' % (r.rev(a), hex(a)))
134
134
135
135
136 @command(b'debugantivirusrunning', [])
136 @command(b'debugantivirusrunning', [])
137 def debugantivirusrunning(ui, repo):
137 def debugantivirusrunning(ui, repo):
138 """attempt to trigger an antivirus scanner to see if one is active"""
138 """attempt to trigger an antivirus scanner to see if one is active"""
139 with repo.cachevfs.open('eicar-test-file.com', b'wb') as f:
139 with repo.cachevfs.open('eicar-test-file.com', b'wb') as f:
140 f.write(
140 f.write(
141 util.b85decode(
141 util.b85decode(
142 # This is a base85-armored version of the EICAR test file. See
142 # This is a base85-armored version of the EICAR test file. See
143 # https://en.wikipedia.org/wiki/EICAR_test_file for details.
143 # https://en.wikipedia.org/wiki/EICAR_test_file for details.
144 b'ST#=}P$fV?P+K%yP+C|uG$>GBDK|qyDK~v2MM*<JQY}+dK~6+LQba95P'
144 b'ST#=}P$fV?P+K%yP+C|uG$>GBDK|qyDK~v2MM*<JQY}+dK~6+LQba95P'
145 b'E<)&Nm5l)EmTEQR4qnHOhq9iNGnJx'
145 b'E<)&Nm5l)EmTEQR4qnHOhq9iNGnJx'
146 )
146 )
147 )
147 )
148 # Give an AV engine time to scan the file.
148 # Give an AV engine time to scan the file.
149 time.sleep(2)
149 time.sleep(2)
150 util.unlink(repo.cachevfs.join('eicar-test-file.com'))
150 util.unlink(repo.cachevfs.join('eicar-test-file.com'))
151
151
152
152
153 @command(b'debugapplystreamclonebundle', [], b'FILE')
153 @command(b'debugapplystreamclonebundle', [], b'FILE')
154 def debugapplystreamclonebundle(ui, repo, fname):
154 def debugapplystreamclonebundle(ui, repo, fname):
155 """apply a stream clone bundle file"""
155 """apply a stream clone bundle file"""
156 f = hg.openpath(ui, fname)
156 f = hg.openpath(ui, fname)
157 gen = exchange.readbundle(ui, f, fname)
157 gen = exchange.readbundle(ui, f, fname)
158 gen.apply(repo)
158 gen.apply(repo)
159
159
160
160
161 @command(
161 @command(
162 b'debugbuilddag',
162 b'debugbuilddag',
163 [
163 [
164 (
164 (
165 b'm',
165 b'm',
166 b'mergeable-file',
166 b'mergeable-file',
167 None,
167 None,
168 _(b'add single file mergeable changes'),
168 _(b'add single file mergeable changes'),
169 ),
169 ),
170 (
170 (
171 b'o',
171 b'o',
172 b'overwritten-file',
172 b'overwritten-file',
173 None,
173 None,
174 _(b'add single file all revs overwrite'),
174 _(b'add single file all revs overwrite'),
175 ),
175 ),
176 (b'n', b'new-file', None, _(b'add new file at each rev')),
176 (b'n', b'new-file', None, _(b'add new file at each rev')),
177 ],
177 ],
178 _(b'[OPTION]... [TEXT]'),
178 _(b'[OPTION]... [TEXT]'),
179 )
179 )
180 def debugbuilddag(
180 def debugbuilddag(
181 ui,
181 ui,
182 repo,
182 repo,
183 text=None,
183 text=None,
184 mergeable_file=False,
184 mergeable_file=False,
185 overwritten_file=False,
185 overwritten_file=False,
186 new_file=False,
186 new_file=False,
187 ):
187 ):
188 """builds a repo with a given DAG from scratch in the current empty repo
188 """builds a repo with a given DAG from scratch in the current empty repo
189
189
190 The description of the DAG is read from stdin if not given on the
190 The description of the DAG is read from stdin if not given on the
191 command line.
191 command line.
192
192
193 Elements:
193 Elements:
194
194
195 - "+n" is a linear run of n nodes based on the current default parent
195 - "+n" is a linear run of n nodes based on the current default parent
196 - "." is a single node based on the current default parent
196 - "." is a single node based on the current default parent
197 - "$" resets the default parent to null (implied at the start);
197 - "$" resets the default parent to null (implied at the start);
198 otherwise the default parent is always the last node created
198 otherwise the default parent is always the last node created
199 - "<p" sets the default parent to the backref p
199 - "<p" sets the default parent to the backref p
200 - "*p" is a fork at parent p, which is a backref
200 - "*p" is a fork at parent p, which is a backref
201 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
201 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
202 - "/p2" is a merge of the preceding node and p2
202 - "/p2" is a merge of the preceding node and p2
203 - ":tag" defines a local tag for the preceding node
203 - ":tag" defines a local tag for the preceding node
204 - "@branch" sets the named branch for subsequent nodes
204 - "@branch" sets the named branch for subsequent nodes
205 - "#...\\n" is a comment up to the end of the line
205 - "#...\\n" is a comment up to the end of the line
206
206
207 Whitespace between the above elements is ignored.
207 Whitespace between the above elements is ignored.
208
208
209 A backref is either
209 A backref is either
210
210
211 - a number n, which references the node curr-n, where curr is the current
211 - a number n, which references the node curr-n, where curr is the current
212 node, or
212 node, or
213 - the name of a local tag you placed earlier using ":tag", or
213 - the name of a local tag you placed earlier using ":tag", or
214 - empty to denote the default parent.
214 - empty to denote the default parent.
215
215
216 All string valued-elements are either strictly alphanumeric, or must
216 All string valued-elements are either strictly alphanumeric, or must
217 be enclosed in double quotes ("..."), with "\\" as escape character.
217 be enclosed in double quotes ("..."), with "\\" as escape character.
218 """
218 """
219
219
220 if text is None:
220 if text is None:
221 ui.status(_(b"reading DAG from stdin\n"))
221 ui.status(_(b"reading DAG from stdin\n"))
222 text = ui.fin.read()
222 text = ui.fin.read()
223
223
224 cl = repo.changelog
224 cl = repo.changelog
225 if len(cl) > 0:
225 if len(cl) > 0:
226 raise error.Abort(_(b'repository is not empty'))
226 raise error.Abort(_(b'repository is not empty'))
227
227
228 # determine number of revs in DAG
228 # determine number of revs in DAG
229 total = 0
229 total = 0
230 for type, data in dagparser.parsedag(text):
230 for type, data in dagparser.parsedag(text):
231 if type == b'n':
231 if type == b'n':
232 total += 1
232 total += 1
233
233
234 if mergeable_file:
234 if mergeable_file:
235 linesperrev = 2
235 linesperrev = 2
236 # make a file with k lines per rev
236 # make a file with k lines per rev
237 initialmergedlines = [
237 initialmergedlines = [
238 b'%d' % i for i in pycompat.xrange(0, total * linesperrev)
238 b'%d' % i for i in pycompat.xrange(0, total * linesperrev)
239 ]
239 ]
240 initialmergedlines.append(b"")
240 initialmergedlines.append(b"")
241
241
242 tags = []
242 tags = []
243 progress = ui.makeprogress(
243 progress = ui.makeprogress(
244 _(b'building'), unit=_(b'revisions'), total=total
244 _(b'building'), unit=_(b'revisions'), total=total
245 )
245 )
246 with progress, repo.wlock(), repo.lock(), repo.transaction(b"builddag"):
246 with progress, repo.wlock(), repo.lock(), repo.transaction(b"builddag"):
247 at = -1
247 at = -1
248 atbranch = b'default'
248 atbranch = b'default'
249 nodeids = []
249 nodeids = []
250 id = 0
250 id = 0
251 progress.update(id)
251 progress.update(id)
252 for type, data in dagparser.parsedag(text):
252 for type, data in dagparser.parsedag(text):
253 if type == b'n':
253 if type == b'n':
254 ui.note((b'node %s\n' % pycompat.bytestr(data)))
254 ui.note((b'node %s\n' % pycompat.bytestr(data)))
255 id, ps = data
255 id, ps = data
256
256
257 files = []
257 files = []
258 filecontent = {}
258 filecontent = {}
259
259
260 p2 = None
260 p2 = None
261 if mergeable_file:
261 if mergeable_file:
262 fn = b"mf"
262 fn = b"mf"
263 p1 = repo[ps[0]]
263 p1 = repo[ps[0]]
264 if len(ps) > 1:
264 if len(ps) > 1:
265 p2 = repo[ps[1]]
265 p2 = repo[ps[1]]
266 pa = p1.ancestor(p2)
266 pa = p1.ancestor(p2)
267 base, local, other = [
267 base, local, other = [
268 x[fn].data() for x in (pa, p1, p2)
268 x[fn].data() for x in (pa, p1, p2)
269 ]
269 ]
270 m3 = simplemerge.Merge3Text(base, local, other)
270 m3 = simplemerge.Merge3Text(base, local, other)
271 ml = [l.strip() for l in m3.merge_lines()]
271 ml = [l.strip() for l in m3.merge_lines()]
272 ml.append(b"")
272 ml.append(b"")
273 elif at > 0:
273 elif at > 0:
274 ml = p1[fn].data().split(b"\n")
274 ml = p1[fn].data().split(b"\n")
275 else:
275 else:
276 ml = initialmergedlines
276 ml = initialmergedlines
277 ml[id * linesperrev] += b" r%i" % id
277 ml[id * linesperrev] += b" r%i" % id
278 mergedtext = b"\n".join(ml)
278 mergedtext = b"\n".join(ml)
279 files.append(fn)
279 files.append(fn)
280 filecontent[fn] = mergedtext
280 filecontent[fn] = mergedtext
281
281
282 if overwritten_file:
282 if overwritten_file:
283 fn = b"of"
283 fn = b"of"
284 files.append(fn)
284 files.append(fn)
285 filecontent[fn] = b"r%i\n" % id
285 filecontent[fn] = b"r%i\n" % id
286
286
287 if new_file:
287 if new_file:
288 fn = b"nf%i" % id
288 fn = b"nf%i" % id
289 files.append(fn)
289 files.append(fn)
290 filecontent[fn] = b"r%i\n" % id
290 filecontent[fn] = b"r%i\n" % id
291 if len(ps) > 1:
291 if len(ps) > 1:
292 if not p2:
292 if not p2:
293 p2 = repo[ps[1]]
293 p2 = repo[ps[1]]
294 for fn in p2:
294 for fn in p2:
295 if fn.startswith(b"nf"):
295 if fn.startswith(b"nf"):
296 files.append(fn)
296 files.append(fn)
297 filecontent[fn] = p2[fn].data()
297 filecontent[fn] = p2[fn].data()
298
298
299 def fctxfn(repo, cx, path):
299 def fctxfn(repo, cx, path):
300 if path in filecontent:
300 if path in filecontent:
301 return context.memfilectx(
301 return context.memfilectx(
302 repo, cx, path, filecontent[path]
302 repo, cx, path, filecontent[path]
303 )
303 )
304 return None
304 return None
305
305
306 if len(ps) == 0 or ps[0] < 0:
306 if len(ps) == 0 or ps[0] < 0:
307 pars = [None, None]
307 pars = [None, None]
308 elif len(ps) == 1:
308 elif len(ps) == 1:
309 pars = [nodeids[ps[0]], None]
309 pars = [nodeids[ps[0]], None]
310 else:
310 else:
311 pars = [nodeids[p] for p in ps]
311 pars = [nodeids[p] for p in ps]
312 cx = context.memctx(
312 cx = context.memctx(
313 repo,
313 repo,
314 pars,
314 pars,
315 b"r%i" % id,
315 b"r%i" % id,
316 files,
316 files,
317 fctxfn,
317 fctxfn,
318 date=(id, 0),
318 date=(id, 0),
319 user=b"debugbuilddag",
319 user=b"debugbuilddag",
320 extra={b'branch': atbranch},
320 extra={b'branch': atbranch},
321 )
321 )
322 nodeid = repo.commitctx(cx)
322 nodeid = repo.commitctx(cx)
323 nodeids.append(nodeid)
323 nodeids.append(nodeid)
324 at = id
324 at = id
325 elif type == b'l':
325 elif type == b'l':
326 id, name = data
326 id, name = data
327 ui.note((b'tag %s\n' % name))
327 ui.note((b'tag %s\n' % name))
328 tags.append(b"%s %s\n" % (hex(repo.changelog.node(id)), name))
328 tags.append(b"%s %s\n" % (hex(repo.changelog.node(id)), name))
329 elif type == b'a':
329 elif type == b'a':
330 ui.note((b'branch %s\n' % data))
330 ui.note((b'branch %s\n' % data))
331 atbranch = data
331 atbranch = data
332 progress.update(id)
332 progress.update(id)
333
333
334 if tags:
334 if tags:
335 repo.vfs.write(b"localtags", b"".join(tags))
335 repo.vfs.write(b"localtags", b"".join(tags))
336
336
337
337
338 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
338 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
339 indent_string = b' ' * indent
339 indent_string = b' ' * indent
340 if all:
340 if all:
341 ui.writenoi18n(
341 ui.writenoi18n(
342 b"%sformat: id, p1, p2, cset, delta base, len(delta)\n"
342 b"%sformat: id, p1, p2, cset, delta base, len(delta)\n"
343 % indent_string
343 % indent_string
344 )
344 )
345
345
346 def showchunks(named):
346 def showchunks(named):
347 ui.write(b"\n%s%s\n" % (indent_string, named))
347 ui.write(b"\n%s%s\n" % (indent_string, named))
348 for deltadata in gen.deltaiter():
348 for deltadata in gen.deltaiter():
349 node, p1, p2, cs, deltabase, delta, flags = deltadata
349 node, p1, p2, cs, deltabase, delta, flags = deltadata
350 ui.write(
350 ui.write(
351 b"%s%s %s %s %s %s %d\n"
351 b"%s%s %s %s %s %s %d\n"
352 % (
352 % (
353 indent_string,
353 indent_string,
354 hex(node),
354 hex(node),
355 hex(p1),
355 hex(p1),
356 hex(p2),
356 hex(p2),
357 hex(cs),
357 hex(cs),
358 hex(deltabase),
358 hex(deltabase),
359 len(delta),
359 len(delta),
360 )
360 )
361 )
361 )
362
362
363 gen.changelogheader()
363 gen.changelogheader()
364 showchunks(b"changelog")
364 showchunks(b"changelog")
365 gen.manifestheader()
365 gen.manifestheader()
366 showchunks(b"manifest")
366 showchunks(b"manifest")
367 for chunkdata in iter(gen.filelogheader, {}):
367 for chunkdata in iter(gen.filelogheader, {}):
368 fname = chunkdata[b'filename']
368 fname = chunkdata[b'filename']
369 showchunks(fname)
369 showchunks(fname)
370 else:
370 else:
371 if isinstance(gen, bundle2.unbundle20):
371 if isinstance(gen, bundle2.unbundle20):
372 raise error.Abort(_(b'use debugbundle2 for this file'))
372 raise error.Abort(_(b'use debugbundle2 for this file'))
373 gen.changelogheader()
373 gen.changelogheader()
374 for deltadata in gen.deltaiter():
374 for deltadata in gen.deltaiter():
375 node, p1, p2, cs, deltabase, delta, flags = deltadata
375 node, p1, p2, cs, deltabase, delta, flags = deltadata
376 ui.write(b"%s%s\n" % (indent_string, hex(node)))
376 ui.write(b"%s%s\n" % (indent_string, hex(node)))
377
377
378
378
379 def _debugobsmarkers(ui, part, indent=0, **opts):
379 def _debugobsmarkers(ui, part, indent=0, **opts):
380 """display version and markers contained in 'data'"""
380 """display version and markers contained in 'data'"""
381 opts = pycompat.byteskwargs(opts)
381 opts = pycompat.byteskwargs(opts)
382 data = part.read()
382 data = part.read()
383 indent_string = b' ' * indent
383 indent_string = b' ' * indent
384 try:
384 try:
385 version, markers = obsolete._readmarkers(data)
385 version, markers = obsolete._readmarkers(data)
386 except error.UnknownVersion as exc:
386 except error.UnknownVersion as exc:
387 msg = b"%sunsupported version: %s (%d bytes)\n"
387 msg = b"%sunsupported version: %s (%d bytes)\n"
388 msg %= indent_string, exc.version, len(data)
388 msg %= indent_string, exc.version, len(data)
389 ui.write(msg)
389 ui.write(msg)
390 else:
390 else:
391 msg = b"%sversion: %d (%d bytes)\n"
391 msg = b"%sversion: %d (%d bytes)\n"
392 msg %= indent_string, version, len(data)
392 msg %= indent_string, version, len(data)
393 ui.write(msg)
393 ui.write(msg)
394 fm = ui.formatter(b'debugobsolete', opts)
394 fm = ui.formatter(b'debugobsolete', opts)
395 for rawmarker in sorted(markers):
395 for rawmarker in sorted(markers):
396 m = obsutil.marker(None, rawmarker)
396 m = obsutil.marker(None, rawmarker)
397 fm.startitem()
397 fm.startitem()
398 fm.plain(indent_string)
398 fm.plain(indent_string)
399 cmdutil.showmarker(fm, m)
399 cmdutil.showmarker(fm, m)
400 fm.end()
400 fm.end()
401
401
402
402
403 def _debugphaseheads(ui, data, indent=0):
403 def _debugphaseheads(ui, data, indent=0):
404 """display version and markers contained in 'data'"""
404 """display version and markers contained in 'data'"""
405 indent_string = b' ' * indent
405 indent_string = b' ' * indent
406 headsbyphase = phases.binarydecode(data)
406 headsbyphase = phases.binarydecode(data)
407 for phase in phases.allphases:
407 for phase in phases.allphases:
408 for head in headsbyphase[phase]:
408 for head in headsbyphase[phase]:
409 ui.write(indent_string)
409 ui.write(indent_string)
410 ui.write(b'%s %s\n' % (hex(head), phases.phasenames[phase]))
410 ui.write(b'%s %s\n' % (hex(head), phases.phasenames[phase]))
411
411
412
412
413 def _quasirepr(thing):
413 def _quasirepr(thing):
414 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
414 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
415 return b'{%s}' % (
415 return b'{%s}' % (
416 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing))
416 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing))
417 )
417 )
418 return pycompat.bytestr(repr(thing))
418 return pycompat.bytestr(repr(thing))
419
419
420
420
421 def _debugbundle2(ui, gen, all=None, **opts):
421 def _debugbundle2(ui, gen, all=None, **opts):
422 """lists the contents of a bundle2"""
422 """lists the contents of a bundle2"""
423 if not isinstance(gen, bundle2.unbundle20):
423 if not isinstance(gen, bundle2.unbundle20):
424 raise error.Abort(_(b'not a bundle2 file'))
424 raise error.Abort(_(b'not a bundle2 file'))
425 ui.write((b'Stream params: %s\n' % _quasirepr(gen.params)))
425 ui.write((b'Stream params: %s\n' % _quasirepr(gen.params)))
426 parttypes = opts.get('part_type', [])
426 parttypes = opts.get('part_type', [])
427 for part in gen.iterparts():
427 for part in gen.iterparts():
428 if parttypes and part.type not in parttypes:
428 if parttypes and part.type not in parttypes:
429 continue
429 continue
430 msg = b'%s -- %s (mandatory: %r)\n'
430 msg = b'%s -- %s (mandatory: %r)\n'
431 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
431 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
432 if part.type == b'changegroup':
432 if part.type == b'changegroup':
433 version = part.params.get(b'version', b'01')
433 version = part.params.get(b'version', b'01')
434 cg = changegroup.getunbundler(version, part, b'UN')
434 cg = changegroup.getunbundler(version, part, b'UN')
435 if not ui.quiet:
435 if not ui.quiet:
436 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
436 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
437 if part.type == b'obsmarkers':
437 if part.type == b'obsmarkers':
438 if not ui.quiet:
438 if not ui.quiet:
439 _debugobsmarkers(ui, part, indent=4, **opts)
439 _debugobsmarkers(ui, part, indent=4, **opts)
440 if part.type == b'phase-heads':
440 if part.type == b'phase-heads':
441 if not ui.quiet:
441 if not ui.quiet:
442 _debugphaseheads(ui, part, indent=4)
442 _debugphaseheads(ui, part, indent=4)
443
443
444
444
445 @command(
445 @command(
446 b'debugbundle',
446 b'debugbundle',
447 [
447 [
448 (b'a', b'all', None, _(b'show all details')),
448 (b'a', b'all', None, _(b'show all details')),
449 (b'', b'part-type', [], _(b'show only the named part type')),
449 (b'', b'part-type', [], _(b'show only the named part type')),
450 (b'', b'spec', None, _(b'print the bundlespec of the bundle')),
450 (b'', b'spec', None, _(b'print the bundlespec of the bundle')),
451 ],
451 ],
452 _(b'FILE'),
452 _(b'FILE'),
453 norepo=True,
453 norepo=True,
454 )
454 )
455 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
455 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
456 """lists the contents of a bundle"""
456 """lists the contents of a bundle"""
457 with hg.openpath(ui, bundlepath) as f:
457 with hg.openpath(ui, bundlepath) as f:
458 if spec:
458 if spec:
459 spec = exchange.getbundlespec(ui, f)
459 spec = exchange.getbundlespec(ui, f)
460 ui.write(b'%s\n' % spec)
460 ui.write(b'%s\n' % spec)
461 return
461 return
462
462
463 gen = exchange.readbundle(ui, f, bundlepath)
463 gen = exchange.readbundle(ui, f, bundlepath)
464 if isinstance(gen, bundle2.unbundle20):
464 if isinstance(gen, bundle2.unbundle20):
465 return _debugbundle2(ui, gen, all=all, **opts)
465 return _debugbundle2(ui, gen, all=all, **opts)
466 _debugchangegroup(ui, gen, all=all, **opts)
466 _debugchangegroup(ui, gen, all=all, **opts)
467
467
468
468
469 @command(b'debugcapabilities', [], _(b'PATH'), norepo=True)
469 @command(b'debugcapabilities', [], _(b'PATH'), norepo=True)
470 def debugcapabilities(ui, path, **opts):
470 def debugcapabilities(ui, path, **opts):
471 """lists the capabilities of a remote peer"""
471 """lists the capabilities of a remote peer"""
472 opts = pycompat.byteskwargs(opts)
472 opts = pycompat.byteskwargs(opts)
473 peer = hg.peer(ui, opts, path)
473 peer = hg.peer(ui, opts, path)
474 caps = peer.capabilities()
474 try:
475 ui.writenoi18n(b'Main capabilities:\n')
475 caps = peer.capabilities()
476 for c in sorted(caps):
476 ui.writenoi18n(b'Main capabilities:\n')
477 ui.write(b' %s\n' % c)
477 for c in sorted(caps):
478 b2caps = bundle2.bundle2caps(peer)
478 ui.write(b' %s\n' % c)
479 if b2caps:
479 b2caps = bundle2.bundle2caps(peer)
480 ui.writenoi18n(b'Bundle2 capabilities:\n')
480 if b2caps:
481 for key, values in sorted(pycompat.iteritems(b2caps)):
481 ui.writenoi18n(b'Bundle2 capabilities:\n')
482 ui.write(b' %s\n' % key)
482 for key, values in sorted(pycompat.iteritems(b2caps)):
483 for v in values:
483 ui.write(b' %s\n' % key)
484 ui.write(b' %s\n' % v)
484 for v in values:
485 ui.write(b' %s\n' % v)
486 finally:
487 peer.close()
485
488
486
489
487 @command(
490 @command(
488 b'debugchangedfiles',
491 b'debugchangedfiles',
489 [
492 [
490 (
493 (
491 b'',
494 b'',
492 b'compute',
495 b'compute',
493 False,
496 False,
494 b"compute information instead of reading it from storage",
497 b"compute information instead of reading it from storage",
495 ),
498 ),
496 ],
499 ],
497 b'REV',
500 b'REV',
498 )
501 )
499 def debugchangedfiles(ui, repo, rev, **opts):
502 def debugchangedfiles(ui, repo, rev, **opts):
500 """list the stored files changes for a revision"""
503 """list the stored files changes for a revision"""
501 ctx = scmutil.revsingle(repo, rev, None)
504 ctx = scmutil.revsingle(repo, rev, None)
502 files = None
505 files = None
503
506
504 if opts['compute']:
507 if opts['compute']:
505 files = metadata.compute_all_files_changes(ctx)
508 files = metadata.compute_all_files_changes(ctx)
506 else:
509 else:
507 sd = repo.changelog.sidedata(ctx.rev())
510 sd = repo.changelog.sidedata(ctx.rev())
508 files_block = sd.get(sidedata.SD_FILES)
511 files_block = sd.get(sidedata.SD_FILES)
509 if files_block is not None:
512 if files_block is not None:
510 files = metadata.decode_files_sidedata(sd)
513 files = metadata.decode_files_sidedata(sd)
511 if files is not None:
514 if files is not None:
512 for f in sorted(files.touched):
515 for f in sorted(files.touched):
513 if f in files.added:
516 if f in files.added:
514 action = b"added"
517 action = b"added"
515 elif f in files.removed:
518 elif f in files.removed:
516 action = b"removed"
519 action = b"removed"
517 elif f in files.merged:
520 elif f in files.merged:
518 action = b"merged"
521 action = b"merged"
519 elif f in files.salvaged:
522 elif f in files.salvaged:
520 action = b"salvaged"
523 action = b"salvaged"
521 else:
524 else:
522 action = b"touched"
525 action = b"touched"
523
526
524 copy_parent = b""
527 copy_parent = b""
525 copy_source = b""
528 copy_source = b""
526 if f in files.copied_from_p1:
529 if f in files.copied_from_p1:
527 copy_parent = b"p1"
530 copy_parent = b"p1"
528 copy_source = files.copied_from_p1[f]
531 copy_source = files.copied_from_p1[f]
529 elif f in files.copied_from_p2:
532 elif f in files.copied_from_p2:
530 copy_parent = b"p2"
533 copy_parent = b"p2"
531 copy_source = files.copied_from_p2[f]
534 copy_source = files.copied_from_p2[f]
532
535
533 data = (action, copy_parent, f, copy_source)
536 data = (action, copy_parent, f, copy_source)
534 template = b"%-8s %2s: %s, %s;\n"
537 template = b"%-8s %2s: %s, %s;\n"
535 ui.write(template % data)
538 ui.write(template % data)
536
539
537
540
538 @command(b'debugcheckstate', [], b'')
541 @command(b'debugcheckstate', [], b'')
539 def debugcheckstate(ui, repo):
542 def debugcheckstate(ui, repo):
540 """validate the correctness of the current dirstate"""
543 """validate the correctness of the current dirstate"""
541 parent1, parent2 = repo.dirstate.parents()
544 parent1, parent2 = repo.dirstate.parents()
542 m1 = repo[parent1].manifest()
545 m1 = repo[parent1].manifest()
543 m2 = repo[parent2].manifest()
546 m2 = repo[parent2].manifest()
544 errors = 0
547 errors = 0
545 for f in repo.dirstate:
548 for f in repo.dirstate:
546 state = repo.dirstate[f]
549 state = repo.dirstate[f]
547 if state in b"nr" and f not in m1:
550 if state in b"nr" and f not in m1:
548 ui.warn(_(b"%s in state %s, but not in manifest1\n") % (f, state))
551 ui.warn(_(b"%s in state %s, but not in manifest1\n") % (f, state))
549 errors += 1
552 errors += 1
550 if state in b"a" and f in m1:
553 if state in b"a" and f in m1:
551 ui.warn(_(b"%s in state %s, but also in manifest1\n") % (f, state))
554 ui.warn(_(b"%s in state %s, but also in manifest1\n") % (f, state))
552 errors += 1
555 errors += 1
553 if state in b"m" and f not in m1 and f not in m2:
556 if state in b"m" and f not in m1 and f not in m2:
554 ui.warn(
557 ui.warn(
555 _(b"%s in state %s, but not in either manifest\n") % (f, state)
558 _(b"%s in state %s, but not in either manifest\n") % (f, state)
556 )
559 )
557 errors += 1
560 errors += 1
558 for f in m1:
561 for f in m1:
559 state = repo.dirstate[f]
562 state = repo.dirstate[f]
560 if state not in b"nrm":
563 if state not in b"nrm":
561 ui.warn(_(b"%s in manifest1, but listed as state %s") % (f, state))
564 ui.warn(_(b"%s in manifest1, but listed as state %s") % (f, state))
562 errors += 1
565 errors += 1
563 if errors:
566 if errors:
564 errstr = _(b".hg/dirstate inconsistent with current parent's manifest")
567 errstr = _(b".hg/dirstate inconsistent with current parent's manifest")
565 raise error.Abort(errstr)
568 raise error.Abort(errstr)
566
569
567
570
568 @command(
571 @command(
569 b'debugcolor',
572 b'debugcolor',
570 [(b'', b'style', None, _(b'show all configured styles'))],
573 [(b'', b'style', None, _(b'show all configured styles'))],
571 b'hg debugcolor',
574 b'hg debugcolor',
572 )
575 )
573 def debugcolor(ui, repo, **opts):
576 def debugcolor(ui, repo, **opts):
574 """show available color, effects or style"""
577 """show available color, effects or style"""
575 ui.writenoi18n(b'color mode: %s\n' % stringutil.pprint(ui._colormode))
578 ui.writenoi18n(b'color mode: %s\n' % stringutil.pprint(ui._colormode))
576 if opts.get('style'):
579 if opts.get('style'):
577 return _debugdisplaystyle(ui)
580 return _debugdisplaystyle(ui)
578 else:
581 else:
579 return _debugdisplaycolor(ui)
582 return _debugdisplaycolor(ui)
580
583
581
584
582 def _debugdisplaycolor(ui):
585 def _debugdisplaycolor(ui):
583 ui = ui.copy()
586 ui = ui.copy()
584 ui._styles.clear()
587 ui._styles.clear()
585 for effect in color._activeeffects(ui).keys():
588 for effect in color._activeeffects(ui).keys():
586 ui._styles[effect] = effect
589 ui._styles[effect] = effect
587 if ui._terminfoparams:
590 if ui._terminfoparams:
588 for k, v in ui.configitems(b'color'):
591 for k, v in ui.configitems(b'color'):
589 if k.startswith(b'color.'):
592 if k.startswith(b'color.'):
590 ui._styles[k] = k[6:]
593 ui._styles[k] = k[6:]
591 elif k.startswith(b'terminfo.'):
594 elif k.startswith(b'terminfo.'):
592 ui._styles[k] = k[9:]
595 ui._styles[k] = k[9:]
593 ui.write(_(b'available colors:\n'))
596 ui.write(_(b'available colors:\n'))
594 # sort label with a '_' after the other to group '_background' entry.
597 # sort label with a '_' after the other to group '_background' entry.
595 items = sorted(ui._styles.items(), key=lambda i: (b'_' in i[0], i[0], i[1]))
598 items = sorted(ui._styles.items(), key=lambda i: (b'_' in i[0], i[0], i[1]))
596 for colorname, label in items:
599 for colorname, label in items:
597 ui.write(b'%s\n' % colorname, label=label)
600 ui.write(b'%s\n' % colorname, label=label)
598
601
599
602
600 def _debugdisplaystyle(ui):
603 def _debugdisplaystyle(ui):
601 ui.write(_(b'available style:\n'))
604 ui.write(_(b'available style:\n'))
602 if not ui._styles:
605 if not ui._styles:
603 return
606 return
604 width = max(len(s) for s in ui._styles)
607 width = max(len(s) for s in ui._styles)
605 for label, effects in sorted(ui._styles.items()):
608 for label, effects in sorted(ui._styles.items()):
606 ui.write(b'%s' % label, label=label)
609 ui.write(b'%s' % label, label=label)
607 if effects:
610 if effects:
608 # 50
611 # 50
609 ui.write(b': ')
612 ui.write(b': ')
610 ui.write(b' ' * (max(0, width - len(label))))
613 ui.write(b' ' * (max(0, width - len(label))))
611 ui.write(b', '.join(ui.label(e, e) for e in effects.split()))
614 ui.write(b', '.join(ui.label(e, e) for e in effects.split()))
612 ui.write(b'\n')
615 ui.write(b'\n')
613
616
614
617
615 @command(b'debugcreatestreamclonebundle', [], b'FILE')
618 @command(b'debugcreatestreamclonebundle', [], b'FILE')
616 def debugcreatestreamclonebundle(ui, repo, fname):
619 def debugcreatestreamclonebundle(ui, repo, fname):
617 """create a stream clone bundle file
620 """create a stream clone bundle file
618
621
619 Stream bundles are special bundles that are essentially archives of
622 Stream bundles are special bundles that are essentially archives of
620 revlog files. They are commonly used for cloning very quickly.
623 revlog files. They are commonly used for cloning very quickly.
621 """
624 """
622 # TODO we may want to turn this into an abort when this functionality
625 # TODO we may want to turn this into an abort when this functionality
623 # is moved into `hg bundle`.
626 # is moved into `hg bundle`.
624 if phases.hassecret(repo):
627 if phases.hassecret(repo):
625 ui.warn(
628 ui.warn(
626 _(
629 _(
627 b'(warning: stream clone bundle will contain secret '
630 b'(warning: stream clone bundle will contain secret '
628 b'revisions)\n'
631 b'revisions)\n'
629 )
632 )
630 )
633 )
631
634
632 requirements, gen = streamclone.generatebundlev1(repo)
635 requirements, gen = streamclone.generatebundlev1(repo)
633 changegroup.writechunks(ui, gen, fname)
636 changegroup.writechunks(ui, gen, fname)
634
637
635 ui.write(_(b'bundle requirements: %s\n') % b', '.join(sorted(requirements)))
638 ui.write(_(b'bundle requirements: %s\n') % b', '.join(sorted(requirements)))
636
639
637
640
638 @command(
641 @command(
639 b'debugdag',
642 b'debugdag',
640 [
643 [
641 (b't', b'tags', None, _(b'use tags as labels')),
644 (b't', b'tags', None, _(b'use tags as labels')),
642 (b'b', b'branches', None, _(b'annotate with branch names')),
645 (b'b', b'branches', None, _(b'annotate with branch names')),
643 (b'', b'dots', None, _(b'use dots for runs')),
646 (b'', b'dots', None, _(b'use dots for runs')),
644 (b's', b'spaces', None, _(b'separate elements by spaces')),
647 (b's', b'spaces', None, _(b'separate elements by spaces')),
645 ],
648 ],
646 _(b'[OPTION]... [FILE [REV]...]'),
649 _(b'[OPTION]... [FILE [REV]...]'),
647 optionalrepo=True,
650 optionalrepo=True,
648 )
651 )
649 def debugdag(ui, repo, file_=None, *revs, **opts):
652 def debugdag(ui, repo, file_=None, *revs, **opts):
650 """format the changelog or an index DAG as a concise textual description
653 """format the changelog or an index DAG as a concise textual description
651
654
652 If you pass a revlog index, the revlog's DAG is emitted. If you list
655 If you pass a revlog index, the revlog's DAG is emitted. If you list
653 revision numbers, they get labeled in the output as rN.
656 revision numbers, they get labeled in the output as rN.
654
657
655 Otherwise, the changelog DAG of the current repo is emitted.
658 Otherwise, the changelog DAG of the current repo is emitted.
656 """
659 """
657 spaces = opts.get('spaces')
660 spaces = opts.get('spaces')
658 dots = opts.get('dots')
661 dots = opts.get('dots')
659 if file_:
662 if file_:
660 rlog = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), file_)
663 rlog = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), file_)
661 revs = {int(r) for r in revs}
664 revs = {int(r) for r in revs}
662
665
663 def events():
666 def events():
664 for r in rlog:
667 for r in rlog:
665 yield b'n', (r, list(p for p in rlog.parentrevs(r) if p != -1))
668 yield b'n', (r, list(p for p in rlog.parentrevs(r) if p != -1))
666 if r in revs:
669 if r in revs:
667 yield b'l', (r, b"r%i" % r)
670 yield b'l', (r, b"r%i" % r)
668
671
669 elif repo:
672 elif repo:
670 cl = repo.changelog
673 cl = repo.changelog
671 tags = opts.get('tags')
674 tags = opts.get('tags')
672 branches = opts.get('branches')
675 branches = opts.get('branches')
673 if tags:
676 if tags:
674 labels = {}
677 labels = {}
675 for l, n in repo.tags().items():
678 for l, n in repo.tags().items():
676 labels.setdefault(cl.rev(n), []).append(l)
679 labels.setdefault(cl.rev(n), []).append(l)
677
680
678 def events():
681 def events():
679 b = b"default"
682 b = b"default"
680 for r in cl:
683 for r in cl:
681 if branches:
684 if branches:
682 newb = cl.read(cl.node(r))[5][b'branch']
685 newb = cl.read(cl.node(r))[5][b'branch']
683 if newb != b:
686 if newb != b:
684 yield b'a', newb
687 yield b'a', newb
685 b = newb
688 b = newb
686 yield b'n', (r, list(p for p in cl.parentrevs(r) if p != -1))
689 yield b'n', (r, list(p for p in cl.parentrevs(r) if p != -1))
687 if tags:
690 if tags:
688 ls = labels.get(r)
691 ls = labels.get(r)
689 if ls:
692 if ls:
690 for l in ls:
693 for l in ls:
691 yield b'l', (r, l)
694 yield b'l', (r, l)
692
695
693 else:
696 else:
694 raise error.Abort(_(b'need repo for changelog dag'))
697 raise error.Abort(_(b'need repo for changelog dag'))
695
698
696 for line in dagparser.dagtextlines(
699 for line in dagparser.dagtextlines(
697 events(),
700 events(),
698 addspaces=spaces,
701 addspaces=spaces,
699 wraplabels=True,
702 wraplabels=True,
700 wrapannotations=True,
703 wrapannotations=True,
701 wrapnonlinear=dots,
704 wrapnonlinear=dots,
702 usedots=dots,
705 usedots=dots,
703 maxlinewidth=70,
706 maxlinewidth=70,
704 ):
707 ):
705 ui.write(line)
708 ui.write(line)
706 ui.write(b"\n")
709 ui.write(b"\n")
707
710
708
711
709 @command(b'debugdata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
712 @command(b'debugdata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
710 def debugdata(ui, repo, file_, rev=None, **opts):
713 def debugdata(ui, repo, file_, rev=None, **opts):
711 """dump the contents of a data file revision"""
714 """dump the contents of a data file revision"""
712 opts = pycompat.byteskwargs(opts)
715 opts = pycompat.byteskwargs(opts)
713 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
716 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
714 if rev is not None:
717 if rev is not None:
715 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
718 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
716 file_, rev = None, file_
719 file_, rev = None, file_
717 elif rev is None:
720 elif rev is None:
718 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
721 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
719 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
722 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
720 try:
723 try:
721 ui.write(r.rawdata(r.lookup(rev)))
724 ui.write(r.rawdata(r.lookup(rev)))
722 except KeyError:
725 except KeyError:
723 raise error.Abort(_(b'invalid revision identifier %s') % rev)
726 raise error.Abort(_(b'invalid revision identifier %s') % rev)
724
727
725
728
726 @command(
729 @command(
727 b'debugdate',
730 b'debugdate',
728 [(b'e', b'extended', None, _(b'try extended date formats'))],
731 [(b'e', b'extended', None, _(b'try extended date formats'))],
729 _(b'[-e] DATE [RANGE]'),
732 _(b'[-e] DATE [RANGE]'),
730 norepo=True,
733 norepo=True,
731 optionalrepo=True,
734 optionalrepo=True,
732 )
735 )
733 def debugdate(ui, date, range=None, **opts):
736 def debugdate(ui, date, range=None, **opts):
734 """parse and display a date"""
737 """parse and display a date"""
735 if opts["extended"]:
738 if opts["extended"]:
736 d = dateutil.parsedate(date, dateutil.extendeddateformats)
739 d = dateutil.parsedate(date, dateutil.extendeddateformats)
737 else:
740 else:
738 d = dateutil.parsedate(date)
741 d = dateutil.parsedate(date)
739 ui.writenoi18n(b"internal: %d %d\n" % d)
742 ui.writenoi18n(b"internal: %d %d\n" % d)
740 ui.writenoi18n(b"standard: %s\n" % dateutil.datestr(d))
743 ui.writenoi18n(b"standard: %s\n" % dateutil.datestr(d))
741 if range:
744 if range:
742 m = dateutil.matchdate(range)
745 m = dateutil.matchdate(range)
743 ui.writenoi18n(b"match: %s\n" % m(d[0]))
746 ui.writenoi18n(b"match: %s\n" % m(d[0]))
744
747
745
748
746 @command(
749 @command(
747 b'debugdeltachain',
750 b'debugdeltachain',
748 cmdutil.debugrevlogopts + cmdutil.formatteropts,
751 cmdutil.debugrevlogopts + cmdutil.formatteropts,
749 _(b'-c|-m|FILE'),
752 _(b'-c|-m|FILE'),
750 optionalrepo=True,
753 optionalrepo=True,
751 )
754 )
752 def debugdeltachain(ui, repo, file_=None, **opts):
755 def debugdeltachain(ui, repo, file_=None, **opts):
753 """dump information about delta chains in a revlog
756 """dump information about delta chains in a revlog
754
757
755 Output can be templatized. Available template keywords are:
758 Output can be templatized. Available template keywords are:
756
759
757 :``rev``: revision number
760 :``rev``: revision number
758 :``chainid``: delta chain identifier (numbered by unique base)
761 :``chainid``: delta chain identifier (numbered by unique base)
759 :``chainlen``: delta chain length to this revision
762 :``chainlen``: delta chain length to this revision
760 :``prevrev``: previous revision in delta chain
763 :``prevrev``: previous revision in delta chain
761 :``deltatype``: role of delta / how it was computed
764 :``deltatype``: role of delta / how it was computed
762 :``compsize``: compressed size of revision
765 :``compsize``: compressed size of revision
763 :``uncompsize``: uncompressed size of revision
766 :``uncompsize``: uncompressed size of revision
764 :``chainsize``: total size of compressed revisions in chain
767 :``chainsize``: total size of compressed revisions in chain
765 :``chainratio``: total chain size divided by uncompressed revision size
768 :``chainratio``: total chain size divided by uncompressed revision size
766 (new delta chains typically start at ratio 2.00)
769 (new delta chains typically start at ratio 2.00)
767 :``lindist``: linear distance from base revision in delta chain to end
770 :``lindist``: linear distance from base revision in delta chain to end
768 of this revision
771 of this revision
769 :``extradist``: total size of revisions not part of this delta chain from
772 :``extradist``: total size of revisions not part of this delta chain from
770 base of delta chain to end of this revision; a measurement
773 base of delta chain to end of this revision; a measurement
771 of how much extra data we need to read/seek across to read
774 of how much extra data we need to read/seek across to read
772 the delta chain for this revision
775 the delta chain for this revision
773 :``extraratio``: extradist divided by chainsize; another representation of
776 :``extraratio``: extradist divided by chainsize; another representation of
774 how much unrelated data is needed to load this delta chain
777 how much unrelated data is needed to load this delta chain
775
778
776 If the repository is configured to use the sparse read, additional keywords
779 If the repository is configured to use the sparse read, additional keywords
777 are available:
780 are available:
778
781
779 :``readsize``: total size of data read from the disk for a revision
782 :``readsize``: total size of data read from the disk for a revision
780 (sum of the sizes of all the blocks)
783 (sum of the sizes of all the blocks)
781 :``largestblock``: size of the largest block of data read from the disk
784 :``largestblock``: size of the largest block of data read from the disk
782 :``readdensity``: density of useful bytes in the data read from the disk
785 :``readdensity``: density of useful bytes in the data read from the disk
783 :``srchunks``: in how many data hunks the whole revision would be read
786 :``srchunks``: in how many data hunks the whole revision would be read
784
787
785 The sparse read can be enabled with experimental.sparse-read = True
788 The sparse read can be enabled with experimental.sparse-read = True
786 """
789 """
787 opts = pycompat.byteskwargs(opts)
790 opts = pycompat.byteskwargs(opts)
788 r = cmdutil.openrevlog(repo, b'debugdeltachain', file_, opts)
791 r = cmdutil.openrevlog(repo, b'debugdeltachain', file_, opts)
789 index = r.index
792 index = r.index
790 start = r.start
793 start = r.start
791 length = r.length
794 length = r.length
792 generaldelta = r.version & revlog.FLAG_GENERALDELTA
795 generaldelta = r.version & revlog.FLAG_GENERALDELTA
793 withsparseread = getattr(r, '_withsparseread', False)
796 withsparseread = getattr(r, '_withsparseread', False)
794
797
795 def revinfo(rev):
798 def revinfo(rev):
796 e = index[rev]
799 e = index[rev]
797 compsize = e[1]
800 compsize = e[1]
798 uncompsize = e[2]
801 uncompsize = e[2]
799 chainsize = 0
802 chainsize = 0
800
803
801 if generaldelta:
804 if generaldelta:
802 if e[3] == e[5]:
805 if e[3] == e[5]:
803 deltatype = b'p1'
806 deltatype = b'p1'
804 elif e[3] == e[6]:
807 elif e[3] == e[6]:
805 deltatype = b'p2'
808 deltatype = b'p2'
806 elif e[3] == rev - 1:
809 elif e[3] == rev - 1:
807 deltatype = b'prev'
810 deltatype = b'prev'
808 elif e[3] == rev:
811 elif e[3] == rev:
809 deltatype = b'base'
812 deltatype = b'base'
810 else:
813 else:
811 deltatype = b'other'
814 deltatype = b'other'
812 else:
815 else:
813 if e[3] == rev:
816 if e[3] == rev:
814 deltatype = b'base'
817 deltatype = b'base'
815 else:
818 else:
816 deltatype = b'prev'
819 deltatype = b'prev'
817
820
818 chain = r._deltachain(rev)[0]
821 chain = r._deltachain(rev)[0]
819 for iterrev in chain:
822 for iterrev in chain:
820 e = index[iterrev]
823 e = index[iterrev]
821 chainsize += e[1]
824 chainsize += e[1]
822
825
823 return compsize, uncompsize, deltatype, chain, chainsize
826 return compsize, uncompsize, deltatype, chain, chainsize
824
827
825 fm = ui.formatter(b'debugdeltachain', opts)
828 fm = ui.formatter(b'debugdeltachain', opts)
826
829
827 fm.plain(
830 fm.plain(
828 b' rev chain# chainlen prev delta '
831 b' rev chain# chainlen prev delta '
829 b'size rawsize chainsize ratio lindist extradist '
832 b'size rawsize chainsize ratio lindist extradist '
830 b'extraratio'
833 b'extraratio'
831 )
834 )
832 if withsparseread:
835 if withsparseread:
833 fm.plain(b' readsize largestblk rddensity srchunks')
836 fm.plain(b' readsize largestblk rddensity srchunks')
834 fm.plain(b'\n')
837 fm.plain(b'\n')
835
838
836 chainbases = {}
839 chainbases = {}
837 for rev in r:
840 for rev in r:
838 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
841 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
839 chainbase = chain[0]
842 chainbase = chain[0]
840 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
843 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
841 basestart = start(chainbase)
844 basestart = start(chainbase)
842 revstart = start(rev)
845 revstart = start(rev)
843 lineardist = revstart + comp - basestart
846 lineardist = revstart + comp - basestart
844 extradist = lineardist - chainsize
847 extradist = lineardist - chainsize
845 try:
848 try:
846 prevrev = chain[-2]
849 prevrev = chain[-2]
847 except IndexError:
850 except IndexError:
848 prevrev = -1
851 prevrev = -1
849
852
850 if uncomp != 0:
853 if uncomp != 0:
851 chainratio = float(chainsize) / float(uncomp)
854 chainratio = float(chainsize) / float(uncomp)
852 else:
855 else:
853 chainratio = chainsize
856 chainratio = chainsize
854
857
855 if chainsize != 0:
858 if chainsize != 0:
856 extraratio = float(extradist) / float(chainsize)
859 extraratio = float(extradist) / float(chainsize)
857 else:
860 else:
858 extraratio = extradist
861 extraratio = extradist
859
862
860 fm.startitem()
863 fm.startitem()
861 fm.write(
864 fm.write(
862 b'rev chainid chainlen prevrev deltatype compsize '
865 b'rev chainid chainlen prevrev deltatype compsize '
863 b'uncompsize chainsize chainratio lindist extradist '
866 b'uncompsize chainsize chainratio lindist extradist '
864 b'extraratio',
867 b'extraratio',
865 b'%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
868 b'%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
866 rev,
869 rev,
867 chainid,
870 chainid,
868 len(chain),
871 len(chain),
869 prevrev,
872 prevrev,
870 deltatype,
873 deltatype,
871 comp,
874 comp,
872 uncomp,
875 uncomp,
873 chainsize,
876 chainsize,
874 chainratio,
877 chainratio,
875 lineardist,
878 lineardist,
876 extradist,
879 extradist,
877 extraratio,
880 extraratio,
878 rev=rev,
881 rev=rev,
879 chainid=chainid,
882 chainid=chainid,
880 chainlen=len(chain),
883 chainlen=len(chain),
881 prevrev=prevrev,
884 prevrev=prevrev,
882 deltatype=deltatype,
885 deltatype=deltatype,
883 compsize=comp,
886 compsize=comp,
884 uncompsize=uncomp,
887 uncompsize=uncomp,
885 chainsize=chainsize,
888 chainsize=chainsize,
886 chainratio=chainratio,
889 chainratio=chainratio,
887 lindist=lineardist,
890 lindist=lineardist,
888 extradist=extradist,
891 extradist=extradist,
889 extraratio=extraratio,
892 extraratio=extraratio,
890 )
893 )
891 if withsparseread:
894 if withsparseread:
892 readsize = 0
895 readsize = 0
893 largestblock = 0
896 largestblock = 0
894 srchunks = 0
897 srchunks = 0
895
898
896 for revschunk in deltautil.slicechunk(r, chain):
899 for revschunk in deltautil.slicechunk(r, chain):
897 srchunks += 1
900 srchunks += 1
898 blkend = start(revschunk[-1]) + length(revschunk[-1])
901 blkend = start(revschunk[-1]) + length(revschunk[-1])
899 blksize = blkend - start(revschunk[0])
902 blksize = blkend - start(revschunk[0])
900
903
901 readsize += blksize
904 readsize += blksize
902 if largestblock < blksize:
905 if largestblock < blksize:
903 largestblock = blksize
906 largestblock = blksize
904
907
905 if readsize:
908 if readsize:
906 readdensity = float(chainsize) / float(readsize)
909 readdensity = float(chainsize) / float(readsize)
907 else:
910 else:
908 readdensity = 1
911 readdensity = 1
909
912
910 fm.write(
913 fm.write(
911 b'readsize largestblock readdensity srchunks',
914 b'readsize largestblock readdensity srchunks',
912 b' %10d %10d %9.5f %8d',
915 b' %10d %10d %9.5f %8d',
913 readsize,
916 readsize,
914 largestblock,
917 largestblock,
915 readdensity,
918 readdensity,
916 srchunks,
919 srchunks,
917 readsize=readsize,
920 readsize=readsize,
918 largestblock=largestblock,
921 largestblock=largestblock,
919 readdensity=readdensity,
922 readdensity=readdensity,
920 srchunks=srchunks,
923 srchunks=srchunks,
921 )
924 )
922
925
923 fm.plain(b'\n')
926 fm.plain(b'\n')
924
927
925 fm.end()
928 fm.end()
926
929
927
930
928 @command(
931 @command(
929 b'debugdirstate|debugstate',
932 b'debugdirstate|debugstate',
930 [
933 [
931 (
934 (
932 b'',
935 b'',
933 b'nodates',
936 b'nodates',
934 None,
937 None,
935 _(b'do not display the saved mtime (DEPRECATED)'),
938 _(b'do not display the saved mtime (DEPRECATED)'),
936 ),
939 ),
937 (b'', b'dates', True, _(b'display the saved mtime')),
940 (b'', b'dates', True, _(b'display the saved mtime')),
938 (b'', b'datesort', None, _(b'sort by saved mtime')),
941 (b'', b'datesort', None, _(b'sort by saved mtime')),
939 ],
942 ],
940 _(b'[OPTION]...'),
943 _(b'[OPTION]...'),
941 )
944 )
942 def debugstate(ui, repo, **opts):
945 def debugstate(ui, repo, **opts):
943 """show the contents of the current dirstate"""
946 """show the contents of the current dirstate"""
944
947
945 nodates = not opts['dates']
948 nodates = not opts['dates']
946 if opts.get('nodates') is not None:
949 if opts.get('nodates') is not None:
947 nodates = True
950 nodates = True
948 datesort = opts.get('datesort')
951 datesort = opts.get('datesort')
949
952
950 if datesort:
953 if datesort:
951 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
954 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
952 else:
955 else:
953 keyfunc = None # sort by filename
956 keyfunc = None # sort by filename
954 for file_, ent in sorted(pycompat.iteritems(repo.dirstate), key=keyfunc):
957 for file_, ent in sorted(pycompat.iteritems(repo.dirstate), key=keyfunc):
955 if ent[3] == -1:
958 if ent[3] == -1:
956 timestr = b'unset '
959 timestr = b'unset '
957 elif nodates:
960 elif nodates:
958 timestr = b'set '
961 timestr = b'set '
959 else:
962 else:
960 timestr = time.strftime(
963 timestr = time.strftime(
961 "%Y-%m-%d %H:%M:%S ", time.localtime(ent[3])
964 "%Y-%m-%d %H:%M:%S ", time.localtime(ent[3])
962 )
965 )
963 timestr = encoding.strtolocal(timestr)
966 timestr = encoding.strtolocal(timestr)
964 if ent[1] & 0o20000:
967 if ent[1] & 0o20000:
965 mode = b'lnk'
968 mode = b'lnk'
966 else:
969 else:
967 mode = b'%3o' % (ent[1] & 0o777 & ~util.umask)
970 mode = b'%3o' % (ent[1] & 0o777 & ~util.umask)
968 ui.write(b"%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
971 ui.write(b"%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
969 for f in repo.dirstate.copies():
972 for f in repo.dirstate.copies():
970 ui.write(_(b"copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
973 ui.write(_(b"copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
971
974
972
975
973 @command(
976 @command(
974 b'debugdiscovery',
977 b'debugdiscovery',
975 [
978 [
976 (b'', b'old', None, _(b'use old-style discovery')),
979 (b'', b'old', None, _(b'use old-style discovery')),
977 (
980 (
978 b'',
981 b'',
979 b'nonheads',
982 b'nonheads',
980 None,
983 None,
981 _(b'use old-style discovery with non-heads included'),
984 _(b'use old-style discovery with non-heads included'),
982 ),
985 ),
983 (b'', b'rev', [], b'restrict discovery to this set of revs'),
986 (b'', b'rev', [], b'restrict discovery to this set of revs'),
984 (b'', b'seed', b'12323', b'specify the random seed use for discovery'),
987 (b'', b'seed', b'12323', b'specify the random seed use for discovery'),
985 (
988 (
986 b'',
989 b'',
987 b'local-as-revs',
990 b'local-as-revs',
988 "",
991 "",
989 'treat local has having these revisions only',
992 'treat local has having these revisions only',
990 ),
993 ),
991 (
994 (
992 b'',
995 b'',
993 b'remote-as-revs',
996 b'remote-as-revs',
994 "",
997 "",
995 'use local as remote, with only these these revisions',
998 'use local as remote, with only these these revisions',
996 ),
999 ),
997 ]
1000 ]
998 + cmdutil.remoteopts,
1001 + cmdutil.remoteopts,
999 _(b'[--rev REV] [OTHER]'),
1002 _(b'[--rev REV] [OTHER]'),
1000 )
1003 )
1001 def debugdiscovery(ui, repo, remoteurl=b"default", **opts):
1004 def debugdiscovery(ui, repo, remoteurl=b"default", **opts):
1002 """runs the changeset discovery protocol in isolation
1005 """runs the changeset discovery protocol in isolation
1003
1006
1004 The local peer can be "replaced" by a subset of the local repository by
1007 The local peer can be "replaced" by a subset of the local repository by
1005 using the `--local-as-revs` flag. Int he same way, usual `remote` peer can
1008 using the `--local-as-revs` flag. Int he same way, usual `remote` peer can
1006 be "replaced" by a subset of the local repository using the
1009 be "replaced" by a subset of the local repository using the
1007 `--local-as-revs` flag. This is useful to efficiently debug pathological
1010 `--local-as-revs` flag. This is useful to efficiently debug pathological
1008 discovery situation.
1011 discovery situation.
1009 """
1012 """
1010 opts = pycompat.byteskwargs(opts)
1013 opts = pycompat.byteskwargs(opts)
1011 unfi = repo.unfiltered()
1014 unfi = repo.unfiltered()
1012
1015
1013 # setup potential extra filtering
1016 # setup potential extra filtering
1014 local_revs = opts[b"local_as_revs"]
1017 local_revs = opts[b"local_as_revs"]
1015 remote_revs = opts[b"remote_as_revs"]
1018 remote_revs = opts[b"remote_as_revs"]
1016
1019
1017 # make sure tests are repeatable
1020 # make sure tests are repeatable
1018 random.seed(int(opts[b'seed']))
1021 random.seed(int(opts[b'seed']))
1019
1022
1020 if not remote_revs:
1023 if not remote_revs:
1021
1024
1022 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
1025 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
1023 remote = hg.peer(repo, opts, remoteurl)
1026 remote = hg.peer(repo, opts, remoteurl)
1024 ui.status(_(b'comparing with %s\n') % util.hidepassword(remoteurl))
1027 ui.status(_(b'comparing with %s\n') % util.hidepassword(remoteurl))
1025 else:
1028 else:
1026 branches = (None, [])
1029 branches = (None, [])
1027 remote_filtered_revs = scmutil.revrange(
1030 remote_filtered_revs = scmutil.revrange(
1028 unfi, [b"not (::(%s))" % remote_revs]
1031 unfi, [b"not (::(%s))" % remote_revs]
1029 )
1032 )
1030 remote_filtered_revs = frozenset(remote_filtered_revs)
1033 remote_filtered_revs = frozenset(remote_filtered_revs)
1031
1034
1032 def remote_func(x):
1035 def remote_func(x):
1033 return remote_filtered_revs
1036 return remote_filtered_revs
1034
1037
1035 repoview.filtertable[b'debug-discovery-remote-filter'] = remote_func
1038 repoview.filtertable[b'debug-discovery-remote-filter'] = remote_func
1036
1039
1037 remote = repo.peer()
1040 remote = repo.peer()
1038 remote._repo = remote._repo.filtered(b'debug-discovery-remote-filter')
1041 remote._repo = remote._repo.filtered(b'debug-discovery-remote-filter')
1039
1042
1040 if local_revs:
1043 if local_revs:
1041 local_filtered_revs = scmutil.revrange(
1044 local_filtered_revs = scmutil.revrange(
1042 unfi, [b"not (::(%s))" % local_revs]
1045 unfi, [b"not (::(%s))" % local_revs]
1043 )
1046 )
1044 local_filtered_revs = frozenset(local_filtered_revs)
1047 local_filtered_revs = frozenset(local_filtered_revs)
1045
1048
1046 def local_func(x):
1049 def local_func(x):
1047 return local_filtered_revs
1050 return local_filtered_revs
1048
1051
1049 repoview.filtertable[b'debug-discovery-local-filter'] = local_func
1052 repoview.filtertable[b'debug-discovery-local-filter'] = local_func
1050 repo = repo.filtered(b'debug-discovery-local-filter')
1053 repo = repo.filtered(b'debug-discovery-local-filter')
1051
1054
1052 data = {}
1055 data = {}
1053 if opts.get(b'old'):
1056 if opts.get(b'old'):
1054
1057
1055 def doit(pushedrevs, remoteheads, remote=remote):
1058 def doit(pushedrevs, remoteheads, remote=remote):
1056 if not util.safehasattr(remote, b'branches'):
1059 if not util.safehasattr(remote, b'branches'):
1057 # enable in-client legacy support
1060 # enable in-client legacy support
1058 remote = localrepo.locallegacypeer(remote.local())
1061 remote = localrepo.locallegacypeer(remote.local())
1059 common, _in, hds = treediscovery.findcommonincoming(
1062 common, _in, hds = treediscovery.findcommonincoming(
1060 repo, remote, force=True, audit=data
1063 repo, remote, force=True, audit=data
1061 )
1064 )
1062 common = set(common)
1065 common = set(common)
1063 if not opts.get(b'nonheads'):
1066 if not opts.get(b'nonheads'):
1064 ui.writenoi18n(
1067 ui.writenoi18n(
1065 b"unpruned common: %s\n"
1068 b"unpruned common: %s\n"
1066 % b" ".join(sorted(short(n) for n in common))
1069 % b" ".join(sorted(short(n) for n in common))
1067 )
1070 )
1068
1071
1069 clnode = repo.changelog.node
1072 clnode = repo.changelog.node
1070 common = repo.revs(b'heads(::%ln)', common)
1073 common = repo.revs(b'heads(::%ln)', common)
1071 common = {clnode(r) for r in common}
1074 common = {clnode(r) for r in common}
1072 return common, hds
1075 return common, hds
1073
1076
1074 else:
1077 else:
1075
1078
1076 def doit(pushedrevs, remoteheads, remote=remote):
1079 def doit(pushedrevs, remoteheads, remote=remote):
1077 nodes = None
1080 nodes = None
1078 if pushedrevs:
1081 if pushedrevs:
1079 revs = scmutil.revrange(repo, pushedrevs)
1082 revs = scmutil.revrange(repo, pushedrevs)
1080 nodes = [repo[r].node() for r in revs]
1083 nodes = [repo[r].node() for r in revs]
1081 common, any, hds = setdiscovery.findcommonheads(
1084 common, any, hds = setdiscovery.findcommonheads(
1082 ui, repo, remote, ancestorsof=nodes, audit=data
1085 ui, repo, remote, ancestorsof=nodes, audit=data
1083 )
1086 )
1084 return common, hds
1087 return common, hds
1085
1088
1086 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
1089 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
1087 localrevs = opts[b'rev']
1090 localrevs = opts[b'rev']
1088 with util.timedcm('debug-discovery') as t:
1091 with util.timedcm('debug-discovery') as t:
1089 common, hds = doit(localrevs, remoterevs)
1092 common, hds = doit(localrevs, remoterevs)
1090
1093
1091 # compute all statistics
1094 # compute all statistics
1092 heads_common = set(common)
1095 heads_common = set(common)
1093 heads_remote = set(hds)
1096 heads_remote = set(hds)
1094 heads_local = set(repo.heads())
1097 heads_local = set(repo.heads())
1095 # note: they cannot be a local or remote head that is in common and not
1098 # note: they cannot be a local or remote head that is in common and not
1096 # itself a head of common.
1099 # itself a head of common.
1097 heads_common_local = heads_common & heads_local
1100 heads_common_local = heads_common & heads_local
1098 heads_common_remote = heads_common & heads_remote
1101 heads_common_remote = heads_common & heads_remote
1099 heads_common_both = heads_common & heads_remote & heads_local
1102 heads_common_both = heads_common & heads_remote & heads_local
1100
1103
1101 all = repo.revs(b'all()')
1104 all = repo.revs(b'all()')
1102 common = repo.revs(b'::%ln', common)
1105 common = repo.revs(b'::%ln', common)
1103 roots_common = repo.revs(b'roots(::%ld)', common)
1106 roots_common = repo.revs(b'roots(::%ld)', common)
1104 missing = repo.revs(b'not ::%ld', common)
1107 missing = repo.revs(b'not ::%ld', common)
1105 heads_missing = repo.revs(b'heads(%ld)', missing)
1108 heads_missing = repo.revs(b'heads(%ld)', missing)
1106 roots_missing = repo.revs(b'roots(%ld)', missing)
1109 roots_missing = repo.revs(b'roots(%ld)', missing)
1107 assert len(common) + len(missing) == len(all)
1110 assert len(common) + len(missing) == len(all)
1108
1111
1109 initial_undecided = repo.revs(
1112 initial_undecided = repo.revs(
1110 b'not (::%ln or %ln::)', heads_common_remote, heads_common_local
1113 b'not (::%ln or %ln::)', heads_common_remote, heads_common_local
1111 )
1114 )
1112 heads_initial_undecided = repo.revs(b'heads(%ld)', initial_undecided)
1115 heads_initial_undecided = repo.revs(b'heads(%ld)', initial_undecided)
1113 roots_initial_undecided = repo.revs(b'roots(%ld)', initial_undecided)
1116 roots_initial_undecided = repo.revs(b'roots(%ld)', initial_undecided)
1114 common_initial_undecided = initial_undecided & common
1117 common_initial_undecided = initial_undecided & common
1115 missing_initial_undecided = initial_undecided & missing
1118 missing_initial_undecided = initial_undecided & missing
1116
1119
1117 data[b'elapsed'] = t.elapsed
1120 data[b'elapsed'] = t.elapsed
1118 data[b'nb-common-heads'] = len(heads_common)
1121 data[b'nb-common-heads'] = len(heads_common)
1119 data[b'nb-common-heads-local'] = len(heads_common_local)
1122 data[b'nb-common-heads-local'] = len(heads_common_local)
1120 data[b'nb-common-heads-remote'] = len(heads_common_remote)
1123 data[b'nb-common-heads-remote'] = len(heads_common_remote)
1121 data[b'nb-common-heads-both'] = len(heads_common_both)
1124 data[b'nb-common-heads-both'] = len(heads_common_both)
1122 data[b'nb-common-roots'] = len(roots_common)
1125 data[b'nb-common-roots'] = len(roots_common)
1123 data[b'nb-head-local'] = len(heads_local)
1126 data[b'nb-head-local'] = len(heads_local)
1124 data[b'nb-head-local-missing'] = len(heads_local) - len(heads_common_local)
1127 data[b'nb-head-local-missing'] = len(heads_local) - len(heads_common_local)
1125 data[b'nb-head-remote'] = len(heads_remote)
1128 data[b'nb-head-remote'] = len(heads_remote)
1126 data[b'nb-head-remote-unknown'] = len(heads_remote) - len(
1129 data[b'nb-head-remote-unknown'] = len(heads_remote) - len(
1127 heads_common_remote
1130 heads_common_remote
1128 )
1131 )
1129 data[b'nb-revs'] = len(all)
1132 data[b'nb-revs'] = len(all)
1130 data[b'nb-revs-common'] = len(common)
1133 data[b'nb-revs-common'] = len(common)
1131 data[b'nb-revs-missing'] = len(missing)
1134 data[b'nb-revs-missing'] = len(missing)
1132 data[b'nb-missing-heads'] = len(heads_missing)
1135 data[b'nb-missing-heads'] = len(heads_missing)
1133 data[b'nb-missing-roots'] = len(roots_missing)
1136 data[b'nb-missing-roots'] = len(roots_missing)
1134 data[b'nb-ini_und'] = len(initial_undecided)
1137 data[b'nb-ini_und'] = len(initial_undecided)
1135 data[b'nb-ini_und-heads'] = len(heads_initial_undecided)
1138 data[b'nb-ini_und-heads'] = len(heads_initial_undecided)
1136 data[b'nb-ini_und-roots'] = len(roots_initial_undecided)
1139 data[b'nb-ini_und-roots'] = len(roots_initial_undecided)
1137 data[b'nb-ini_und-common'] = len(common_initial_undecided)
1140 data[b'nb-ini_und-common'] = len(common_initial_undecided)
1138 data[b'nb-ini_und-missing'] = len(missing_initial_undecided)
1141 data[b'nb-ini_und-missing'] = len(missing_initial_undecided)
1139
1142
1140 # display discovery summary
1143 # display discovery summary
1141 ui.writenoi18n(b"elapsed time: %(elapsed)f seconds\n" % data)
1144 ui.writenoi18n(b"elapsed time: %(elapsed)f seconds\n" % data)
1142 ui.writenoi18n(b"round-trips: %(total-roundtrips)9d\n" % data)
1145 ui.writenoi18n(b"round-trips: %(total-roundtrips)9d\n" % data)
1143 ui.writenoi18n(b"heads summary:\n")
1146 ui.writenoi18n(b"heads summary:\n")
1144 ui.writenoi18n(b" total common heads: %(nb-common-heads)9d\n" % data)
1147 ui.writenoi18n(b" total common heads: %(nb-common-heads)9d\n" % data)
1145 ui.writenoi18n(
1148 ui.writenoi18n(
1146 b" also local heads: %(nb-common-heads-local)9d\n" % data
1149 b" also local heads: %(nb-common-heads-local)9d\n" % data
1147 )
1150 )
1148 ui.writenoi18n(
1151 ui.writenoi18n(
1149 b" also remote heads: %(nb-common-heads-remote)9d\n" % data
1152 b" also remote heads: %(nb-common-heads-remote)9d\n" % data
1150 )
1153 )
1151 ui.writenoi18n(b" both: %(nb-common-heads-both)9d\n" % data)
1154 ui.writenoi18n(b" both: %(nb-common-heads-both)9d\n" % data)
1152 ui.writenoi18n(b" local heads: %(nb-head-local)9d\n" % data)
1155 ui.writenoi18n(b" local heads: %(nb-head-local)9d\n" % data)
1153 ui.writenoi18n(
1156 ui.writenoi18n(
1154 b" common: %(nb-common-heads-local)9d\n" % data
1157 b" common: %(nb-common-heads-local)9d\n" % data
1155 )
1158 )
1156 ui.writenoi18n(
1159 ui.writenoi18n(
1157 b" missing: %(nb-head-local-missing)9d\n" % data
1160 b" missing: %(nb-head-local-missing)9d\n" % data
1158 )
1161 )
1159 ui.writenoi18n(b" remote heads: %(nb-head-remote)9d\n" % data)
1162 ui.writenoi18n(b" remote heads: %(nb-head-remote)9d\n" % data)
1160 ui.writenoi18n(
1163 ui.writenoi18n(
1161 b" common: %(nb-common-heads-remote)9d\n" % data
1164 b" common: %(nb-common-heads-remote)9d\n" % data
1162 )
1165 )
1163 ui.writenoi18n(
1166 ui.writenoi18n(
1164 b" unknown: %(nb-head-remote-unknown)9d\n" % data
1167 b" unknown: %(nb-head-remote-unknown)9d\n" % data
1165 )
1168 )
1166 ui.writenoi18n(b"local changesets: %(nb-revs)9d\n" % data)
1169 ui.writenoi18n(b"local changesets: %(nb-revs)9d\n" % data)
1167 ui.writenoi18n(b" common: %(nb-revs-common)9d\n" % data)
1170 ui.writenoi18n(b" common: %(nb-revs-common)9d\n" % data)
1168 ui.writenoi18n(b" heads: %(nb-common-heads)9d\n" % data)
1171 ui.writenoi18n(b" heads: %(nb-common-heads)9d\n" % data)
1169 ui.writenoi18n(b" roots: %(nb-common-roots)9d\n" % data)
1172 ui.writenoi18n(b" roots: %(nb-common-roots)9d\n" % data)
1170 ui.writenoi18n(b" missing: %(nb-revs-missing)9d\n" % data)
1173 ui.writenoi18n(b" missing: %(nb-revs-missing)9d\n" % data)
1171 ui.writenoi18n(b" heads: %(nb-missing-heads)9d\n" % data)
1174 ui.writenoi18n(b" heads: %(nb-missing-heads)9d\n" % data)
1172 ui.writenoi18n(b" roots: %(nb-missing-roots)9d\n" % data)
1175 ui.writenoi18n(b" roots: %(nb-missing-roots)9d\n" % data)
1173 ui.writenoi18n(b" first undecided set: %(nb-ini_und)9d\n" % data)
1176 ui.writenoi18n(b" first undecided set: %(nb-ini_und)9d\n" % data)
1174 ui.writenoi18n(b" heads: %(nb-ini_und-heads)9d\n" % data)
1177 ui.writenoi18n(b" heads: %(nb-ini_und-heads)9d\n" % data)
1175 ui.writenoi18n(b" roots: %(nb-ini_und-roots)9d\n" % data)
1178 ui.writenoi18n(b" roots: %(nb-ini_und-roots)9d\n" % data)
1176 ui.writenoi18n(b" common: %(nb-ini_und-common)9d\n" % data)
1179 ui.writenoi18n(b" common: %(nb-ini_und-common)9d\n" % data)
1177 ui.writenoi18n(b" missing: %(nb-ini_und-missing)9d\n" % data)
1180 ui.writenoi18n(b" missing: %(nb-ini_und-missing)9d\n" % data)
1178
1181
1179 if ui.verbose:
1182 if ui.verbose:
1180 ui.writenoi18n(
1183 ui.writenoi18n(
1181 b"common heads: %s\n"
1184 b"common heads: %s\n"
1182 % b" ".join(sorted(short(n) for n in heads_common))
1185 % b" ".join(sorted(short(n) for n in heads_common))
1183 )
1186 )
1184
1187
1185
1188
1186 _chunksize = 4 << 10
1189 _chunksize = 4 << 10
1187
1190
1188
1191
1189 @command(
1192 @command(
1190 b'debugdownload',
1193 b'debugdownload',
1191 [
1194 [
1192 (b'o', b'output', b'', _(b'path')),
1195 (b'o', b'output', b'', _(b'path')),
1193 ],
1196 ],
1194 optionalrepo=True,
1197 optionalrepo=True,
1195 )
1198 )
1196 def debugdownload(ui, repo, url, output=None, **opts):
1199 def debugdownload(ui, repo, url, output=None, **opts):
1197 """download a resource using Mercurial logic and config"""
1200 """download a resource using Mercurial logic and config"""
1198 fh = urlmod.open(ui, url, output)
1201 fh = urlmod.open(ui, url, output)
1199
1202
1200 dest = ui
1203 dest = ui
1201 if output:
1204 if output:
1202 dest = open(output, b"wb", _chunksize)
1205 dest = open(output, b"wb", _chunksize)
1203 try:
1206 try:
1204 data = fh.read(_chunksize)
1207 data = fh.read(_chunksize)
1205 while data:
1208 while data:
1206 dest.write(data)
1209 dest.write(data)
1207 data = fh.read(_chunksize)
1210 data = fh.read(_chunksize)
1208 finally:
1211 finally:
1209 if output:
1212 if output:
1210 dest.close()
1213 dest.close()
1211
1214
1212
1215
1213 @command(b'debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
1216 @command(b'debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
1214 def debugextensions(ui, repo, **opts):
1217 def debugextensions(ui, repo, **opts):
1215 '''show information about active extensions'''
1218 '''show information about active extensions'''
1216 opts = pycompat.byteskwargs(opts)
1219 opts = pycompat.byteskwargs(opts)
1217 exts = extensions.extensions(ui)
1220 exts = extensions.extensions(ui)
1218 hgver = util.version()
1221 hgver = util.version()
1219 fm = ui.formatter(b'debugextensions', opts)
1222 fm = ui.formatter(b'debugextensions', opts)
1220 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
1223 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
1221 isinternal = extensions.ismoduleinternal(extmod)
1224 isinternal = extensions.ismoduleinternal(extmod)
1222 extsource = None
1225 extsource = None
1223
1226
1224 if util.safehasattr(extmod, '__file__'):
1227 if util.safehasattr(extmod, '__file__'):
1225 extsource = pycompat.fsencode(extmod.__file__)
1228 extsource = pycompat.fsencode(extmod.__file__)
1226 elif getattr(sys, 'oxidized', False):
1229 elif getattr(sys, 'oxidized', False):
1227 extsource = pycompat.sysexecutable
1230 extsource = pycompat.sysexecutable
1228 if isinternal:
1231 if isinternal:
1229 exttestedwith = [] # never expose magic string to users
1232 exttestedwith = [] # never expose magic string to users
1230 else:
1233 else:
1231 exttestedwith = getattr(extmod, 'testedwith', b'').split()
1234 exttestedwith = getattr(extmod, 'testedwith', b'').split()
1232 extbuglink = getattr(extmod, 'buglink', None)
1235 extbuglink = getattr(extmod, 'buglink', None)
1233
1236
1234 fm.startitem()
1237 fm.startitem()
1235
1238
1236 if ui.quiet or ui.verbose:
1239 if ui.quiet or ui.verbose:
1237 fm.write(b'name', b'%s\n', extname)
1240 fm.write(b'name', b'%s\n', extname)
1238 else:
1241 else:
1239 fm.write(b'name', b'%s', extname)
1242 fm.write(b'name', b'%s', extname)
1240 if isinternal or hgver in exttestedwith:
1243 if isinternal or hgver in exttestedwith:
1241 fm.plain(b'\n')
1244 fm.plain(b'\n')
1242 elif not exttestedwith:
1245 elif not exttestedwith:
1243 fm.plain(_(b' (untested!)\n'))
1246 fm.plain(_(b' (untested!)\n'))
1244 else:
1247 else:
1245 lasttestedversion = exttestedwith[-1]
1248 lasttestedversion = exttestedwith[-1]
1246 fm.plain(b' (%s!)\n' % lasttestedversion)
1249 fm.plain(b' (%s!)\n' % lasttestedversion)
1247
1250
1248 fm.condwrite(
1251 fm.condwrite(
1249 ui.verbose and extsource,
1252 ui.verbose and extsource,
1250 b'source',
1253 b'source',
1251 _(b' location: %s\n'),
1254 _(b' location: %s\n'),
1252 extsource or b"",
1255 extsource or b"",
1253 )
1256 )
1254
1257
1255 if ui.verbose:
1258 if ui.verbose:
1256 fm.plain(_(b' bundled: %s\n') % [b'no', b'yes'][isinternal])
1259 fm.plain(_(b' bundled: %s\n') % [b'no', b'yes'][isinternal])
1257 fm.data(bundled=isinternal)
1260 fm.data(bundled=isinternal)
1258
1261
1259 fm.condwrite(
1262 fm.condwrite(
1260 ui.verbose and exttestedwith,
1263 ui.verbose and exttestedwith,
1261 b'testedwith',
1264 b'testedwith',
1262 _(b' tested with: %s\n'),
1265 _(b' tested with: %s\n'),
1263 fm.formatlist(exttestedwith, name=b'ver'),
1266 fm.formatlist(exttestedwith, name=b'ver'),
1264 )
1267 )
1265
1268
1266 fm.condwrite(
1269 fm.condwrite(
1267 ui.verbose and extbuglink,
1270 ui.verbose and extbuglink,
1268 b'buglink',
1271 b'buglink',
1269 _(b' bug reporting: %s\n'),
1272 _(b' bug reporting: %s\n'),
1270 extbuglink or b"",
1273 extbuglink or b"",
1271 )
1274 )
1272
1275
1273 fm.end()
1276 fm.end()
1274
1277
1275
1278
1276 @command(
1279 @command(
1277 b'debugfileset',
1280 b'debugfileset',
1278 [
1281 [
1279 (
1282 (
1280 b'r',
1283 b'r',
1281 b'rev',
1284 b'rev',
1282 b'',
1285 b'',
1283 _(b'apply the filespec on this revision'),
1286 _(b'apply the filespec on this revision'),
1284 _(b'REV'),
1287 _(b'REV'),
1285 ),
1288 ),
1286 (
1289 (
1287 b'',
1290 b'',
1288 b'all-files',
1291 b'all-files',
1289 False,
1292 False,
1290 _(b'test files from all revisions and working directory'),
1293 _(b'test files from all revisions and working directory'),
1291 ),
1294 ),
1292 (
1295 (
1293 b's',
1296 b's',
1294 b'show-matcher',
1297 b'show-matcher',
1295 None,
1298 None,
1296 _(b'print internal representation of matcher'),
1299 _(b'print internal representation of matcher'),
1297 ),
1300 ),
1298 (
1301 (
1299 b'p',
1302 b'p',
1300 b'show-stage',
1303 b'show-stage',
1301 [],
1304 [],
1302 _(b'print parsed tree at the given stage'),
1305 _(b'print parsed tree at the given stage'),
1303 _(b'NAME'),
1306 _(b'NAME'),
1304 ),
1307 ),
1305 ],
1308 ],
1306 _(b'[-r REV] [--all-files] [OPTION]... FILESPEC'),
1309 _(b'[-r REV] [--all-files] [OPTION]... FILESPEC'),
1307 )
1310 )
1308 def debugfileset(ui, repo, expr, **opts):
1311 def debugfileset(ui, repo, expr, **opts):
1309 '''parse and apply a fileset specification'''
1312 '''parse and apply a fileset specification'''
1310 from . import fileset
1313 from . import fileset
1311
1314
1312 fileset.symbols # force import of fileset so we have predicates to optimize
1315 fileset.symbols # force import of fileset so we have predicates to optimize
1313 opts = pycompat.byteskwargs(opts)
1316 opts = pycompat.byteskwargs(opts)
1314 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
1317 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
1315
1318
1316 stages = [
1319 stages = [
1317 (b'parsed', pycompat.identity),
1320 (b'parsed', pycompat.identity),
1318 (b'analyzed', filesetlang.analyze),
1321 (b'analyzed', filesetlang.analyze),
1319 (b'optimized', filesetlang.optimize),
1322 (b'optimized', filesetlang.optimize),
1320 ]
1323 ]
1321 stagenames = {n for n, f in stages}
1324 stagenames = {n for n, f in stages}
1322
1325
1323 showalways = set()
1326 showalways = set()
1324 if ui.verbose and not opts[b'show_stage']:
1327 if ui.verbose and not opts[b'show_stage']:
1325 # show parsed tree by --verbose (deprecated)
1328 # show parsed tree by --verbose (deprecated)
1326 showalways.add(b'parsed')
1329 showalways.add(b'parsed')
1327 if opts[b'show_stage'] == [b'all']:
1330 if opts[b'show_stage'] == [b'all']:
1328 showalways.update(stagenames)
1331 showalways.update(stagenames)
1329 else:
1332 else:
1330 for n in opts[b'show_stage']:
1333 for n in opts[b'show_stage']:
1331 if n not in stagenames:
1334 if n not in stagenames:
1332 raise error.Abort(_(b'invalid stage name: %s') % n)
1335 raise error.Abort(_(b'invalid stage name: %s') % n)
1333 showalways.update(opts[b'show_stage'])
1336 showalways.update(opts[b'show_stage'])
1334
1337
1335 tree = filesetlang.parse(expr)
1338 tree = filesetlang.parse(expr)
1336 for n, f in stages:
1339 for n, f in stages:
1337 tree = f(tree)
1340 tree = f(tree)
1338 if n in showalways:
1341 if n in showalways:
1339 if opts[b'show_stage'] or n != b'parsed':
1342 if opts[b'show_stage'] or n != b'parsed':
1340 ui.write(b"* %s:\n" % n)
1343 ui.write(b"* %s:\n" % n)
1341 ui.write(filesetlang.prettyformat(tree), b"\n")
1344 ui.write(filesetlang.prettyformat(tree), b"\n")
1342
1345
1343 files = set()
1346 files = set()
1344 if opts[b'all_files']:
1347 if opts[b'all_files']:
1345 for r in repo:
1348 for r in repo:
1346 c = repo[r]
1349 c = repo[r]
1347 files.update(c.files())
1350 files.update(c.files())
1348 files.update(c.substate)
1351 files.update(c.substate)
1349 if opts[b'all_files'] or ctx.rev() is None:
1352 if opts[b'all_files'] or ctx.rev() is None:
1350 wctx = repo[None]
1353 wctx = repo[None]
1351 files.update(
1354 files.update(
1352 repo.dirstate.walk(
1355 repo.dirstate.walk(
1353 scmutil.matchall(repo),
1356 scmutil.matchall(repo),
1354 subrepos=list(wctx.substate),
1357 subrepos=list(wctx.substate),
1355 unknown=True,
1358 unknown=True,
1356 ignored=True,
1359 ignored=True,
1357 )
1360 )
1358 )
1361 )
1359 files.update(wctx.substate)
1362 files.update(wctx.substate)
1360 else:
1363 else:
1361 files.update(ctx.files())
1364 files.update(ctx.files())
1362 files.update(ctx.substate)
1365 files.update(ctx.substate)
1363
1366
1364 m = ctx.matchfileset(repo.getcwd(), expr)
1367 m = ctx.matchfileset(repo.getcwd(), expr)
1365 if opts[b'show_matcher'] or (opts[b'show_matcher'] is None and ui.verbose):
1368 if opts[b'show_matcher'] or (opts[b'show_matcher'] is None and ui.verbose):
1366 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
1369 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
1367 for f in sorted(files):
1370 for f in sorted(files):
1368 if not m(f):
1371 if not m(f):
1369 continue
1372 continue
1370 ui.write(b"%s\n" % f)
1373 ui.write(b"%s\n" % f)
1371
1374
1372
1375
1373 @command(b'debugformat', [] + cmdutil.formatteropts)
1376 @command(b'debugformat', [] + cmdutil.formatteropts)
1374 def debugformat(ui, repo, **opts):
1377 def debugformat(ui, repo, **opts):
1375 """display format information about the current repository
1378 """display format information about the current repository
1376
1379
1377 Use --verbose to get extra information about current config value and
1380 Use --verbose to get extra information about current config value and
1378 Mercurial default."""
1381 Mercurial default."""
1379 opts = pycompat.byteskwargs(opts)
1382 opts = pycompat.byteskwargs(opts)
1380 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
1383 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
1381 maxvariantlength = max(len(b'format-variant'), maxvariantlength)
1384 maxvariantlength = max(len(b'format-variant'), maxvariantlength)
1382
1385
1383 def makeformatname(name):
1386 def makeformatname(name):
1384 return b'%s:' + (b' ' * (maxvariantlength - len(name)))
1387 return b'%s:' + (b' ' * (maxvariantlength - len(name)))
1385
1388
1386 fm = ui.formatter(b'debugformat', opts)
1389 fm = ui.formatter(b'debugformat', opts)
1387 if fm.isplain():
1390 if fm.isplain():
1388
1391
1389 def formatvalue(value):
1392 def formatvalue(value):
1390 if util.safehasattr(value, b'startswith'):
1393 if util.safehasattr(value, b'startswith'):
1391 return value
1394 return value
1392 if value:
1395 if value:
1393 return b'yes'
1396 return b'yes'
1394 else:
1397 else:
1395 return b'no'
1398 return b'no'
1396
1399
1397 else:
1400 else:
1398 formatvalue = pycompat.identity
1401 formatvalue = pycompat.identity
1399
1402
1400 fm.plain(b'format-variant')
1403 fm.plain(b'format-variant')
1401 fm.plain(b' ' * (maxvariantlength - len(b'format-variant')))
1404 fm.plain(b' ' * (maxvariantlength - len(b'format-variant')))
1402 fm.plain(b' repo')
1405 fm.plain(b' repo')
1403 if ui.verbose:
1406 if ui.verbose:
1404 fm.plain(b' config default')
1407 fm.plain(b' config default')
1405 fm.plain(b'\n')
1408 fm.plain(b'\n')
1406 for fv in upgrade.allformatvariant:
1409 for fv in upgrade.allformatvariant:
1407 fm.startitem()
1410 fm.startitem()
1408 repovalue = fv.fromrepo(repo)
1411 repovalue = fv.fromrepo(repo)
1409 configvalue = fv.fromconfig(repo)
1412 configvalue = fv.fromconfig(repo)
1410
1413
1411 if repovalue != configvalue:
1414 if repovalue != configvalue:
1412 namelabel = b'formatvariant.name.mismatchconfig'
1415 namelabel = b'formatvariant.name.mismatchconfig'
1413 repolabel = b'formatvariant.repo.mismatchconfig'
1416 repolabel = b'formatvariant.repo.mismatchconfig'
1414 elif repovalue != fv.default:
1417 elif repovalue != fv.default:
1415 namelabel = b'formatvariant.name.mismatchdefault'
1418 namelabel = b'formatvariant.name.mismatchdefault'
1416 repolabel = b'formatvariant.repo.mismatchdefault'
1419 repolabel = b'formatvariant.repo.mismatchdefault'
1417 else:
1420 else:
1418 namelabel = b'formatvariant.name.uptodate'
1421 namelabel = b'formatvariant.name.uptodate'
1419 repolabel = b'formatvariant.repo.uptodate'
1422 repolabel = b'formatvariant.repo.uptodate'
1420
1423
1421 fm.write(b'name', makeformatname(fv.name), fv.name, label=namelabel)
1424 fm.write(b'name', makeformatname(fv.name), fv.name, label=namelabel)
1422 fm.write(b'repo', b' %3s', formatvalue(repovalue), label=repolabel)
1425 fm.write(b'repo', b' %3s', formatvalue(repovalue), label=repolabel)
1423 if fv.default != configvalue:
1426 if fv.default != configvalue:
1424 configlabel = b'formatvariant.config.special'
1427 configlabel = b'formatvariant.config.special'
1425 else:
1428 else:
1426 configlabel = b'formatvariant.config.default'
1429 configlabel = b'formatvariant.config.default'
1427 fm.condwrite(
1430 fm.condwrite(
1428 ui.verbose,
1431 ui.verbose,
1429 b'config',
1432 b'config',
1430 b' %6s',
1433 b' %6s',
1431 formatvalue(configvalue),
1434 formatvalue(configvalue),
1432 label=configlabel,
1435 label=configlabel,
1433 )
1436 )
1434 fm.condwrite(
1437 fm.condwrite(
1435 ui.verbose,
1438 ui.verbose,
1436 b'default',
1439 b'default',
1437 b' %7s',
1440 b' %7s',
1438 formatvalue(fv.default),
1441 formatvalue(fv.default),
1439 label=b'formatvariant.default',
1442 label=b'formatvariant.default',
1440 )
1443 )
1441 fm.plain(b'\n')
1444 fm.plain(b'\n')
1442 fm.end()
1445 fm.end()
1443
1446
1444
1447
1445 @command(b'debugfsinfo', [], _(b'[PATH]'), norepo=True)
1448 @command(b'debugfsinfo', [], _(b'[PATH]'), norepo=True)
1446 def debugfsinfo(ui, path=b"."):
1449 def debugfsinfo(ui, path=b"."):
1447 """show information detected about current filesystem"""
1450 """show information detected about current filesystem"""
1448 ui.writenoi18n(b'path: %s\n' % path)
1451 ui.writenoi18n(b'path: %s\n' % path)
1449 ui.writenoi18n(
1452 ui.writenoi18n(
1450 b'mounted on: %s\n' % (util.getfsmountpoint(path) or b'(unknown)')
1453 b'mounted on: %s\n' % (util.getfsmountpoint(path) or b'(unknown)')
1451 )
1454 )
1452 ui.writenoi18n(b'exec: %s\n' % (util.checkexec(path) and b'yes' or b'no'))
1455 ui.writenoi18n(b'exec: %s\n' % (util.checkexec(path) and b'yes' or b'no'))
1453 ui.writenoi18n(b'fstype: %s\n' % (util.getfstype(path) or b'(unknown)'))
1456 ui.writenoi18n(b'fstype: %s\n' % (util.getfstype(path) or b'(unknown)'))
1454 ui.writenoi18n(
1457 ui.writenoi18n(
1455 b'symlink: %s\n' % (util.checklink(path) and b'yes' or b'no')
1458 b'symlink: %s\n' % (util.checklink(path) and b'yes' or b'no')
1456 )
1459 )
1457 ui.writenoi18n(
1460 ui.writenoi18n(
1458 b'hardlink: %s\n' % (util.checknlink(path) and b'yes' or b'no')
1461 b'hardlink: %s\n' % (util.checknlink(path) and b'yes' or b'no')
1459 )
1462 )
1460 casesensitive = b'(unknown)'
1463 casesensitive = b'(unknown)'
1461 try:
1464 try:
1462 with pycompat.namedtempfile(prefix=b'.debugfsinfo', dir=path) as f:
1465 with pycompat.namedtempfile(prefix=b'.debugfsinfo', dir=path) as f:
1463 casesensitive = util.fscasesensitive(f.name) and b'yes' or b'no'
1466 casesensitive = util.fscasesensitive(f.name) and b'yes' or b'no'
1464 except OSError:
1467 except OSError:
1465 pass
1468 pass
1466 ui.writenoi18n(b'case-sensitive: %s\n' % casesensitive)
1469 ui.writenoi18n(b'case-sensitive: %s\n' % casesensitive)
1467
1470
1468
1471
1469 @command(
1472 @command(
1470 b'debuggetbundle',
1473 b'debuggetbundle',
1471 [
1474 [
1472 (b'H', b'head', [], _(b'id of head node'), _(b'ID')),
1475 (b'H', b'head', [], _(b'id of head node'), _(b'ID')),
1473 (b'C', b'common', [], _(b'id of common node'), _(b'ID')),
1476 (b'C', b'common', [], _(b'id of common node'), _(b'ID')),
1474 (
1477 (
1475 b't',
1478 b't',
1476 b'type',
1479 b'type',
1477 b'bzip2',
1480 b'bzip2',
1478 _(b'bundle compression type to use'),
1481 _(b'bundle compression type to use'),
1479 _(b'TYPE'),
1482 _(b'TYPE'),
1480 ),
1483 ),
1481 ],
1484 ],
1482 _(b'REPO FILE [-H|-C ID]...'),
1485 _(b'REPO FILE [-H|-C ID]...'),
1483 norepo=True,
1486 norepo=True,
1484 )
1487 )
1485 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1488 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1486 """retrieves a bundle from a repo
1489 """retrieves a bundle from a repo
1487
1490
1488 Every ID must be a full-length hex node id string. Saves the bundle to the
1491 Every ID must be a full-length hex node id string. Saves the bundle to the
1489 given file.
1492 given file.
1490 """
1493 """
1491 opts = pycompat.byteskwargs(opts)
1494 opts = pycompat.byteskwargs(opts)
1492 repo = hg.peer(ui, opts, repopath)
1495 repo = hg.peer(ui, opts, repopath)
1493 if not repo.capable(b'getbundle'):
1496 if not repo.capable(b'getbundle'):
1494 raise error.Abort(b"getbundle() not supported by target repository")
1497 raise error.Abort(b"getbundle() not supported by target repository")
1495 args = {}
1498 args = {}
1496 if common:
1499 if common:
1497 args['common'] = [bin(s) for s in common]
1500 args['common'] = [bin(s) for s in common]
1498 if head:
1501 if head:
1499 args['heads'] = [bin(s) for s in head]
1502 args['heads'] = [bin(s) for s in head]
1500 # TODO: get desired bundlecaps from command line.
1503 # TODO: get desired bundlecaps from command line.
1501 args['bundlecaps'] = None
1504 args['bundlecaps'] = None
1502 bundle = repo.getbundle(b'debug', **args)
1505 bundle = repo.getbundle(b'debug', **args)
1503
1506
1504 bundletype = opts.get(b'type', b'bzip2').lower()
1507 bundletype = opts.get(b'type', b'bzip2').lower()
1505 btypes = {
1508 btypes = {
1506 b'none': b'HG10UN',
1509 b'none': b'HG10UN',
1507 b'bzip2': b'HG10BZ',
1510 b'bzip2': b'HG10BZ',
1508 b'gzip': b'HG10GZ',
1511 b'gzip': b'HG10GZ',
1509 b'bundle2': b'HG20',
1512 b'bundle2': b'HG20',
1510 }
1513 }
1511 bundletype = btypes.get(bundletype)
1514 bundletype = btypes.get(bundletype)
1512 if bundletype not in bundle2.bundletypes:
1515 if bundletype not in bundle2.bundletypes:
1513 raise error.Abort(_(b'unknown bundle type specified with --type'))
1516 raise error.Abort(_(b'unknown bundle type specified with --type'))
1514 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1517 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1515
1518
1516
1519
1517 @command(b'debugignore', [], b'[FILE]')
1520 @command(b'debugignore', [], b'[FILE]')
1518 def debugignore(ui, repo, *files, **opts):
1521 def debugignore(ui, repo, *files, **opts):
1519 """display the combined ignore pattern and information about ignored files
1522 """display the combined ignore pattern and information about ignored files
1520
1523
1521 With no argument display the combined ignore pattern.
1524 With no argument display the combined ignore pattern.
1522
1525
1523 Given space separated file names, shows if the given file is ignored and
1526 Given space separated file names, shows if the given file is ignored and
1524 if so, show the ignore rule (file and line number) that matched it.
1527 if so, show the ignore rule (file and line number) that matched it.
1525 """
1528 """
1526 ignore = repo.dirstate._ignore
1529 ignore = repo.dirstate._ignore
1527 if not files:
1530 if not files:
1528 # Show all the patterns
1531 # Show all the patterns
1529 ui.write(b"%s\n" % pycompat.byterepr(ignore))
1532 ui.write(b"%s\n" % pycompat.byterepr(ignore))
1530 else:
1533 else:
1531 m = scmutil.match(repo[None], pats=files)
1534 m = scmutil.match(repo[None], pats=files)
1532 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1535 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1533 for f in m.files():
1536 for f in m.files():
1534 nf = util.normpath(f)
1537 nf = util.normpath(f)
1535 ignored = None
1538 ignored = None
1536 ignoredata = None
1539 ignoredata = None
1537 if nf != b'.':
1540 if nf != b'.':
1538 if ignore(nf):
1541 if ignore(nf):
1539 ignored = nf
1542 ignored = nf
1540 ignoredata = repo.dirstate._ignorefileandline(nf)
1543 ignoredata = repo.dirstate._ignorefileandline(nf)
1541 else:
1544 else:
1542 for p in pathutil.finddirs(nf):
1545 for p in pathutil.finddirs(nf):
1543 if ignore(p):
1546 if ignore(p):
1544 ignored = p
1547 ignored = p
1545 ignoredata = repo.dirstate._ignorefileandline(p)
1548 ignoredata = repo.dirstate._ignorefileandline(p)
1546 break
1549 break
1547 if ignored:
1550 if ignored:
1548 if ignored == nf:
1551 if ignored == nf:
1549 ui.write(_(b"%s is ignored\n") % uipathfn(f))
1552 ui.write(_(b"%s is ignored\n") % uipathfn(f))
1550 else:
1553 else:
1551 ui.write(
1554 ui.write(
1552 _(
1555 _(
1553 b"%s is ignored because of "
1556 b"%s is ignored because of "
1554 b"containing directory %s\n"
1557 b"containing directory %s\n"
1555 )
1558 )
1556 % (uipathfn(f), ignored)
1559 % (uipathfn(f), ignored)
1557 )
1560 )
1558 ignorefile, lineno, line = ignoredata
1561 ignorefile, lineno, line = ignoredata
1559 ui.write(
1562 ui.write(
1560 _(b"(ignore rule in %s, line %d: '%s')\n")
1563 _(b"(ignore rule in %s, line %d: '%s')\n")
1561 % (ignorefile, lineno, line)
1564 % (ignorefile, lineno, line)
1562 )
1565 )
1563 else:
1566 else:
1564 ui.write(_(b"%s is not ignored\n") % uipathfn(f))
1567 ui.write(_(b"%s is not ignored\n") % uipathfn(f))
1565
1568
1566
1569
1567 @command(
1570 @command(
1568 b'debugindex',
1571 b'debugindex',
1569 cmdutil.debugrevlogopts + cmdutil.formatteropts,
1572 cmdutil.debugrevlogopts + cmdutil.formatteropts,
1570 _(b'-c|-m|FILE'),
1573 _(b'-c|-m|FILE'),
1571 )
1574 )
1572 def debugindex(ui, repo, file_=None, **opts):
1575 def debugindex(ui, repo, file_=None, **opts):
1573 """dump index data for a storage primitive"""
1576 """dump index data for a storage primitive"""
1574 opts = pycompat.byteskwargs(opts)
1577 opts = pycompat.byteskwargs(opts)
1575 store = cmdutil.openstorage(repo, b'debugindex', file_, opts)
1578 store = cmdutil.openstorage(repo, b'debugindex', file_, opts)
1576
1579
1577 if ui.debugflag:
1580 if ui.debugflag:
1578 shortfn = hex
1581 shortfn = hex
1579 else:
1582 else:
1580 shortfn = short
1583 shortfn = short
1581
1584
1582 idlen = 12
1585 idlen = 12
1583 for i in store:
1586 for i in store:
1584 idlen = len(shortfn(store.node(i)))
1587 idlen = len(shortfn(store.node(i)))
1585 break
1588 break
1586
1589
1587 fm = ui.formatter(b'debugindex', opts)
1590 fm = ui.formatter(b'debugindex', opts)
1588 fm.plain(
1591 fm.plain(
1589 b' rev linkrev %s %s p2\n'
1592 b' rev linkrev %s %s p2\n'
1590 % (b'nodeid'.ljust(idlen), b'p1'.ljust(idlen))
1593 % (b'nodeid'.ljust(idlen), b'p1'.ljust(idlen))
1591 )
1594 )
1592
1595
1593 for rev in store:
1596 for rev in store:
1594 node = store.node(rev)
1597 node = store.node(rev)
1595 parents = store.parents(node)
1598 parents = store.parents(node)
1596
1599
1597 fm.startitem()
1600 fm.startitem()
1598 fm.write(b'rev', b'%6d ', rev)
1601 fm.write(b'rev', b'%6d ', rev)
1599 fm.write(b'linkrev', b'%7d ', store.linkrev(rev))
1602 fm.write(b'linkrev', b'%7d ', store.linkrev(rev))
1600 fm.write(b'node', b'%s ', shortfn(node))
1603 fm.write(b'node', b'%s ', shortfn(node))
1601 fm.write(b'p1', b'%s ', shortfn(parents[0]))
1604 fm.write(b'p1', b'%s ', shortfn(parents[0]))
1602 fm.write(b'p2', b'%s', shortfn(parents[1]))
1605 fm.write(b'p2', b'%s', shortfn(parents[1]))
1603 fm.plain(b'\n')
1606 fm.plain(b'\n')
1604
1607
1605 fm.end()
1608 fm.end()
1606
1609
1607
1610
1608 @command(
1611 @command(
1609 b'debugindexdot',
1612 b'debugindexdot',
1610 cmdutil.debugrevlogopts,
1613 cmdutil.debugrevlogopts,
1611 _(b'-c|-m|FILE'),
1614 _(b'-c|-m|FILE'),
1612 optionalrepo=True,
1615 optionalrepo=True,
1613 )
1616 )
1614 def debugindexdot(ui, repo, file_=None, **opts):
1617 def debugindexdot(ui, repo, file_=None, **opts):
1615 """dump an index DAG as a graphviz dot file"""
1618 """dump an index DAG as a graphviz dot file"""
1616 opts = pycompat.byteskwargs(opts)
1619 opts = pycompat.byteskwargs(opts)
1617 r = cmdutil.openstorage(repo, b'debugindexdot', file_, opts)
1620 r = cmdutil.openstorage(repo, b'debugindexdot', file_, opts)
1618 ui.writenoi18n(b"digraph G {\n")
1621 ui.writenoi18n(b"digraph G {\n")
1619 for i in r:
1622 for i in r:
1620 node = r.node(i)
1623 node = r.node(i)
1621 pp = r.parents(node)
1624 pp = r.parents(node)
1622 ui.write(b"\t%d -> %d\n" % (r.rev(pp[0]), i))
1625 ui.write(b"\t%d -> %d\n" % (r.rev(pp[0]), i))
1623 if pp[1] != nullid:
1626 if pp[1] != nullid:
1624 ui.write(b"\t%d -> %d\n" % (r.rev(pp[1]), i))
1627 ui.write(b"\t%d -> %d\n" % (r.rev(pp[1]), i))
1625 ui.write(b"}\n")
1628 ui.write(b"}\n")
1626
1629
1627
1630
1628 @command(b'debugindexstats', [])
1631 @command(b'debugindexstats', [])
1629 def debugindexstats(ui, repo):
1632 def debugindexstats(ui, repo):
1630 """show stats related to the changelog index"""
1633 """show stats related to the changelog index"""
1631 repo.changelog.shortest(nullid, 1)
1634 repo.changelog.shortest(nullid, 1)
1632 index = repo.changelog.index
1635 index = repo.changelog.index
1633 if not util.safehasattr(index, b'stats'):
1636 if not util.safehasattr(index, b'stats'):
1634 raise error.Abort(_(b'debugindexstats only works with native code'))
1637 raise error.Abort(_(b'debugindexstats only works with native code'))
1635 for k, v in sorted(index.stats().items()):
1638 for k, v in sorted(index.stats().items()):
1636 ui.write(b'%s: %d\n' % (k, v))
1639 ui.write(b'%s: %d\n' % (k, v))
1637
1640
1638
1641
1639 @command(b'debuginstall', [] + cmdutil.formatteropts, b'', norepo=True)
1642 @command(b'debuginstall', [] + cmdutil.formatteropts, b'', norepo=True)
1640 def debuginstall(ui, **opts):
1643 def debuginstall(ui, **opts):
1641 """test Mercurial installation
1644 """test Mercurial installation
1642
1645
1643 Returns 0 on success.
1646 Returns 0 on success.
1644 """
1647 """
1645 opts = pycompat.byteskwargs(opts)
1648 opts = pycompat.byteskwargs(opts)
1646
1649
1647 problems = 0
1650 problems = 0
1648
1651
1649 fm = ui.formatter(b'debuginstall', opts)
1652 fm = ui.formatter(b'debuginstall', opts)
1650 fm.startitem()
1653 fm.startitem()
1651
1654
1652 # encoding might be unknown or wrong. don't translate these messages.
1655 # encoding might be unknown or wrong. don't translate these messages.
1653 fm.write(b'encoding', b"checking encoding (%s)...\n", encoding.encoding)
1656 fm.write(b'encoding', b"checking encoding (%s)...\n", encoding.encoding)
1654 err = None
1657 err = None
1655 try:
1658 try:
1656 codecs.lookup(pycompat.sysstr(encoding.encoding))
1659 codecs.lookup(pycompat.sysstr(encoding.encoding))
1657 except LookupError as inst:
1660 except LookupError as inst:
1658 err = stringutil.forcebytestr(inst)
1661 err = stringutil.forcebytestr(inst)
1659 problems += 1
1662 problems += 1
1660 fm.condwrite(
1663 fm.condwrite(
1661 err,
1664 err,
1662 b'encodingerror',
1665 b'encodingerror',
1663 b" %s\n (check that your locale is properly set)\n",
1666 b" %s\n (check that your locale is properly set)\n",
1664 err,
1667 err,
1665 )
1668 )
1666
1669
1667 # Python
1670 # Python
1668 pythonlib = None
1671 pythonlib = None
1669 if util.safehasattr(os, '__file__'):
1672 if util.safehasattr(os, '__file__'):
1670 pythonlib = os.path.dirname(pycompat.fsencode(os.__file__))
1673 pythonlib = os.path.dirname(pycompat.fsencode(os.__file__))
1671 elif getattr(sys, 'oxidized', False):
1674 elif getattr(sys, 'oxidized', False):
1672 pythonlib = pycompat.sysexecutable
1675 pythonlib = pycompat.sysexecutable
1673
1676
1674 fm.write(
1677 fm.write(
1675 b'pythonexe',
1678 b'pythonexe',
1676 _(b"checking Python executable (%s)\n"),
1679 _(b"checking Python executable (%s)\n"),
1677 pycompat.sysexecutable or _(b"unknown"),
1680 pycompat.sysexecutable or _(b"unknown"),
1678 )
1681 )
1679 fm.write(
1682 fm.write(
1680 b'pythonimplementation',
1683 b'pythonimplementation',
1681 _(b"checking Python implementation (%s)\n"),
1684 _(b"checking Python implementation (%s)\n"),
1682 pycompat.sysbytes(platform.python_implementation()),
1685 pycompat.sysbytes(platform.python_implementation()),
1683 )
1686 )
1684 fm.write(
1687 fm.write(
1685 b'pythonver',
1688 b'pythonver',
1686 _(b"checking Python version (%s)\n"),
1689 _(b"checking Python version (%s)\n"),
1687 (b"%d.%d.%d" % sys.version_info[:3]),
1690 (b"%d.%d.%d" % sys.version_info[:3]),
1688 )
1691 )
1689 fm.write(
1692 fm.write(
1690 b'pythonlib',
1693 b'pythonlib',
1691 _(b"checking Python lib (%s)...\n"),
1694 _(b"checking Python lib (%s)...\n"),
1692 pythonlib or _(b"unknown"),
1695 pythonlib or _(b"unknown"),
1693 )
1696 )
1694
1697
1695 try:
1698 try:
1696 from . import rustext
1699 from . import rustext
1697
1700
1698 rustext.__doc__ # trigger lazy import
1701 rustext.__doc__ # trigger lazy import
1699 except ImportError:
1702 except ImportError:
1700 rustext = None
1703 rustext = None
1701
1704
1702 security = set(sslutil.supportedprotocols)
1705 security = set(sslutil.supportedprotocols)
1703 if sslutil.hassni:
1706 if sslutil.hassni:
1704 security.add(b'sni')
1707 security.add(b'sni')
1705
1708
1706 fm.write(
1709 fm.write(
1707 b'pythonsecurity',
1710 b'pythonsecurity',
1708 _(b"checking Python security support (%s)\n"),
1711 _(b"checking Python security support (%s)\n"),
1709 fm.formatlist(sorted(security), name=b'protocol', fmt=b'%s', sep=b','),
1712 fm.formatlist(sorted(security), name=b'protocol', fmt=b'%s', sep=b','),
1710 )
1713 )
1711
1714
1712 # These are warnings, not errors. So don't increment problem count. This
1715 # These are warnings, not errors. So don't increment problem count. This
1713 # may change in the future.
1716 # may change in the future.
1714 if b'tls1.2' not in security:
1717 if b'tls1.2' not in security:
1715 fm.plain(
1718 fm.plain(
1716 _(
1719 _(
1717 b' TLS 1.2 not supported by Python install; '
1720 b' TLS 1.2 not supported by Python install; '
1718 b'network connections lack modern security\n'
1721 b'network connections lack modern security\n'
1719 )
1722 )
1720 )
1723 )
1721 if b'sni' not in security:
1724 if b'sni' not in security:
1722 fm.plain(
1725 fm.plain(
1723 _(
1726 _(
1724 b' SNI not supported by Python install; may have '
1727 b' SNI not supported by Python install; may have '
1725 b'connectivity issues with some servers\n'
1728 b'connectivity issues with some servers\n'
1726 )
1729 )
1727 )
1730 )
1728
1731
1729 fm.plain(
1732 fm.plain(
1730 _(
1733 _(
1731 b"checking Rust extensions (%s)\n"
1734 b"checking Rust extensions (%s)\n"
1732 % (b'missing' if rustext is None else b'installed')
1735 % (b'missing' if rustext is None else b'installed')
1733 ),
1736 ),
1734 )
1737 )
1735
1738
1736 # TODO print CA cert info
1739 # TODO print CA cert info
1737
1740
1738 # hg version
1741 # hg version
1739 hgver = util.version()
1742 hgver = util.version()
1740 fm.write(
1743 fm.write(
1741 b'hgver', _(b"checking Mercurial version (%s)\n"), hgver.split(b'+')[0]
1744 b'hgver', _(b"checking Mercurial version (%s)\n"), hgver.split(b'+')[0]
1742 )
1745 )
1743 fm.write(
1746 fm.write(
1744 b'hgverextra',
1747 b'hgverextra',
1745 _(b"checking Mercurial custom build (%s)\n"),
1748 _(b"checking Mercurial custom build (%s)\n"),
1746 b'+'.join(hgver.split(b'+')[1:]),
1749 b'+'.join(hgver.split(b'+')[1:]),
1747 )
1750 )
1748
1751
1749 # compiled modules
1752 # compiled modules
1750 hgmodules = None
1753 hgmodules = None
1751 if util.safehasattr(sys.modules[__name__], '__file__'):
1754 if util.safehasattr(sys.modules[__name__], '__file__'):
1752 hgmodules = os.path.dirname(pycompat.fsencode(__file__))
1755 hgmodules = os.path.dirname(pycompat.fsencode(__file__))
1753 elif getattr(sys, 'oxidized', False):
1756 elif getattr(sys, 'oxidized', False):
1754 hgmodules = pycompat.sysexecutable
1757 hgmodules = pycompat.sysexecutable
1755
1758
1756 fm.write(
1759 fm.write(
1757 b'hgmodulepolicy', _(b"checking module policy (%s)\n"), policy.policy
1760 b'hgmodulepolicy', _(b"checking module policy (%s)\n"), policy.policy
1758 )
1761 )
1759 fm.write(
1762 fm.write(
1760 b'hgmodules',
1763 b'hgmodules',
1761 _(b"checking installed modules (%s)...\n"),
1764 _(b"checking installed modules (%s)...\n"),
1762 hgmodules or _(b"unknown"),
1765 hgmodules or _(b"unknown"),
1763 )
1766 )
1764
1767
1765 rustandc = policy.policy in (b'rust+c', b'rust+c-allow')
1768 rustandc = policy.policy in (b'rust+c', b'rust+c-allow')
1766 rustext = rustandc # for now, that's the only case
1769 rustext = rustandc # for now, that's the only case
1767 cext = policy.policy in (b'c', b'allow') or rustandc
1770 cext = policy.policy in (b'c', b'allow') or rustandc
1768 nopure = cext or rustext
1771 nopure = cext or rustext
1769 if nopure:
1772 if nopure:
1770 err = None
1773 err = None
1771 try:
1774 try:
1772 if cext:
1775 if cext:
1773 from .cext import ( # pytype: disable=import-error
1776 from .cext import ( # pytype: disable=import-error
1774 base85,
1777 base85,
1775 bdiff,
1778 bdiff,
1776 mpatch,
1779 mpatch,
1777 osutil,
1780 osutil,
1778 )
1781 )
1779
1782
1780 # quiet pyflakes
1783 # quiet pyflakes
1781 dir(bdiff), dir(mpatch), dir(base85), dir(osutil)
1784 dir(bdiff), dir(mpatch), dir(base85), dir(osutil)
1782 if rustext:
1785 if rustext:
1783 from .rustext import ( # pytype: disable=import-error
1786 from .rustext import ( # pytype: disable=import-error
1784 ancestor,
1787 ancestor,
1785 dirstate,
1788 dirstate,
1786 )
1789 )
1787
1790
1788 dir(ancestor), dir(dirstate) # quiet pyflakes
1791 dir(ancestor), dir(dirstate) # quiet pyflakes
1789 except Exception as inst:
1792 except Exception as inst:
1790 err = stringutil.forcebytestr(inst)
1793 err = stringutil.forcebytestr(inst)
1791 problems += 1
1794 problems += 1
1792 fm.condwrite(err, b'extensionserror', b" %s\n", err)
1795 fm.condwrite(err, b'extensionserror', b" %s\n", err)
1793
1796
1794 compengines = util.compengines._engines.values()
1797 compengines = util.compengines._engines.values()
1795 fm.write(
1798 fm.write(
1796 b'compengines',
1799 b'compengines',
1797 _(b'checking registered compression engines (%s)\n'),
1800 _(b'checking registered compression engines (%s)\n'),
1798 fm.formatlist(
1801 fm.formatlist(
1799 sorted(e.name() for e in compengines),
1802 sorted(e.name() for e in compengines),
1800 name=b'compengine',
1803 name=b'compengine',
1801 fmt=b'%s',
1804 fmt=b'%s',
1802 sep=b', ',
1805 sep=b', ',
1803 ),
1806 ),
1804 )
1807 )
1805 fm.write(
1808 fm.write(
1806 b'compenginesavail',
1809 b'compenginesavail',
1807 _(b'checking available compression engines (%s)\n'),
1810 _(b'checking available compression engines (%s)\n'),
1808 fm.formatlist(
1811 fm.formatlist(
1809 sorted(e.name() for e in compengines if e.available()),
1812 sorted(e.name() for e in compengines if e.available()),
1810 name=b'compengine',
1813 name=b'compengine',
1811 fmt=b'%s',
1814 fmt=b'%s',
1812 sep=b', ',
1815 sep=b', ',
1813 ),
1816 ),
1814 )
1817 )
1815 wirecompengines = compression.compengines.supportedwireengines(
1818 wirecompengines = compression.compengines.supportedwireengines(
1816 compression.SERVERROLE
1819 compression.SERVERROLE
1817 )
1820 )
1818 fm.write(
1821 fm.write(
1819 b'compenginesserver',
1822 b'compenginesserver',
1820 _(
1823 _(
1821 b'checking available compression engines '
1824 b'checking available compression engines '
1822 b'for wire protocol (%s)\n'
1825 b'for wire protocol (%s)\n'
1823 ),
1826 ),
1824 fm.formatlist(
1827 fm.formatlist(
1825 [e.name() for e in wirecompengines if e.wireprotosupport()],
1828 [e.name() for e in wirecompengines if e.wireprotosupport()],
1826 name=b'compengine',
1829 name=b'compengine',
1827 fmt=b'%s',
1830 fmt=b'%s',
1828 sep=b', ',
1831 sep=b', ',
1829 ),
1832 ),
1830 )
1833 )
1831 re2 = b'missing'
1834 re2 = b'missing'
1832 if util._re2:
1835 if util._re2:
1833 re2 = b'available'
1836 re2 = b'available'
1834 fm.plain(_(b'checking "re2" regexp engine (%s)\n') % re2)
1837 fm.plain(_(b'checking "re2" regexp engine (%s)\n') % re2)
1835 fm.data(re2=bool(util._re2))
1838 fm.data(re2=bool(util._re2))
1836
1839
1837 # templates
1840 # templates
1838 p = templater.templatedir()
1841 p = templater.templatedir()
1839 fm.write(b'templatedirs', b'checking templates (%s)...\n', p or b'')
1842 fm.write(b'templatedirs', b'checking templates (%s)...\n', p or b'')
1840 fm.condwrite(not p, b'', _(b" no template directories found\n"))
1843 fm.condwrite(not p, b'', _(b" no template directories found\n"))
1841 if p:
1844 if p:
1842 (m, fp) = templater.try_open_template(b"map-cmdline.default")
1845 (m, fp) = templater.try_open_template(b"map-cmdline.default")
1843 if m:
1846 if m:
1844 # template found, check if it is working
1847 # template found, check if it is working
1845 err = None
1848 err = None
1846 try:
1849 try:
1847 templater.templater.frommapfile(m)
1850 templater.templater.frommapfile(m)
1848 except Exception as inst:
1851 except Exception as inst:
1849 err = stringutil.forcebytestr(inst)
1852 err = stringutil.forcebytestr(inst)
1850 p = None
1853 p = None
1851 fm.condwrite(err, b'defaulttemplateerror', b" %s\n", err)
1854 fm.condwrite(err, b'defaulttemplateerror', b" %s\n", err)
1852 else:
1855 else:
1853 p = None
1856 p = None
1854 fm.condwrite(
1857 fm.condwrite(
1855 p, b'defaulttemplate', _(b"checking default template (%s)\n"), m
1858 p, b'defaulttemplate', _(b"checking default template (%s)\n"), m
1856 )
1859 )
1857 fm.condwrite(
1860 fm.condwrite(
1858 not m,
1861 not m,
1859 b'defaulttemplatenotfound',
1862 b'defaulttemplatenotfound',
1860 _(b" template '%s' not found\n"),
1863 _(b" template '%s' not found\n"),
1861 b"default",
1864 b"default",
1862 )
1865 )
1863 if not p:
1866 if not p:
1864 problems += 1
1867 problems += 1
1865 fm.condwrite(
1868 fm.condwrite(
1866 not p, b'', _(b" (templates seem to have been installed incorrectly)\n")
1869 not p, b'', _(b" (templates seem to have been installed incorrectly)\n")
1867 )
1870 )
1868
1871
1869 # editor
1872 # editor
1870 editor = ui.geteditor()
1873 editor = ui.geteditor()
1871 editor = util.expandpath(editor)
1874 editor = util.expandpath(editor)
1872 editorbin = procutil.shellsplit(editor)[0]
1875 editorbin = procutil.shellsplit(editor)[0]
1873 fm.write(b'editor', _(b"checking commit editor... (%s)\n"), editorbin)
1876 fm.write(b'editor', _(b"checking commit editor... (%s)\n"), editorbin)
1874 cmdpath = procutil.findexe(editorbin)
1877 cmdpath = procutil.findexe(editorbin)
1875 fm.condwrite(
1878 fm.condwrite(
1876 not cmdpath and editor == b'vi',
1879 not cmdpath and editor == b'vi',
1877 b'vinotfound',
1880 b'vinotfound',
1878 _(
1881 _(
1879 b" No commit editor set and can't find %s in PATH\n"
1882 b" No commit editor set and can't find %s in PATH\n"
1880 b" (specify a commit editor in your configuration"
1883 b" (specify a commit editor in your configuration"
1881 b" file)\n"
1884 b" file)\n"
1882 ),
1885 ),
1883 not cmdpath and editor == b'vi' and editorbin,
1886 not cmdpath and editor == b'vi' and editorbin,
1884 )
1887 )
1885 fm.condwrite(
1888 fm.condwrite(
1886 not cmdpath and editor != b'vi',
1889 not cmdpath and editor != b'vi',
1887 b'editornotfound',
1890 b'editornotfound',
1888 _(
1891 _(
1889 b" Can't find editor '%s' in PATH\n"
1892 b" Can't find editor '%s' in PATH\n"
1890 b" (specify a commit editor in your configuration"
1893 b" (specify a commit editor in your configuration"
1891 b" file)\n"
1894 b" file)\n"
1892 ),
1895 ),
1893 not cmdpath and editorbin,
1896 not cmdpath and editorbin,
1894 )
1897 )
1895 if not cmdpath and editor != b'vi':
1898 if not cmdpath and editor != b'vi':
1896 problems += 1
1899 problems += 1
1897
1900
1898 # check username
1901 # check username
1899 username = None
1902 username = None
1900 err = None
1903 err = None
1901 try:
1904 try:
1902 username = ui.username()
1905 username = ui.username()
1903 except error.Abort as e:
1906 except error.Abort as e:
1904 err = e.message
1907 err = e.message
1905 problems += 1
1908 problems += 1
1906
1909
1907 fm.condwrite(
1910 fm.condwrite(
1908 username, b'username', _(b"checking username (%s)\n"), username
1911 username, b'username', _(b"checking username (%s)\n"), username
1909 )
1912 )
1910 fm.condwrite(
1913 fm.condwrite(
1911 err,
1914 err,
1912 b'usernameerror',
1915 b'usernameerror',
1913 _(
1916 _(
1914 b"checking username...\n %s\n"
1917 b"checking username...\n %s\n"
1915 b" (specify a username in your configuration file)\n"
1918 b" (specify a username in your configuration file)\n"
1916 ),
1919 ),
1917 err,
1920 err,
1918 )
1921 )
1919
1922
1920 for name, mod in extensions.extensions():
1923 for name, mod in extensions.extensions():
1921 handler = getattr(mod, 'debuginstall', None)
1924 handler = getattr(mod, 'debuginstall', None)
1922 if handler is not None:
1925 if handler is not None:
1923 problems += handler(ui, fm)
1926 problems += handler(ui, fm)
1924
1927
1925 fm.condwrite(not problems, b'', _(b"no problems detected\n"))
1928 fm.condwrite(not problems, b'', _(b"no problems detected\n"))
1926 if not problems:
1929 if not problems:
1927 fm.data(problems=problems)
1930 fm.data(problems=problems)
1928 fm.condwrite(
1931 fm.condwrite(
1929 problems,
1932 problems,
1930 b'problems',
1933 b'problems',
1931 _(b"%d problems detected, please check your install!\n"),
1934 _(b"%d problems detected, please check your install!\n"),
1932 problems,
1935 problems,
1933 )
1936 )
1934 fm.end()
1937 fm.end()
1935
1938
1936 return problems
1939 return problems
1937
1940
1938
1941
1939 @command(b'debugknown', [], _(b'REPO ID...'), norepo=True)
1942 @command(b'debugknown', [], _(b'REPO ID...'), norepo=True)
1940 def debugknown(ui, repopath, *ids, **opts):
1943 def debugknown(ui, repopath, *ids, **opts):
1941 """test whether node ids are known to a repo
1944 """test whether node ids are known to a repo
1942
1945
1943 Every ID must be a full-length hex node id string. Returns a list of 0s
1946 Every ID must be a full-length hex node id string. Returns a list of 0s
1944 and 1s indicating unknown/known.
1947 and 1s indicating unknown/known.
1945 """
1948 """
1946 opts = pycompat.byteskwargs(opts)
1949 opts = pycompat.byteskwargs(opts)
1947 repo = hg.peer(ui, opts, repopath)
1950 repo = hg.peer(ui, opts, repopath)
1948 if not repo.capable(b'known'):
1951 if not repo.capable(b'known'):
1949 raise error.Abort(b"known() not supported by target repository")
1952 raise error.Abort(b"known() not supported by target repository")
1950 flags = repo.known([bin(s) for s in ids])
1953 flags = repo.known([bin(s) for s in ids])
1951 ui.write(b"%s\n" % (b"".join([f and b"1" or b"0" for f in flags])))
1954 ui.write(b"%s\n" % (b"".join([f and b"1" or b"0" for f in flags])))
1952
1955
1953
1956
1954 @command(b'debuglabelcomplete', [], _(b'LABEL...'))
1957 @command(b'debuglabelcomplete', [], _(b'LABEL...'))
1955 def debuglabelcomplete(ui, repo, *args):
1958 def debuglabelcomplete(ui, repo, *args):
1956 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1959 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1957 debugnamecomplete(ui, repo, *args)
1960 debugnamecomplete(ui, repo, *args)
1958
1961
1959
1962
1960 @command(
1963 @command(
1961 b'debuglocks',
1964 b'debuglocks',
1962 [
1965 [
1963 (b'L', b'force-free-lock', None, _(b'free the store lock (DANGEROUS)')),
1966 (b'L', b'force-free-lock', None, _(b'free the store lock (DANGEROUS)')),
1964 (
1967 (
1965 b'W',
1968 b'W',
1966 b'force-free-wlock',
1969 b'force-free-wlock',
1967 None,
1970 None,
1968 _(b'free the working state lock (DANGEROUS)'),
1971 _(b'free the working state lock (DANGEROUS)'),
1969 ),
1972 ),
1970 (b's', b'set-lock', None, _(b'set the store lock until stopped')),
1973 (b's', b'set-lock', None, _(b'set the store lock until stopped')),
1971 (
1974 (
1972 b'S',
1975 b'S',
1973 b'set-wlock',
1976 b'set-wlock',
1974 None,
1977 None,
1975 _(b'set the working state lock until stopped'),
1978 _(b'set the working state lock until stopped'),
1976 ),
1979 ),
1977 ],
1980 ],
1978 _(b'[OPTION]...'),
1981 _(b'[OPTION]...'),
1979 )
1982 )
1980 def debuglocks(ui, repo, **opts):
1983 def debuglocks(ui, repo, **opts):
1981 """show or modify state of locks
1984 """show or modify state of locks
1982
1985
1983 By default, this command will show which locks are held. This
1986 By default, this command will show which locks are held. This
1984 includes the user and process holding the lock, the amount of time
1987 includes the user and process holding the lock, the amount of time
1985 the lock has been held, and the machine name where the process is
1988 the lock has been held, and the machine name where the process is
1986 running if it's not local.
1989 running if it's not local.
1987
1990
1988 Locks protect the integrity of Mercurial's data, so should be
1991 Locks protect the integrity of Mercurial's data, so should be
1989 treated with care. System crashes or other interruptions may cause
1992 treated with care. System crashes or other interruptions may cause
1990 locks to not be properly released, though Mercurial will usually
1993 locks to not be properly released, though Mercurial will usually
1991 detect and remove such stale locks automatically.
1994 detect and remove such stale locks automatically.
1992
1995
1993 However, detecting stale locks may not always be possible (for
1996 However, detecting stale locks may not always be possible (for
1994 instance, on a shared filesystem). Removing locks may also be
1997 instance, on a shared filesystem). Removing locks may also be
1995 blocked by filesystem permissions.
1998 blocked by filesystem permissions.
1996
1999
1997 Setting a lock will prevent other commands from changing the data.
2000 Setting a lock will prevent other commands from changing the data.
1998 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
2001 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1999 The set locks are removed when the command exits.
2002 The set locks are removed when the command exits.
2000
2003
2001 Returns 0 if no locks are held.
2004 Returns 0 if no locks are held.
2002
2005
2003 """
2006 """
2004
2007
2005 if opts.get('force_free_lock'):
2008 if opts.get('force_free_lock'):
2006 repo.svfs.unlink(b'lock')
2009 repo.svfs.unlink(b'lock')
2007 if opts.get('force_free_wlock'):
2010 if opts.get('force_free_wlock'):
2008 repo.vfs.unlink(b'wlock')
2011 repo.vfs.unlink(b'wlock')
2009 if opts.get('force_free_lock') or opts.get('force_free_wlock'):
2012 if opts.get('force_free_lock') or opts.get('force_free_wlock'):
2010 return 0
2013 return 0
2011
2014
2012 locks = []
2015 locks = []
2013 try:
2016 try:
2014 if opts.get('set_wlock'):
2017 if opts.get('set_wlock'):
2015 try:
2018 try:
2016 locks.append(repo.wlock(False))
2019 locks.append(repo.wlock(False))
2017 except error.LockHeld:
2020 except error.LockHeld:
2018 raise error.Abort(_(b'wlock is already held'))
2021 raise error.Abort(_(b'wlock is already held'))
2019 if opts.get('set_lock'):
2022 if opts.get('set_lock'):
2020 try:
2023 try:
2021 locks.append(repo.lock(False))
2024 locks.append(repo.lock(False))
2022 except error.LockHeld:
2025 except error.LockHeld:
2023 raise error.Abort(_(b'lock is already held'))
2026 raise error.Abort(_(b'lock is already held'))
2024 if len(locks):
2027 if len(locks):
2025 ui.promptchoice(_(b"ready to release the lock (y)? $$ &Yes"))
2028 ui.promptchoice(_(b"ready to release the lock (y)? $$ &Yes"))
2026 return 0
2029 return 0
2027 finally:
2030 finally:
2028 release(*locks)
2031 release(*locks)
2029
2032
2030 now = time.time()
2033 now = time.time()
2031 held = 0
2034 held = 0
2032
2035
2033 def report(vfs, name, method):
2036 def report(vfs, name, method):
2034 # this causes stale locks to get reaped for more accurate reporting
2037 # this causes stale locks to get reaped for more accurate reporting
2035 try:
2038 try:
2036 l = method(False)
2039 l = method(False)
2037 except error.LockHeld:
2040 except error.LockHeld:
2038 l = None
2041 l = None
2039
2042
2040 if l:
2043 if l:
2041 l.release()
2044 l.release()
2042 else:
2045 else:
2043 try:
2046 try:
2044 st = vfs.lstat(name)
2047 st = vfs.lstat(name)
2045 age = now - st[stat.ST_MTIME]
2048 age = now - st[stat.ST_MTIME]
2046 user = util.username(st.st_uid)
2049 user = util.username(st.st_uid)
2047 locker = vfs.readlock(name)
2050 locker = vfs.readlock(name)
2048 if b":" in locker:
2051 if b":" in locker:
2049 host, pid = locker.split(b':')
2052 host, pid = locker.split(b':')
2050 if host == socket.gethostname():
2053 if host == socket.gethostname():
2051 locker = b'user %s, process %s' % (user or b'None', pid)
2054 locker = b'user %s, process %s' % (user or b'None', pid)
2052 else:
2055 else:
2053 locker = b'user %s, process %s, host %s' % (
2056 locker = b'user %s, process %s, host %s' % (
2054 user or b'None',
2057 user or b'None',
2055 pid,
2058 pid,
2056 host,
2059 host,
2057 )
2060 )
2058 ui.writenoi18n(b"%-6s %s (%ds)\n" % (name + b":", locker, age))
2061 ui.writenoi18n(b"%-6s %s (%ds)\n" % (name + b":", locker, age))
2059 return 1
2062 return 1
2060 except OSError as e:
2063 except OSError as e:
2061 if e.errno != errno.ENOENT:
2064 if e.errno != errno.ENOENT:
2062 raise
2065 raise
2063
2066
2064 ui.writenoi18n(b"%-6s free\n" % (name + b":"))
2067 ui.writenoi18n(b"%-6s free\n" % (name + b":"))
2065 return 0
2068 return 0
2066
2069
2067 held += report(repo.svfs, b"lock", repo.lock)
2070 held += report(repo.svfs, b"lock", repo.lock)
2068 held += report(repo.vfs, b"wlock", repo.wlock)
2071 held += report(repo.vfs, b"wlock", repo.wlock)
2069
2072
2070 return held
2073 return held
2071
2074
2072
2075
2073 @command(
2076 @command(
2074 b'debugmanifestfulltextcache',
2077 b'debugmanifestfulltextcache',
2075 [
2078 [
2076 (b'', b'clear', False, _(b'clear the cache')),
2079 (b'', b'clear', False, _(b'clear the cache')),
2077 (
2080 (
2078 b'a',
2081 b'a',
2079 b'add',
2082 b'add',
2080 [],
2083 [],
2081 _(b'add the given manifest nodes to the cache'),
2084 _(b'add the given manifest nodes to the cache'),
2082 _(b'NODE'),
2085 _(b'NODE'),
2083 ),
2086 ),
2084 ],
2087 ],
2085 b'',
2088 b'',
2086 )
2089 )
2087 def debugmanifestfulltextcache(ui, repo, add=(), **opts):
2090 def debugmanifestfulltextcache(ui, repo, add=(), **opts):
2088 """show, clear or amend the contents of the manifest fulltext cache"""
2091 """show, clear or amend the contents of the manifest fulltext cache"""
2089
2092
2090 def getcache():
2093 def getcache():
2091 r = repo.manifestlog.getstorage(b'')
2094 r = repo.manifestlog.getstorage(b'')
2092 try:
2095 try:
2093 return r._fulltextcache
2096 return r._fulltextcache
2094 except AttributeError:
2097 except AttributeError:
2095 msg = _(
2098 msg = _(
2096 b"Current revlog implementation doesn't appear to have a "
2099 b"Current revlog implementation doesn't appear to have a "
2097 b"manifest fulltext cache\n"
2100 b"manifest fulltext cache\n"
2098 )
2101 )
2099 raise error.Abort(msg)
2102 raise error.Abort(msg)
2100
2103
2101 if opts.get('clear'):
2104 if opts.get('clear'):
2102 with repo.wlock():
2105 with repo.wlock():
2103 cache = getcache()
2106 cache = getcache()
2104 cache.clear(clear_persisted_data=True)
2107 cache.clear(clear_persisted_data=True)
2105 return
2108 return
2106
2109
2107 if add:
2110 if add:
2108 with repo.wlock():
2111 with repo.wlock():
2109 m = repo.manifestlog
2112 m = repo.manifestlog
2110 store = m.getstorage(b'')
2113 store = m.getstorage(b'')
2111 for n in add:
2114 for n in add:
2112 try:
2115 try:
2113 manifest = m[store.lookup(n)]
2116 manifest = m[store.lookup(n)]
2114 except error.LookupError as e:
2117 except error.LookupError as e:
2115 raise error.Abort(e, hint=b"Check your manifest node id")
2118 raise error.Abort(e, hint=b"Check your manifest node id")
2116 manifest.read() # stores revisision in cache too
2119 manifest.read() # stores revisision in cache too
2117 return
2120 return
2118
2121
2119 cache = getcache()
2122 cache = getcache()
2120 if not len(cache):
2123 if not len(cache):
2121 ui.write(_(b'cache empty\n'))
2124 ui.write(_(b'cache empty\n'))
2122 else:
2125 else:
2123 ui.write(
2126 ui.write(
2124 _(
2127 _(
2125 b'cache contains %d manifest entries, in order of most to '
2128 b'cache contains %d manifest entries, in order of most to '
2126 b'least recent:\n'
2129 b'least recent:\n'
2127 )
2130 )
2128 % (len(cache),)
2131 % (len(cache),)
2129 )
2132 )
2130 totalsize = 0
2133 totalsize = 0
2131 for nodeid in cache:
2134 for nodeid in cache:
2132 # Use cache.get to not update the LRU order
2135 # Use cache.get to not update the LRU order
2133 data = cache.peek(nodeid)
2136 data = cache.peek(nodeid)
2134 size = len(data)
2137 size = len(data)
2135 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
2138 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
2136 ui.write(
2139 ui.write(
2137 _(b'id: %s, size %s\n') % (hex(nodeid), util.bytecount(size))
2140 _(b'id: %s, size %s\n') % (hex(nodeid), util.bytecount(size))
2138 )
2141 )
2139 ondisk = cache._opener.stat(b'manifestfulltextcache').st_size
2142 ondisk = cache._opener.stat(b'manifestfulltextcache').st_size
2140 ui.write(
2143 ui.write(
2141 _(b'total cache data size %s, on-disk %s\n')
2144 _(b'total cache data size %s, on-disk %s\n')
2142 % (util.bytecount(totalsize), util.bytecount(ondisk))
2145 % (util.bytecount(totalsize), util.bytecount(ondisk))
2143 )
2146 )
2144
2147
2145
2148
2146 @command(b'debugmergestate', [] + cmdutil.templateopts, b'')
2149 @command(b'debugmergestate', [] + cmdutil.templateopts, b'')
2147 def debugmergestate(ui, repo, *args, **opts):
2150 def debugmergestate(ui, repo, *args, **opts):
2148 """print merge state
2151 """print merge state
2149
2152
2150 Use --verbose to print out information about whether v1 or v2 merge state
2153 Use --verbose to print out information about whether v1 or v2 merge state
2151 was chosen."""
2154 was chosen."""
2152
2155
2153 if ui.verbose:
2156 if ui.verbose:
2154 ms = mergestatemod.mergestate(repo)
2157 ms = mergestatemod.mergestate(repo)
2155
2158
2156 # sort so that reasonable information is on top
2159 # sort so that reasonable information is on top
2157 v1records = ms._readrecordsv1()
2160 v1records = ms._readrecordsv1()
2158 v2records = ms._readrecordsv2()
2161 v2records = ms._readrecordsv2()
2159
2162
2160 if not v1records and not v2records:
2163 if not v1records and not v2records:
2161 pass
2164 pass
2162 elif not v2records:
2165 elif not v2records:
2163 ui.writenoi18n(b'no version 2 merge state\n')
2166 ui.writenoi18n(b'no version 2 merge state\n')
2164 elif ms._v1v2match(v1records, v2records):
2167 elif ms._v1v2match(v1records, v2records):
2165 ui.writenoi18n(b'v1 and v2 states match: using v2\n')
2168 ui.writenoi18n(b'v1 and v2 states match: using v2\n')
2166 else:
2169 else:
2167 ui.writenoi18n(b'v1 and v2 states mismatch: using v1\n')
2170 ui.writenoi18n(b'v1 and v2 states mismatch: using v1\n')
2168
2171
2169 opts = pycompat.byteskwargs(opts)
2172 opts = pycompat.byteskwargs(opts)
2170 if not opts[b'template']:
2173 if not opts[b'template']:
2171 opts[b'template'] = (
2174 opts[b'template'] = (
2172 b'{if(commits, "", "no merge state found\n")}'
2175 b'{if(commits, "", "no merge state found\n")}'
2173 b'{commits % "{name}{if(label, " ({label})")}: {node}\n"}'
2176 b'{commits % "{name}{if(label, " ({label})")}: {node}\n"}'
2174 b'{files % "file: {path} (state \\"{state}\\")\n'
2177 b'{files % "file: {path} (state \\"{state}\\")\n'
2175 b'{if(local_path, "'
2178 b'{if(local_path, "'
2176 b' local path: {local_path} (hash {local_key}, flags \\"{local_flags}\\")\n'
2179 b' local path: {local_path} (hash {local_key}, flags \\"{local_flags}\\")\n'
2177 b' ancestor path: {ancestor_path} (node {ancestor_node})\n'
2180 b' ancestor path: {ancestor_path} (node {ancestor_node})\n'
2178 b' other path: {other_path} (node {other_node})\n'
2181 b' other path: {other_path} (node {other_node})\n'
2179 b'")}'
2182 b'")}'
2180 b'{if(rename_side, "'
2183 b'{if(rename_side, "'
2181 b' rename side: {rename_side}\n'
2184 b' rename side: {rename_side}\n'
2182 b' renamed path: {renamed_path}\n'
2185 b' renamed path: {renamed_path}\n'
2183 b'")}'
2186 b'")}'
2184 b'{extras % " extra: {key} = {value}\n"}'
2187 b'{extras % " extra: {key} = {value}\n"}'
2185 b'"}'
2188 b'"}'
2186 b'{extras % "extra: {file} ({key} = {value})\n"}'
2189 b'{extras % "extra: {file} ({key} = {value})\n"}'
2187 )
2190 )
2188
2191
2189 ms = mergestatemod.mergestate.read(repo)
2192 ms = mergestatemod.mergestate.read(repo)
2190
2193
2191 fm = ui.formatter(b'debugmergestate', opts)
2194 fm = ui.formatter(b'debugmergestate', opts)
2192 fm.startitem()
2195 fm.startitem()
2193
2196
2194 fm_commits = fm.nested(b'commits')
2197 fm_commits = fm.nested(b'commits')
2195 if ms.active():
2198 if ms.active():
2196 for name, node, label_index in (
2199 for name, node, label_index in (
2197 (b'local', ms.local, 0),
2200 (b'local', ms.local, 0),
2198 (b'other', ms.other, 1),
2201 (b'other', ms.other, 1),
2199 ):
2202 ):
2200 fm_commits.startitem()
2203 fm_commits.startitem()
2201 fm_commits.data(name=name)
2204 fm_commits.data(name=name)
2202 fm_commits.data(node=hex(node))
2205 fm_commits.data(node=hex(node))
2203 if ms._labels and len(ms._labels) > label_index:
2206 if ms._labels and len(ms._labels) > label_index:
2204 fm_commits.data(label=ms._labels[label_index])
2207 fm_commits.data(label=ms._labels[label_index])
2205 fm_commits.end()
2208 fm_commits.end()
2206
2209
2207 fm_files = fm.nested(b'files')
2210 fm_files = fm.nested(b'files')
2208 if ms.active():
2211 if ms.active():
2209 for f in ms:
2212 for f in ms:
2210 fm_files.startitem()
2213 fm_files.startitem()
2211 fm_files.data(path=f)
2214 fm_files.data(path=f)
2212 state = ms._state[f]
2215 state = ms._state[f]
2213 fm_files.data(state=state[0])
2216 fm_files.data(state=state[0])
2214 if state[0] in (
2217 if state[0] in (
2215 mergestatemod.MERGE_RECORD_UNRESOLVED,
2218 mergestatemod.MERGE_RECORD_UNRESOLVED,
2216 mergestatemod.MERGE_RECORD_RESOLVED,
2219 mergestatemod.MERGE_RECORD_RESOLVED,
2217 ):
2220 ):
2218 fm_files.data(local_key=state[1])
2221 fm_files.data(local_key=state[1])
2219 fm_files.data(local_path=state[2])
2222 fm_files.data(local_path=state[2])
2220 fm_files.data(ancestor_path=state[3])
2223 fm_files.data(ancestor_path=state[3])
2221 fm_files.data(ancestor_node=state[4])
2224 fm_files.data(ancestor_node=state[4])
2222 fm_files.data(other_path=state[5])
2225 fm_files.data(other_path=state[5])
2223 fm_files.data(other_node=state[6])
2226 fm_files.data(other_node=state[6])
2224 fm_files.data(local_flags=state[7])
2227 fm_files.data(local_flags=state[7])
2225 elif state[0] in (
2228 elif state[0] in (
2226 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
2229 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
2227 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
2230 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
2228 ):
2231 ):
2229 fm_files.data(renamed_path=state[1])
2232 fm_files.data(renamed_path=state[1])
2230 fm_files.data(rename_side=state[2])
2233 fm_files.data(rename_side=state[2])
2231 fm_extras = fm_files.nested(b'extras')
2234 fm_extras = fm_files.nested(b'extras')
2232 for k, v in sorted(ms.extras(f).items()):
2235 for k, v in sorted(ms.extras(f).items()):
2233 fm_extras.startitem()
2236 fm_extras.startitem()
2234 fm_extras.data(key=k)
2237 fm_extras.data(key=k)
2235 fm_extras.data(value=v)
2238 fm_extras.data(value=v)
2236 fm_extras.end()
2239 fm_extras.end()
2237
2240
2238 fm_files.end()
2241 fm_files.end()
2239
2242
2240 fm_extras = fm.nested(b'extras')
2243 fm_extras = fm.nested(b'extras')
2241 for f, d in sorted(pycompat.iteritems(ms.allextras())):
2244 for f, d in sorted(pycompat.iteritems(ms.allextras())):
2242 if f in ms:
2245 if f in ms:
2243 # If file is in mergestate, we have already processed it's extras
2246 # If file is in mergestate, we have already processed it's extras
2244 continue
2247 continue
2245 for k, v in pycompat.iteritems(d):
2248 for k, v in pycompat.iteritems(d):
2246 fm_extras.startitem()
2249 fm_extras.startitem()
2247 fm_extras.data(file=f)
2250 fm_extras.data(file=f)
2248 fm_extras.data(key=k)
2251 fm_extras.data(key=k)
2249 fm_extras.data(value=v)
2252 fm_extras.data(value=v)
2250 fm_extras.end()
2253 fm_extras.end()
2251
2254
2252 fm.end()
2255 fm.end()
2253
2256
2254
2257
2255 @command(b'debugnamecomplete', [], _(b'NAME...'))
2258 @command(b'debugnamecomplete', [], _(b'NAME...'))
2256 def debugnamecomplete(ui, repo, *args):
2259 def debugnamecomplete(ui, repo, *args):
2257 '''complete "names" - tags, open branch names, bookmark names'''
2260 '''complete "names" - tags, open branch names, bookmark names'''
2258
2261
2259 names = set()
2262 names = set()
2260 # since we previously only listed open branches, we will handle that
2263 # since we previously only listed open branches, we will handle that
2261 # specially (after this for loop)
2264 # specially (after this for loop)
2262 for name, ns in pycompat.iteritems(repo.names):
2265 for name, ns in pycompat.iteritems(repo.names):
2263 if name != b'branches':
2266 if name != b'branches':
2264 names.update(ns.listnames(repo))
2267 names.update(ns.listnames(repo))
2265 names.update(
2268 names.update(
2266 tag
2269 tag
2267 for (tag, heads, tip, closed) in repo.branchmap().iterbranches()
2270 for (tag, heads, tip, closed) in repo.branchmap().iterbranches()
2268 if not closed
2271 if not closed
2269 )
2272 )
2270 completions = set()
2273 completions = set()
2271 if not args:
2274 if not args:
2272 args = [b'']
2275 args = [b'']
2273 for a in args:
2276 for a in args:
2274 completions.update(n for n in names if n.startswith(a))
2277 completions.update(n for n in names if n.startswith(a))
2275 ui.write(b'\n'.join(sorted(completions)))
2278 ui.write(b'\n'.join(sorted(completions)))
2276 ui.write(b'\n')
2279 ui.write(b'\n')
2277
2280
2278
2281
2279 @command(
2282 @command(
2280 b'debugnodemap',
2283 b'debugnodemap',
2281 [
2284 [
2282 (
2285 (
2283 b'',
2286 b'',
2284 b'dump-new',
2287 b'dump-new',
2285 False,
2288 False,
2286 _(b'write a (new) persistent binary nodemap on stdout'),
2289 _(b'write a (new) persistent binary nodemap on stdout'),
2287 ),
2290 ),
2288 (b'', b'dump-disk', False, _(b'dump on-disk data on stdout')),
2291 (b'', b'dump-disk', False, _(b'dump on-disk data on stdout')),
2289 (
2292 (
2290 b'',
2293 b'',
2291 b'check',
2294 b'check',
2292 False,
2295 False,
2293 _(b'check that the data on disk data are correct.'),
2296 _(b'check that the data on disk data are correct.'),
2294 ),
2297 ),
2295 (
2298 (
2296 b'',
2299 b'',
2297 b'metadata',
2300 b'metadata',
2298 False,
2301 False,
2299 _(b'display the on disk meta data for the nodemap'),
2302 _(b'display the on disk meta data for the nodemap'),
2300 ),
2303 ),
2301 ],
2304 ],
2302 )
2305 )
2303 def debugnodemap(ui, repo, **opts):
2306 def debugnodemap(ui, repo, **opts):
2304 """write and inspect on disk nodemap"""
2307 """write and inspect on disk nodemap"""
2305 if opts['dump_new']:
2308 if opts['dump_new']:
2306 unfi = repo.unfiltered()
2309 unfi = repo.unfiltered()
2307 cl = unfi.changelog
2310 cl = unfi.changelog
2308 if util.safehasattr(cl.index, "nodemap_data_all"):
2311 if util.safehasattr(cl.index, "nodemap_data_all"):
2309 data = cl.index.nodemap_data_all()
2312 data = cl.index.nodemap_data_all()
2310 else:
2313 else:
2311 data = nodemap.persistent_data(cl.index)
2314 data = nodemap.persistent_data(cl.index)
2312 ui.write(data)
2315 ui.write(data)
2313 elif opts['dump_disk']:
2316 elif opts['dump_disk']:
2314 unfi = repo.unfiltered()
2317 unfi = repo.unfiltered()
2315 cl = unfi.changelog
2318 cl = unfi.changelog
2316 nm_data = nodemap.persisted_data(cl)
2319 nm_data = nodemap.persisted_data(cl)
2317 if nm_data is not None:
2320 if nm_data is not None:
2318 docket, data = nm_data
2321 docket, data = nm_data
2319 ui.write(data[:])
2322 ui.write(data[:])
2320 elif opts['check']:
2323 elif opts['check']:
2321 unfi = repo.unfiltered()
2324 unfi = repo.unfiltered()
2322 cl = unfi.changelog
2325 cl = unfi.changelog
2323 nm_data = nodemap.persisted_data(cl)
2326 nm_data = nodemap.persisted_data(cl)
2324 if nm_data is not None:
2327 if nm_data is not None:
2325 docket, data = nm_data
2328 docket, data = nm_data
2326 return nodemap.check_data(ui, cl.index, data)
2329 return nodemap.check_data(ui, cl.index, data)
2327 elif opts['metadata']:
2330 elif opts['metadata']:
2328 unfi = repo.unfiltered()
2331 unfi = repo.unfiltered()
2329 cl = unfi.changelog
2332 cl = unfi.changelog
2330 nm_data = nodemap.persisted_data(cl)
2333 nm_data = nodemap.persisted_data(cl)
2331 if nm_data is not None:
2334 if nm_data is not None:
2332 docket, data = nm_data
2335 docket, data = nm_data
2333 ui.write((b"uid: %s\n") % docket.uid)
2336 ui.write((b"uid: %s\n") % docket.uid)
2334 ui.write((b"tip-rev: %d\n") % docket.tip_rev)
2337 ui.write((b"tip-rev: %d\n") % docket.tip_rev)
2335 ui.write((b"tip-node: %s\n") % hex(docket.tip_node))
2338 ui.write((b"tip-node: %s\n") % hex(docket.tip_node))
2336 ui.write((b"data-length: %d\n") % docket.data_length)
2339 ui.write((b"data-length: %d\n") % docket.data_length)
2337 ui.write((b"data-unused: %d\n") % docket.data_unused)
2340 ui.write((b"data-unused: %d\n") % docket.data_unused)
2338 unused_perc = docket.data_unused * 100.0 / docket.data_length
2341 unused_perc = docket.data_unused * 100.0 / docket.data_length
2339 ui.write((b"data-unused: %2.3f%%\n") % unused_perc)
2342 ui.write((b"data-unused: %2.3f%%\n") % unused_perc)
2340
2343
2341
2344
2342 @command(
2345 @command(
2343 b'debugobsolete',
2346 b'debugobsolete',
2344 [
2347 [
2345 (b'', b'flags', 0, _(b'markers flag')),
2348 (b'', b'flags', 0, _(b'markers flag')),
2346 (
2349 (
2347 b'',
2350 b'',
2348 b'record-parents',
2351 b'record-parents',
2349 False,
2352 False,
2350 _(b'record parent information for the precursor'),
2353 _(b'record parent information for the precursor'),
2351 ),
2354 ),
2352 (b'r', b'rev', [], _(b'display markers relevant to REV')),
2355 (b'r', b'rev', [], _(b'display markers relevant to REV')),
2353 (
2356 (
2354 b'',
2357 b'',
2355 b'exclusive',
2358 b'exclusive',
2356 False,
2359 False,
2357 _(b'restrict display to markers only relevant to REV'),
2360 _(b'restrict display to markers only relevant to REV'),
2358 ),
2361 ),
2359 (b'', b'index', False, _(b'display index of the marker')),
2362 (b'', b'index', False, _(b'display index of the marker')),
2360 (b'', b'delete', [], _(b'delete markers specified by indices')),
2363 (b'', b'delete', [], _(b'delete markers specified by indices')),
2361 ]
2364 ]
2362 + cmdutil.commitopts2
2365 + cmdutil.commitopts2
2363 + cmdutil.formatteropts,
2366 + cmdutil.formatteropts,
2364 _(b'[OBSOLETED [REPLACEMENT ...]]'),
2367 _(b'[OBSOLETED [REPLACEMENT ...]]'),
2365 )
2368 )
2366 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2369 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2367 """create arbitrary obsolete marker
2370 """create arbitrary obsolete marker
2368
2371
2369 With no arguments, displays the list of obsolescence markers."""
2372 With no arguments, displays the list of obsolescence markers."""
2370
2373
2371 opts = pycompat.byteskwargs(opts)
2374 opts = pycompat.byteskwargs(opts)
2372
2375
2373 def parsenodeid(s):
2376 def parsenodeid(s):
2374 try:
2377 try:
2375 # We do not use revsingle/revrange functions here to accept
2378 # We do not use revsingle/revrange functions here to accept
2376 # arbitrary node identifiers, possibly not present in the
2379 # arbitrary node identifiers, possibly not present in the
2377 # local repository.
2380 # local repository.
2378 n = bin(s)
2381 n = bin(s)
2379 if len(n) != len(nullid):
2382 if len(n) != len(nullid):
2380 raise TypeError()
2383 raise TypeError()
2381 return n
2384 return n
2382 except TypeError:
2385 except TypeError:
2383 raise error.InputError(
2386 raise error.InputError(
2384 b'changeset references must be full hexadecimal '
2387 b'changeset references must be full hexadecimal '
2385 b'node identifiers'
2388 b'node identifiers'
2386 )
2389 )
2387
2390
2388 if opts.get(b'delete'):
2391 if opts.get(b'delete'):
2389 indices = []
2392 indices = []
2390 for v in opts.get(b'delete'):
2393 for v in opts.get(b'delete'):
2391 try:
2394 try:
2392 indices.append(int(v))
2395 indices.append(int(v))
2393 except ValueError:
2396 except ValueError:
2394 raise error.InputError(
2397 raise error.InputError(
2395 _(b'invalid index value: %r') % v,
2398 _(b'invalid index value: %r') % v,
2396 hint=_(b'use integers for indices'),
2399 hint=_(b'use integers for indices'),
2397 )
2400 )
2398
2401
2399 if repo.currenttransaction():
2402 if repo.currenttransaction():
2400 raise error.Abort(
2403 raise error.Abort(
2401 _(b'cannot delete obsmarkers in the middle of transaction.')
2404 _(b'cannot delete obsmarkers in the middle of transaction.')
2402 )
2405 )
2403
2406
2404 with repo.lock():
2407 with repo.lock():
2405 n = repair.deleteobsmarkers(repo.obsstore, indices)
2408 n = repair.deleteobsmarkers(repo.obsstore, indices)
2406 ui.write(_(b'deleted %i obsolescence markers\n') % n)
2409 ui.write(_(b'deleted %i obsolescence markers\n') % n)
2407
2410
2408 return
2411 return
2409
2412
2410 if precursor is not None:
2413 if precursor is not None:
2411 if opts[b'rev']:
2414 if opts[b'rev']:
2412 raise error.InputError(
2415 raise error.InputError(
2413 b'cannot select revision when creating marker'
2416 b'cannot select revision when creating marker'
2414 )
2417 )
2415 metadata = {}
2418 metadata = {}
2416 metadata[b'user'] = encoding.fromlocal(opts[b'user'] or ui.username())
2419 metadata[b'user'] = encoding.fromlocal(opts[b'user'] or ui.username())
2417 succs = tuple(parsenodeid(succ) for succ in successors)
2420 succs = tuple(parsenodeid(succ) for succ in successors)
2418 l = repo.lock()
2421 l = repo.lock()
2419 try:
2422 try:
2420 tr = repo.transaction(b'debugobsolete')
2423 tr = repo.transaction(b'debugobsolete')
2421 try:
2424 try:
2422 date = opts.get(b'date')
2425 date = opts.get(b'date')
2423 if date:
2426 if date:
2424 date = dateutil.parsedate(date)
2427 date = dateutil.parsedate(date)
2425 else:
2428 else:
2426 date = None
2429 date = None
2427 prec = parsenodeid(precursor)
2430 prec = parsenodeid(precursor)
2428 parents = None
2431 parents = None
2429 if opts[b'record_parents']:
2432 if opts[b'record_parents']:
2430 if prec not in repo.unfiltered():
2433 if prec not in repo.unfiltered():
2431 raise error.Abort(
2434 raise error.Abort(
2432 b'cannot used --record-parents on '
2435 b'cannot used --record-parents on '
2433 b'unknown changesets'
2436 b'unknown changesets'
2434 )
2437 )
2435 parents = repo.unfiltered()[prec].parents()
2438 parents = repo.unfiltered()[prec].parents()
2436 parents = tuple(p.node() for p in parents)
2439 parents = tuple(p.node() for p in parents)
2437 repo.obsstore.create(
2440 repo.obsstore.create(
2438 tr,
2441 tr,
2439 prec,
2442 prec,
2440 succs,
2443 succs,
2441 opts[b'flags'],
2444 opts[b'flags'],
2442 parents=parents,
2445 parents=parents,
2443 date=date,
2446 date=date,
2444 metadata=metadata,
2447 metadata=metadata,
2445 ui=ui,
2448 ui=ui,
2446 )
2449 )
2447 tr.close()
2450 tr.close()
2448 except ValueError as exc:
2451 except ValueError as exc:
2449 raise error.Abort(
2452 raise error.Abort(
2450 _(b'bad obsmarker input: %s') % pycompat.bytestr(exc)
2453 _(b'bad obsmarker input: %s') % pycompat.bytestr(exc)
2451 )
2454 )
2452 finally:
2455 finally:
2453 tr.release()
2456 tr.release()
2454 finally:
2457 finally:
2455 l.release()
2458 l.release()
2456 else:
2459 else:
2457 if opts[b'rev']:
2460 if opts[b'rev']:
2458 revs = scmutil.revrange(repo, opts[b'rev'])
2461 revs = scmutil.revrange(repo, opts[b'rev'])
2459 nodes = [repo[r].node() for r in revs]
2462 nodes = [repo[r].node() for r in revs]
2460 markers = list(
2463 markers = list(
2461 obsutil.getmarkers(
2464 obsutil.getmarkers(
2462 repo, nodes=nodes, exclusive=opts[b'exclusive']
2465 repo, nodes=nodes, exclusive=opts[b'exclusive']
2463 )
2466 )
2464 )
2467 )
2465 markers.sort(key=lambda x: x._data)
2468 markers.sort(key=lambda x: x._data)
2466 else:
2469 else:
2467 markers = obsutil.getmarkers(repo)
2470 markers = obsutil.getmarkers(repo)
2468
2471
2469 markerstoiter = markers
2472 markerstoiter = markers
2470 isrelevant = lambda m: True
2473 isrelevant = lambda m: True
2471 if opts.get(b'rev') and opts.get(b'index'):
2474 if opts.get(b'rev') and opts.get(b'index'):
2472 markerstoiter = obsutil.getmarkers(repo)
2475 markerstoiter = obsutil.getmarkers(repo)
2473 markerset = set(markers)
2476 markerset = set(markers)
2474 isrelevant = lambda m: m in markerset
2477 isrelevant = lambda m: m in markerset
2475
2478
2476 fm = ui.formatter(b'debugobsolete', opts)
2479 fm = ui.formatter(b'debugobsolete', opts)
2477 for i, m in enumerate(markerstoiter):
2480 for i, m in enumerate(markerstoiter):
2478 if not isrelevant(m):
2481 if not isrelevant(m):
2479 # marker can be irrelevant when we're iterating over a set
2482 # marker can be irrelevant when we're iterating over a set
2480 # of markers (markerstoiter) which is bigger than the set
2483 # of markers (markerstoiter) which is bigger than the set
2481 # of markers we want to display (markers)
2484 # of markers we want to display (markers)
2482 # this can happen if both --index and --rev options are
2485 # this can happen if both --index and --rev options are
2483 # provided and thus we need to iterate over all of the markers
2486 # provided and thus we need to iterate over all of the markers
2484 # to get the correct indices, but only display the ones that
2487 # to get the correct indices, but only display the ones that
2485 # are relevant to --rev value
2488 # are relevant to --rev value
2486 continue
2489 continue
2487 fm.startitem()
2490 fm.startitem()
2488 ind = i if opts.get(b'index') else None
2491 ind = i if opts.get(b'index') else None
2489 cmdutil.showmarker(fm, m, index=ind)
2492 cmdutil.showmarker(fm, m, index=ind)
2490 fm.end()
2493 fm.end()
2491
2494
2492
2495
2493 @command(
2496 @command(
2494 b'debugp1copies',
2497 b'debugp1copies',
2495 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2498 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2496 _(b'[-r REV]'),
2499 _(b'[-r REV]'),
2497 )
2500 )
2498 def debugp1copies(ui, repo, **opts):
2501 def debugp1copies(ui, repo, **opts):
2499 """dump copy information compared to p1"""
2502 """dump copy information compared to p1"""
2500
2503
2501 opts = pycompat.byteskwargs(opts)
2504 opts = pycompat.byteskwargs(opts)
2502 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2505 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2503 for dst, src in ctx.p1copies().items():
2506 for dst, src in ctx.p1copies().items():
2504 ui.write(b'%s -> %s\n' % (src, dst))
2507 ui.write(b'%s -> %s\n' % (src, dst))
2505
2508
2506
2509
2507 @command(
2510 @command(
2508 b'debugp2copies',
2511 b'debugp2copies',
2509 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2512 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2510 _(b'[-r REV]'),
2513 _(b'[-r REV]'),
2511 )
2514 )
2512 def debugp1copies(ui, repo, **opts):
2515 def debugp1copies(ui, repo, **opts):
2513 """dump copy information compared to p2"""
2516 """dump copy information compared to p2"""
2514
2517
2515 opts = pycompat.byteskwargs(opts)
2518 opts = pycompat.byteskwargs(opts)
2516 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2519 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2517 for dst, src in ctx.p2copies().items():
2520 for dst, src in ctx.p2copies().items():
2518 ui.write(b'%s -> %s\n' % (src, dst))
2521 ui.write(b'%s -> %s\n' % (src, dst))
2519
2522
2520
2523
2521 @command(
2524 @command(
2522 b'debugpathcomplete',
2525 b'debugpathcomplete',
2523 [
2526 [
2524 (b'f', b'full', None, _(b'complete an entire path')),
2527 (b'f', b'full', None, _(b'complete an entire path')),
2525 (b'n', b'normal', None, _(b'show only normal files')),
2528 (b'n', b'normal', None, _(b'show only normal files')),
2526 (b'a', b'added', None, _(b'show only added files')),
2529 (b'a', b'added', None, _(b'show only added files')),
2527 (b'r', b'removed', None, _(b'show only removed files')),
2530 (b'r', b'removed', None, _(b'show only removed files')),
2528 ],
2531 ],
2529 _(b'FILESPEC...'),
2532 _(b'FILESPEC...'),
2530 )
2533 )
2531 def debugpathcomplete(ui, repo, *specs, **opts):
2534 def debugpathcomplete(ui, repo, *specs, **opts):
2532 """complete part or all of a tracked path
2535 """complete part or all of a tracked path
2533
2536
2534 This command supports shells that offer path name completion. It
2537 This command supports shells that offer path name completion. It
2535 currently completes only files already known to the dirstate.
2538 currently completes only files already known to the dirstate.
2536
2539
2537 Completion extends only to the next path segment unless
2540 Completion extends only to the next path segment unless
2538 --full is specified, in which case entire paths are used."""
2541 --full is specified, in which case entire paths are used."""
2539
2542
2540 def complete(path, acceptable):
2543 def complete(path, acceptable):
2541 dirstate = repo.dirstate
2544 dirstate = repo.dirstate
2542 spec = os.path.normpath(os.path.join(encoding.getcwd(), path))
2545 spec = os.path.normpath(os.path.join(encoding.getcwd(), path))
2543 rootdir = repo.root + pycompat.ossep
2546 rootdir = repo.root + pycompat.ossep
2544 if spec != repo.root and not spec.startswith(rootdir):
2547 if spec != repo.root and not spec.startswith(rootdir):
2545 return [], []
2548 return [], []
2546 if os.path.isdir(spec):
2549 if os.path.isdir(spec):
2547 spec += b'/'
2550 spec += b'/'
2548 spec = spec[len(rootdir) :]
2551 spec = spec[len(rootdir) :]
2549 fixpaths = pycompat.ossep != b'/'
2552 fixpaths = pycompat.ossep != b'/'
2550 if fixpaths:
2553 if fixpaths:
2551 spec = spec.replace(pycompat.ossep, b'/')
2554 spec = spec.replace(pycompat.ossep, b'/')
2552 speclen = len(spec)
2555 speclen = len(spec)
2553 fullpaths = opts['full']
2556 fullpaths = opts['full']
2554 files, dirs = set(), set()
2557 files, dirs = set(), set()
2555 adddir, addfile = dirs.add, files.add
2558 adddir, addfile = dirs.add, files.add
2556 for f, st in pycompat.iteritems(dirstate):
2559 for f, st in pycompat.iteritems(dirstate):
2557 if f.startswith(spec) and st[0] in acceptable:
2560 if f.startswith(spec) and st[0] in acceptable:
2558 if fixpaths:
2561 if fixpaths:
2559 f = f.replace(b'/', pycompat.ossep)
2562 f = f.replace(b'/', pycompat.ossep)
2560 if fullpaths:
2563 if fullpaths:
2561 addfile(f)
2564 addfile(f)
2562 continue
2565 continue
2563 s = f.find(pycompat.ossep, speclen)
2566 s = f.find(pycompat.ossep, speclen)
2564 if s >= 0:
2567 if s >= 0:
2565 adddir(f[:s])
2568 adddir(f[:s])
2566 else:
2569 else:
2567 addfile(f)
2570 addfile(f)
2568 return files, dirs
2571 return files, dirs
2569
2572
2570 acceptable = b''
2573 acceptable = b''
2571 if opts['normal']:
2574 if opts['normal']:
2572 acceptable += b'nm'
2575 acceptable += b'nm'
2573 if opts['added']:
2576 if opts['added']:
2574 acceptable += b'a'
2577 acceptable += b'a'
2575 if opts['removed']:
2578 if opts['removed']:
2576 acceptable += b'r'
2579 acceptable += b'r'
2577 cwd = repo.getcwd()
2580 cwd = repo.getcwd()
2578 if not specs:
2581 if not specs:
2579 specs = [b'.']
2582 specs = [b'.']
2580
2583
2581 files, dirs = set(), set()
2584 files, dirs = set(), set()
2582 for spec in specs:
2585 for spec in specs:
2583 f, d = complete(spec, acceptable or b'nmar')
2586 f, d = complete(spec, acceptable or b'nmar')
2584 files.update(f)
2587 files.update(f)
2585 dirs.update(d)
2588 dirs.update(d)
2586 files.update(dirs)
2589 files.update(dirs)
2587 ui.write(b'\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2590 ui.write(b'\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2588 ui.write(b'\n')
2591 ui.write(b'\n')
2589
2592
2590
2593
2591 @command(
2594 @command(
2592 b'debugpathcopies',
2595 b'debugpathcopies',
2593 cmdutil.walkopts,
2596 cmdutil.walkopts,
2594 b'hg debugpathcopies REV1 REV2 [FILE]',
2597 b'hg debugpathcopies REV1 REV2 [FILE]',
2595 inferrepo=True,
2598 inferrepo=True,
2596 )
2599 )
2597 def debugpathcopies(ui, repo, rev1, rev2, *pats, **opts):
2600 def debugpathcopies(ui, repo, rev1, rev2, *pats, **opts):
2598 """show copies between two revisions"""
2601 """show copies between two revisions"""
2599 ctx1 = scmutil.revsingle(repo, rev1)
2602 ctx1 = scmutil.revsingle(repo, rev1)
2600 ctx2 = scmutil.revsingle(repo, rev2)
2603 ctx2 = scmutil.revsingle(repo, rev2)
2601 m = scmutil.match(ctx1, pats, opts)
2604 m = scmutil.match(ctx1, pats, opts)
2602 for dst, src in sorted(copies.pathcopies(ctx1, ctx2, m).items()):
2605 for dst, src in sorted(copies.pathcopies(ctx1, ctx2, m).items()):
2603 ui.write(b'%s -> %s\n' % (src, dst))
2606 ui.write(b'%s -> %s\n' % (src, dst))
2604
2607
2605
2608
2606 @command(b'debugpeer', [], _(b'PATH'), norepo=True)
2609 @command(b'debugpeer', [], _(b'PATH'), norepo=True)
2607 def debugpeer(ui, path):
2610 def debugpeer(ui, path):
2608 """establish a connection to a peer repository"""
2611 """establish a connection to a peer repository"""
2609 # Always enable peer request logging. Requires --debug to display
2612 # Always enable peer request logging. Requires --debug to display
2610 # though.
2613 # though.
2611 overrides = {
2614 overrides = {
2612 (b'devel', b'debug.peer-request'): True,
2615 (b'devel', b'debug.peer-request'): True,
2613 }
2616 }
2614
2617
2615 with ui.configoverride(overrides):
2618 with ui.configoverride(overrides):
2616 peer = hg.peer(ui, {}, path)
2619 peer = hg.peer(ui, {}, path)
2617
2620
2618 local = peer.local() is not None
2621 try:
2619 canpush = peer.canpush()
2622 local = peer.local() is not None
2620
2623 canpush = peer.canpush()
2621 ui.write(_(b'url: %s\n') % peer.url())
2624
2622 ui.write(_(b'local: %s\n') % (_(b'yes') if local else _(b'no')))
2625 ui.write(_(b'url: %s\n') % peer.url())
2623 ui.write(_(b'pushable: %s\n') % (_(b'yes') if canpush else _(b'no')))
2626 ui.write(_(b'local: %s\n') % (_(b'yes') if local else _(b'no')))
2627 ui.write(
2628 _(b'pushable: %s\n') % (_(b'yes') if canpush else _(b'no'))
2629 )
2630 finally:
2631 peer.close()
2624
2632
2625
2633
2626 @command(
2634 @command(
2627 b'debugpickmergetool',
2635 b'debugpickmergetool',
2628 [
2636 [
2629 (b'r', b'rev', b'', _(b'check for files in this revision'), _(b'REV')),
2637 (b'r', b'rev', b'', _(b'check for files in this revision'), _(b'REV')),
2630 (b'', b'changedelete', None, _(b'emulate merging change and delete')),
2638 (b'', b'changedelete', None, _(b'emulate merging change and delete')),
2631 ]
2639 ]
2632 + cmdutil.walkopts
2640 + cmdutil.walkopts
2633 + cmdutil.mergetoolopts,
2641 + cmdutil.mergetoolopts,
2634 _(b'[PATTERN]...'),
2642 _(b'[PATTERN]...'),
2635 inferrepo=True,
2643 inferrepo=True,
2636 )
2644 )
2637 def debugpickmergetool(ui, repo, *pats, **opts):
2645 def debugpickmergetool(ui, repo, *pats, **opts):
2638 """examine which merge tool is chosen for specified file
2646 """examine which merge tool is chosen for specified file
2639
2647
2640 As described in :hg:`help merge-tools`, Mercurial examines
2648 As described in :hg:`help merge-tools`, Mercurial examines
2641 configurations below in this order to decide which merge tool is
2649 configurations below in this order to decide which merge tool is
2642 chosen for specified file.
2650 chosen for specified file.
2643
2651
2644 1. ``--tool`` option
2652 1. ``--tool`` option
2645 2. ``HGMERGE`` environment variable
2653 2. ``HGMERGE`` environment variable
2646 3. configurations in ``merge-patterns`` section
2654 3. configurations in ``merge-patterns`` section
2647 4. configuration of ``ui.merge``
2655 4. configuration of ``ui.merge``
2648 5. configurations in ``merge-tools`` section
2656 5. configurations in ``merge-tools`` section
2649 6. ``hgmerge`` tool (for historical reason only)
2657 6. ``hgmerge`` tool (for historical reason only)
2650 7. default tool for fallback (``:merge`` or ``:prompt``)
2658 7. default tool for fallback (``:merge`` or ``:prompt``)
2651
2659
2652 This command writes out examination result in the style below::
2660 This command writes out examination result in the style below::
2653
2661
2654 FILE = MERGETOOL
2662 FILE = MERGETOOL
2655
2663
2656 By default, all files known in the first parent context of the
2664 By default, all files known in the first parent context of the
2657 working directory are examined. Use file patterns and/or -I/-X
2665 working directory are examined. Use file patterns and/or -I/-X
2658 options to limit target files. -r/--rev is also useful to examine
2666 options to limit target files. -r/--rev is also useful to examine
2659 files in another context without actual updating to it.
2667 files in another context without actual updating to it.
2660
2668
2661 With --debug, this command shows warning messages while matching
2669 With --debug, this command shows warning messages while matching
2662 against ``merge-patterns`` and so on, too. It is recommended to
2670 against ``merge-patterns`` and so on, too. It is recommended to
2663 use this option with explicit file patterns and/or -I/-X options,
2671 use this option with explicit file patterns and/or -I/-X options,
2664 because this option increases amount of output per file according
2672 because this option increases amount of output per file according
2665 to configurations in hgrc.
2673 to configurations in hgrc.
2666
2674
2667 With -v/--verbose, this command shows configurations below at
2675 With -v/--verbose, this command shows configurations below at
2668 first (only if specified).
2676 first (only if specified).
2669
2677
2670 - ``--tool`` option
2678 - ``--tool`` option
2671 - ``HGMERGE`` environment variable
2679 - ``HGMERGE`` environment variable
2672 - configuration of ``ui.merge``
2680 - configuration of ``ui.merge``
2673
2681
2674 If merge tool is chosen before matching against
2682 If merge tool is chosen before matching against
2675 ``merge-patterns``, this command can't show any helpful
2683 ``merge-patterns``, this command can't show any helpful
2676 information, even with --debug. In such case, information above is
2684 information, even with --debug. In such case, information above is
2677 useful to know why a merge tool is chosen.
2685 useful to know why a merge tool is chosen.
2678 """
2686 """
2679 opts = pycompat.byteskwargs(opts)
2687 opts = pycompat.byteskwargs(opts)
2680 overrides = {}
2688 overrides = {}
2681 if opts[b'tool']:
2689 if opts[b'tool']:
2682 overrides[(b'ui', b'forcemerge')] = opts[b'tool']
2690 overrides[(b'ui', b'forcemerge')] = opts[b'tool']
2683 ui.notenoi18n(b'with --tool %r\n' % (pycompat.bytestr(opts[b'tool'])))
2691 ui.notenoi18n(b'with --tool %r\n' % (pycompat.bytestr(opts[b'tool'])))
2684
2692
2685 with ui.configoverride(overrides, b'debugmergepatterns'):
2693 with ui.configoverride(overrides, b'debugmergepatterns'):
2686 hgmerge = encoding.environ.get(b"HGMERGE")
2694 hgmerge = encoding.environ.get(b"HGMERGE")
2687 if hgmerge is not None:
2695 if hgmerge is not None:
2688 ui.notenoi18n(b'with HGMERGE=%r\n' % (pycompat.bytestr(hgmerge)))
2696 ui.notenoi18n(b'with HGMERGE=%r\n' % (pycompat.bytestr(hgmerge)))
2689 uimerge = ui.config(b"ui", b"merge")
2697 uimerge = ui.config(b"ui", b"merge")
2690 if uimerge:
2698 if uimerge:
2691 ui.notenoi18n(b'with ui.merge=%r\n' % (pycompat.bytestr(uimerge)))
2699 ui.notenoi18n(b'with ui.merge=%r\n' % (pycompat.bytestr(uimerge)))
2692
2700
2693 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2701 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2694 m = scmutil.match(ctx, pats, opts)
2702 m = scmutil.match(ctx, pats, opts)
2695 changedelete = opts[b'changedelete']
2703 changedelete = opts[b'changedelete']
2696 for path in ctx.walk(m):
2704 for path in ctx.walk(m):
2697 fctx = ctx[path]
2705 fctx = ctx[path]
2698 try:
2706 try:
2699 if not ui.debugflag:
2707 if not ui.debugflag:
2700 ui.pushbuffer(error=True)
2708 ui.pushbuffer(error=True)
2701 tool, toolpath = filemerge._picktool(
2709 tool, toolpath = filemerge._picktool(
2702 repo,
2710 repo,
2703 ui,
2711 ui,
2704 path,
2712 path,
2705 fctx.isbinary(),
2713 fctx.isbinary(),
2706 b'l' in fctx.flags(),
2714 b'l' in fctx.flags(),
2707 changedelete,
2715 changedelete,
2708 )
2716 )
2709 finally:
2717 finally:
2710 if not ui.debugflag:
2718 if not ui.debugflag:
2711 ui.popbuffer()
2719 ui.popbuffer()
2712 ui.write(b'%s = %s\n' % (path, tool))
2720 ui.write(b'%s = %s\n' % (path, tool))
2713
2721
2714
2722
2715 @command(b'debugpushkey', [], _(b'REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2723 @command(b'debugpushkey', [], _(b'REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2716 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2724 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2717 """access the pushkey key/value protocol
2725 """access the pushkey key/value protocol
2718
2726
2719 With two args, list the keys in the given namespace.
2727 With two args, list the keys in the given namespace.
2720
2728
2721 With five args, set a key to new if it currently is set to old.
2729 With five args, set a key to new if it currently is set to old.
2722 Reports success or failure.
2730 Reports success or failure.
2723 """
2731 """
2724
2732
2725 target = hg.peer(ui, {}, repopath)
2733 target = hg.peer(ui, {}, repopath)
2726 if keyinfo:
2734 try:
2727 key, old, new = keyinfo
2735 if keyinfo:
2728 with target.commandexecutor() as e:
2736 key, old, new = keyinfo
2729 r = e.callcommand(
2737 with target.commandexecutor() as e:
2730 b'pushkey',
2738 r = e.callcommand(
2731 {
2739 b'pushkey',
2732 b'namespace': namespace,
2740 {
2733 b'key': key,
2741 b'namespace': namespace,
2734 b'old': old,
2742 b'key': key,
2735 b'new': new,
2743 b'old': old,
2736 },
2744 b'new': new,
2737 ).result()
2745 },
2738
2746 ).result()
2739 ui.status(pycompat.bytestr(r) + b'\n')
2747
2740 return not r
2748 ui.status(pycompat.bytestr(r) + b'\n')
2741 else:
2749 return not r
2742 for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))):
2750 else:
2743 ui.write(
2751 for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))):
2744 b"%s\t%s\n" % (stringutil.escapestr(k), stringutil.escapestr(v))
2752 ui.write(
2745 )
2753 b"%s\t%s\n"
2754 % (stringutil.escapestr(k), stringutil.escapestr(v))
2755 )
2756 finally:
2757 target.close()
2746
2758
2747
2759
2748 @command(b'debugpvec', [], _(b'A B'))
2760 @command(b'debugpvec', [], _(b'A B'))
2749 def debugpvec(ui, repo, a, b=None):
2761 def debugpvec(ui, repo, a, b=None):
2750 ca = scmutil.revsingle(repo, a)
2762 ca = scmutil.revsingle(repo, a)
2751 cb = scmutil.revsingle(repo, b)
2763 cb = scmutil.revsingle(repo, b)
2752 pa = pvec.ctxpvec(ca)
2764 pa = pvec.ctxpvec(ca)
2753 pb = pvec.ctxpvec(cb)
2765 pb = pvec.ctxpvec(cb)
2754 if pa == pb:
2766 if pa == pb:
2755 rel = b"="
2767 rel = b"="
2756 elif pa > pb:
2768 elif pa > pb:
2757 rel = b">"
2769 rel = b">"
2758 elif pa < pb:
2770 elif pa < pb:
2759 rel = b"<"
2771 rel = b"<"
2760 elif pa | pb:
2772 elif pa | pb:
2761 rel = b"|"
2773 rel = b"|"
2762 ui.write(_(b"a: %s\n") % pa)
2774 ui.write(_(b"a: %s\n") % pa)
2763 ui.write(_(b"b: %s\n") % pb)
2775 ui.write(_(b"b: %s\n") % pb)
2764 ui.write(_(b"depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2776 ui.write(_(b"depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2765 ui.write(
2777 ui.write(
2766 _(b"delta: %d hdist: %d distance: %d relation: %s\n")
2778 _(b"delta: %d hdist: %d distance: %d relation: %s\n")
2767 % (
2779 % (
2768 abs(pa._depth - pb._depth),
2780 abs(pa._depth - pb._depth),
2769 pvec._hamming(pa._vec, pb._vec),
2781 pvec._hamming(pa._vec, pb._vec),
2770 pa.distance(pb),
2782 pa.distance(pb),
2771 rel,
2783 rel,
2772 )
2784 )
2773 )
2785 )
2774
2786
2775
2787
2776 @command(
2788 @command(
2777 b'debugrebuilddirstate|debugrebuildstate',
2789 b'debugrebuilddirstate|debugrebuildstate',
2778 [
2790 [
2779 (b'r', b'rev', b'', _(b'revision to rebuild to'), _(b'REV')),
2791 (b'r', b'rev', b'', _(b'revision to rebuild to'), _(b'REV')),
2780 (
2792 (
2781 b'',
2793 b'',
2782 b'minimal',
2794 b'minimal',
2783 None,
2795 None,
2784 _(
2796 _(
2785 b'only rebuild files that are inconsistent with '
2797 b'only rebuild files that are inconsistent with '
2786 b'the working copy parent'
2798 b'the working copy parent'
2787 ),
2799 ),
2788 ),
2800 ),
2789 ],
2801 ],
2790 _(b'[-r REV]'),
2802 _(b'[-r REV]'),
2791 )
2803 )
2792 def debugrebuilddirstate(ui, repo, rev, **opts):
2804 def debugrebuilddirstate(ui, repo, rev, **opts):
2793 """rebuild the dirstate as it would look like for the given revision
2805 """rebuild the dirstate as it would look like for the given revision
2794
2806
2795 If no revision is specified the first current parent will be used.
2807 If no revision is specified the first current parent will be used.
2796
2808
2797 The dirstate will be set to the files of the given revision.
2809 The dirstate will be set to the files of the given revision.
2798 The actual working directory content or existing dirstate
2810 The actual working directory content or existing dirstate
2799 information such as adds or removes is not considered.
2811 information such as adds or removes is not considered.
2800
2812
2801 ``minimal`` will only rebuild the dirstate status for files that claim to be
2813 ``minimal`` will only rebuild the dirstate status for files that claim to be
2802 tracked but are not in the parent manifest, or that exist in the parent
2814 tracked but are not in the parent manifest, or that exist in the parent
2803 manifest but are not in the dirstate. It will not change adds, removes, or
2815 manifest but are not in the dirstate. It will not change adds, removes, or
2804 modified files that are in the working copy parent.
2816 modified files that are in the working copy parent.
2805
2817
2806 One use of this command is to make the next :hg:`status` invocation
2818 One use of this command is to make the next :hg:`status` invocation
2807 check the actual file content.
2819 check the actual file content.
2808 """
2820 """
2809 ctx = scmutil.revsingle(repo, rev)
2821 ctx = scmutil.revsingle(repo, rev)
2810 with repo.wlock():
2822 with repo.wlock():
2811 dirstate = repo.dirstate
2823 dirstate = repo.dirstate
2812 changedfiles = None
2824 changedfiles = None
2813 # See command doc for what minimal does.
2825 # See command doc for what minimal does.
2814 if opts.get('minimal'):
2826 if opts.get('minimal'):
2815 manifestfiles = set(ctx.manifest().keys())
2827 manifestfiles = set(ctx.manifest().keys())
2816 dirstatefiles = set(dirstate)
2828 dirstatefiles = set(dirstate)
2817 manifestonly = manifestfiles - dirstatefiles
2829 manifestonly = manifestfiles - dirstatefiles
2818 dsonly = dirstatefiles - manifestfiles
2830 dsonly = dirstatefiles - manifestfiles
2819 dsnotadded = {f for f in dsonly if dirstate[f] != b'a'}
2831 dsnotadded = {f for f in dsonly if dirstate[f] != b'a'}
2820 changedfiles = manifestonly | dsnotadded
2832 changedfiles = manifestonly | dsnotadded
2821
2833
2822 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2834 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2823
2835
2824
2836
2825 @command(b'debugrebuildfncache', [], b'')
2837 @command(b'debugrebuildfncache', [], b'')
2826 def debugrebuildfncache(ui, repo):
2838 def debugrebuildfncache(ui, repo):
2827 """rebuild the fncache file"""
2839 """rebuild the fncache file"""
2828 repair.rebuildfncache(ui, repo)
2840 repair.rebuildfncache(ui, repo)
2829
2841
2830
2842
2831 @command(
2843 @command(
2832 b'debugrename',
2844 b'debugrename',
2833 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2845 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2834 _(b'[-r REV] [FILE]...'),
2846 _(b'[-r REV] [FILE]...'),
2835 )
2847 )
2836 def debugrename(ui, repo, *pats, **opts):
2848 def debugrename(ui, repo, *pats, **opts):
2837 """dump rename information"""
2849 """dump rename information"""
2838
2850
2839 opts = pycompat.byteskwargs(opts)
2851 opts = pycompat.byteskwargs(opts)
2840 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2852 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2841 m = scmutil.match(ctx, pats, opts)
2853 m = scmutil.match(ctx, pats, opts)
2842 for abs in ctx.walk(m):
2854 for abs in ctx.walk(m):
2843 fctx = ctx[abs]
2855 fctx = ctx[abs]
2844 o = fctx.filelog().renamed(fctx.filenode())
2856 o = fctx.filelog().renamed(fctx.filenode())
2845 rel = repo.pathto(abs)
2857 rel = repo.pathto(abs)
2846 if o:
2858 if o:
2847 ui.write(_(b"%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2859 ui.write(_(b"%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2848 else:
2860 else:
2849 ui.write(_(b"%s not renamed\n") % rel)
2861 ui.write(_(b"%s not renamed\n") % rel)
2850
2862
2851
2863
2852 @command(b'debugrequires|debugrequirements', [], b'')
2864 @command(b'debugrequires|debugrequirements', [], b'')
2853 def debugrequirements(ui, repo):
2865 def debugrequirements(ui, repo):
2854 """ print the current repo requirements """
2866 """ print the current repo requirements """
2855 for r in sorted(repo.requirements):
2867 for r in sorted(repo.requirements):
2856 ui.write(b"%s\n" % r)
2868 ui.write(b"%s\n" % r)
2857
2869
2858
2870
2859 @command(
2871 @command(
2860 b'debugrevlog',
2872 b'debugrevlog',
2861 cmdutil.debugrevlogopts + [(b'd', b'dump', False, _(b'dump index data'))],
2873 cmdutil.debugrevlogopts + [(b'd', b'dump', False, _(b'dump index data'))],
2862 _(b'-c|-m|FILE'),
2874 _(b'-c|-m|FILE'),
2863 optionalrepo=True,
2875 optionalrepo=True,
2864 )
2876 )
2865 def debugrevlog(ui, repo, file_=None, **opts):
2877 def debugrevlog(ui, repo, file_=None, **opts):
2866 """show data and statistics about a revlog"""
2878 """show data and statistics about a revlog"""
2867 opts = pycompat.byteskwargs(opts)
2879 opts = pycompat.byteskwargs(opts)
2868 r = cmdutil.openrevlog(repo, b'debugrevlog', file_, opts)
2880 r = cmdutil.openrevlog(repo, b'debugrevlog', file_, opts)
2869
2881
2870 if opts.get(b"dump"):
2882 if opts.get(b"dump"):
2871 numrevs = len(r)
2883 numrevs = len(r)
2872 ui.write(
2884 ui.write(
2873 (
2885 (
2874 b"# rev p1rev p2rev start end deltastart base p1 p2"
2886 b"# rev p1rev p2rev start end deltastart base p1 p2"
2875 b" rawsize totalsize compression heads chainlen\n"
2887 b" rawsize totalsize compression heads chainlen\n"
2876 )
2888 )
2877 )
2889 )
2878 ts = 0
2890 ts = 0
2879 heads = set()
2891 heads = set()
2880
2892
2881 for rev in pycompat.xrange(numrevs):
2893 for rev in pycompat.xrange(numrevs):
2882 dbase = r.deltaparent(rev)
2894 dbase = r.deltaparent(rev)
2883 if dbase == -1:
2895 if dbase == -1:
2884 dbase = rev
2896 dbase = rev
2885 cbase = r.chainbase(rev)
2897 cbase = r.chainbase(rev)
2886 clen = r.chainlen(rev)
2898 clen = r.chainlen(rev)
2887 p1, p2 = r.parentrevs(rev)
2899 p1, p2 = r.parentrevs(rev)
2888 rs = r.rawsize(rev)
2900 rs = r.rawsize(rev)
2889 ts = ts + rs
2901 ts = ts + rs
2890 heads -= set(r.parentrevs(rev))
2902 heads -= set(r.parentrevs(rev))
2891 heads.add(rev)
2903 heads.add(rev)
2892 try:
2904 try:
2893 compression = ts / r.end(rev)
2905 compression = ts / r.end(rev)
2894 except ZeroDivisionError:
2906 except ZeroDivisionError:
2895 compression = 0
2907 compression = 0
2896 ui.write(
2908 ui.write(
2897 b"%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2909 b"%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2898 b"%11d %5d %8d\n"
2910 b"%11d %5d %8d\n"
2899 % (
2911 % (
2900 rev,
2912 rev,
2901 p1,
2913 p1,
2902 p2,
2914 p2,
2903 r.start(rev),
2915 r.start(rev),
2904 r.end(rev),
2916 r.end(rev),
2905 r.start(dbase),
2917 r.start(dbase),
2906 r.start(cbase),
2918 r.start(cbase),
2907 r.start(p1),
2919 r.start(p1),
2908 r.start(p2),
2920 r.start(p2),
2909 rs,
2921 rs,
2910 ts,
2922 ts,
2911 compression,
2923 compression,
2912 len(heads),
2924 len(heads),
2913 clen,
2925 clen,
2914 )
2926 )
2915 )
2927 )
2916 return 0
2928 return 0
2917
2929
2918 v = r.version
2930 v = r.version
2919 format = v & 0xFFFF
2931 format = v & 0xFFFF
2920 flags = []
2932 flags = []
2921 gdelta = False
2933 gdelta = False
2922 if v & revlog.FLAG_INLINE_DATA:
2934 if v & revlog.FLAG_INLINE_DATA:
2923 flags.append(b'inline')
2935 flags.append(b'inline')
2924 if v & revlog.FLAG_GENERALDELTA:
2936 if v & revlog.FLAG_GENERALDELTA:
2925 gdelta = True
2937 gdelta = True
2926 flags.append(b'generaldelta')
2938 flags.append(b'generaldelta')
2927 if not flags:
2939 if not flags:
2928 flags = [b'(none)']
2940 flags = [b'(none)']
2929
2941
2930 ### tracks merge vs single parent
2942 ### tracks merge vs single parent
2931 nummerges = 0
2943 nummerges = 0
2932
2944
2933 ### tracks ways the "delta" are build
2945 ### tracks ways the "delta" are build
2934 # nodelta
2946 # nodelta
2935 numempty = 0
2947 numempty = 0
2936 numemptytext = 0
2948 numemptytext = 0
2937 numemptydelta = 0
2949 numemptydelta = 0
2938 # full file content
2950 # full file content
2939 numfull = 0
2951 numfull = 0
2940 # intermediate snapshot against a prior snapshot
2952 # intermediate snapshot against a prior snapshot
2941 numsemi = 0
2953 numsemi = 0
2942 # snapshot count per depth
2954 # snapshot count per depth
2943 numsnapdepth = collections.defaultdict(lambda: 0)
2955 numsnapdepth = collections.defaultdict(lambda: 0)
2944 # delta against previous revision
2956 # delta against previous revision
2945 numprev = 0
2957 numprev = 0
2946 # delta against first or second parent (not prev)
2958 # delta against first or second parent (not prev)
2947 nump1 = 0
2959 nump1 = 0
2948 nump2 = 0
2960 nump2 = 0
2949 # delta against neither prev nor parents
2961 # delta against neither prev nor parents
2950 numother = 0
2962 numother = 0
2951 # delta against prev that are also first or second parent
2963 # delta against prev that are also first or second parent
2952 # (details of `numprev`)
2964 # (details of `numprev`)
2953 nump1prev = 0
2965 nump1prev = 0
2954 nump2prev = 0
2966 nump2prev = 0
2955
2967
2956 # data about delta chain of each revs
2968 # data about delta chain of each revs
2957 chainlengths = []
2969 chainlengths = []
2958 chainbases = []
2970 chainbases = []
2959 chainspans = []
2971 chainspans = []
2960
2972
2961 # data about each revision
2973 # data about each revision
2962 datasize = [None, 0, 0]
2974 datasize = [None, 0, 0]
2963 fullsize = [None, 0, 0]
2975 fullsize = [None, 0, 0]
2964 semisize = [None, 0, 0]
2976 semisize = [None, 0, 0]
2965 # snapshot count per depth
2977 # snapshot count per depth
2966 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2978 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2967 deltasize = [None, 0, 0]
2979 deltasize = [None, 0, 0]
2968 chunktypecounts = {}
2980 chunktypecounts = {}
2969 chunktypesizes = {}
2981 chunktypesizes = {}
2970
2982
2971 def addsize(size, l):
2983 def addsize(size, l):
2972 if l[0] is None or size < l[0]:
2984 if l[0] is None or size < l[0]:
2973 l[0] = size
2985 l[0] = size
2974 if size > l[1]:
2986 if size > l[1]:
2975 l[1] = size
2987 l[1] = size
2976 l[2] += size
2988 l[2] += size
2977
2989
2978 numrevs = len(r)
2990 numrevs = len(r)
2979 for rev in pycompat.xrange(numrevs):
2991 for rev in pycompat.xrange(numrevs):
2980 p1, p2 = r.parentrevs(rev)
2992 p1, p2 = r.parentrevs(rev)
2981 delta = r.deltaparent(rev)
2993 delta = r.deltaparent(rev)
2982 if format > 0:
2994 if format > 0:
2983 addsize(r.rawsize(rev), datasize)
2995 addsize(r.rawsize(rev), datasize)
2984 if p2 != nullrev:
2996 if p2 != nullrev:
2985 nummerges += 1
2997 nummerges += 1
2986 size = r.length(rev)
2998 size = r.length(rev)
2987 if delta == nullrev:
2999 if delta == nullrev:
2988 chainlengths.append(0)
3000 chainlengths.append(0)
2989 chainbases.append(r.start(rev))
3001 chainbases.append(r.start(rev))
2990 chainspans.append(size)
3002 chainspans.append(size)
2991 if size == 0:
3003 if size == 0:
2992 numempty += 1
3004 numempty += 1
2993 numemptytext += 1
3005 numemptytext += 1
2994 else:
3006 else:
2995 numfull += 1
3007 numfull += 1
2996 numsnapdepth[0] += 1
3008 numsnapdepth[0] += 1
2997 addsize(size, fullsize)
3009 addsize(size, fullsize)
2998 addsize(size, snapsizedepth[0])
3010 addsize(size, snapsizedepth[0])
2999 else:
3011 else:
3000 chainlengths.append(chainlengths[delta] + 1)
3012 chainlengths.append(chainlengths[delta] + 1)
3001 baseaddr = chainbases[delta]
3013 baseaddr = chainbases[delta]
3002 revaddr = r.start(rev)
3014 revaddr = r.start(rev)
3003 chainbases.append(baseaddr)
3015 chainbases.append(baseaddr)
3004 chainspans.append((revaddr - baseaddr) + size)
3016 chainspans.append((revaddr - baseaddr) + size)
3005 if size == 0:
3017 if size == 0:
3006 numempty += 1
3018 numempty += 1
3007 numemptydelta += 1
3019 numemptydelta += 1
3008 elif r.issnapshot(rev):
3020 elif r.issnapshot(rev):
3009 addsize(size, semisize)
3021 addsize(size, semisize)
3010 numsemi += 1
3022 numsemi += 1
3011 depth = r.snapshotdepth(rev)
3023 depth = r.snapshotdepth(rev)
3012 numsnapdepth[depth] += 1
3024 numsnapdepth[depth] += 1
3013 addsize(size, snapsizedepth[depth])
3025 addsize(size, snapsizedepth[depth])
3014 else:
3026 else:
3015 addsize(size, deltasize)
3027 addsize(size, deltasize)
3016 if delta == rev - 1:
3028 if delta == rev - 1:
3017 numprev += 1
3029 numprev += 1
3018 if delta == p1:
3030 if delta == p1:
3019 nump1prev += 1
3031 nump1prev += 1
3020 elif delta == p2:
3032 elif delta == p2:
3021 nump2prev += 1
3033 nump2prev += 1
3022 elif delta == p1:
3034 elif delta == p1:
3023 nump1 += 1
3035 nump1 += 1
3024 elif delta == p2:
3036 elif delta == p2:
3025 nump2 += 1
3037 nump2 += 1
3026 elif delta != nullrev:
3038 elif delta != nullrev:
3027 numother += 1
3039 numother += 1
3028
3040
3029 # Obtain data on the raw chunks in the revlog.
3041 # Obtain data on the raw chunks in the revlog.
3030 if util.safehasattr(r, b'_getsegmentforrevs'):
3042 if util.safehasattr(r, b'_getsegmentforrevs'):
3031 segment = r._getsegmentforrevs(rev, rev)[1]
3043 segment = r._getsegmentforrevs(rev, rev)[1]
3032 else:
3044 else:
3033 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
3045 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
3034 if segment:
3046 if segment:
3035 chunktype = bytes(segment[0:1])
3047 chunktype = bytes(segment[0:1])
3036 else:
3048 else:
3037 chunktype = b'empty'
3049 chunktype = b'empty'
3038
3050
3039 if chunktype not in chunktypecounts:
3051 if chunktype not in chunktypecounts:
3040 chunktypecounts[chunktype] = 0
3052 chunktypecounts[chunktype] = 0
3041 chunktypesizes[chunktype] = 0
3053 chunktypesizes[chunktype] = 0
3042
3054
3043 chunktypecounts[chunktype] += 1
3055 chunktypecounts[chunktype] += 1
3044 chunktypesizes[chunktype] += size
3056 chunktypesizes[chunktype] += size
3045
3057
3046 # Adjust size min value for empty cases
3058 # Adjust size min value for empty cases
3047 for size in (datasize, fullsize, semisize, deltasize):
3059 for size in (datasize, fullsize, semisize, deltasize):
3048 if size[0] is None:
3060 if size[0] is None:
3049 size[0] = 0
3061 size[0] = 0
3050
3062
3051 numdeltas = numrevs - numfull - numempty - numsemi
3063 numdeltas = numrevs - numfull - numempty - numsemi
3052 numoprev = numprev - nump1prev - nump2prev
3064 numoprev = numprev - nump1prev - nump2prev
3053 totalrawsize = datasize[2]
3065 totalrawsize = datasize[2]
3054 datasize[2] /= numrevs
3066 datasize[2] /= numrevs
3055 fulltotal = fullsize[2]
3067 fulltotal = fullsize[2]
3056 if numfull == 0:
3068 if numfull == 0:
3057 fullsize[2] = 0
3069 fullsize[2] = 0
3058 else:
3070 else:
3059 fullsize[2] /= numfull
3071 fullsize[2] /= numfull
3060 semitotal = semisize[2]
3072 semitotal = semisize[2]
3061 snaptotal = {}
3073 snaptotal = {}
3062 if numsemi > 0:
3074 if numsemi > 0:
3063 semisize[2] /= numsemi
3075 semisize[2] /= numsemi
3064 for depth in snapsizedepth:
3076 for depth in snapsizedepth:
3065 snaptotal[depth] = snapsizedepth[depth][2]
3077 snaptotal[depth] = snapsizedepth[depth][2]
3066 snapsizedepth[depth][2] /= numsnapdepth[depth]
3078 snapsizedepth[depth][2] /= numsnapdepth[depth]
3067
3079
3068 deltatotal = deltasize[2]
3080 deltatotal = deltasize[2]
3069 if numdeltas > 0:
3081 if numdeltas > 0:
3070 deltasize[2] /= numdeltas
3082 deltasize[2] /= numdeltas
3071 totalsize = fulltotal + semitotal + deltatotal
3083 totalsize = fulltotal + semitotal + deltatotal
3072 avgchainlen = sum(chainlengths) / numrevs
3084 avgchainlen = sum(chainlengths) / numrevs
3073 maxchainlen = max(chainlengths)
3085 maxchainlen = max(chainlengths)
3074 maxchainspan = max(chainspans)
3086 maxchainspan = max(chainspans)
3075 compratio = 1
3087 compratio = 1
3076 if totalsize:
3088 if totalsize:
3077 compratio = totalrawsize / totalsize
3089 compratio = totalrawsize / totalsize
3078
3090
3079 basedfmtstr = b'%%%dd\n'
3091 basedfmtstr = b'%%%dd\n'
3080 basepcfmtstr = b'%%%dd %s(%%5.2f%%%%)\n'
3092 basepcfmtstr = b'%%%dd %s(%%5.2f%%%%)\n'
3081
3093
3082 def dfmtstr(max):
3094 def dfmtstr(max):
3083 return basedfmtstr % len(str(max))
3095 return basedfmtstr % len(str(max))
3084
3096
3085 def pcfmtstr(max, padding=0):
3097 def pcfmtstr(max, padding=0):
3086 return basepcfmtstr % (len(str(max)), b' ' * padding)
3098 return basepcfmtstr % (len(str(max)), b' ' * padding)
3087
3099
3088 def pcfmt(value, total):
3100 def pcfmt(value, total):
3089 if total:
3101 if total:
3090 return (value, 100 * float(value) / total)
3102 return (value, 100 * float(value) / total)
3091 else:
3103 else:
3092 return value, 100.0
3104 return value, 100.0
3093
3105
3094 ui.writenoi18n(b'format : %d\n' % format)
3106 ui.writenoi18n(b'format : %d\n' % format)
3095 ui.writenoi18n(b'flags : %s\n' % b', '.join(flags))
3107 ui.writenoi18n(b'flags : %s\n' % b', '.join(flags))
3096
3108
3097 ui.write(b'\n')
3109 ui.write(b'\n')
3098 fmt = pcfmtstr(totalsize)
3110 fmt = pcfmtstr(totalsize)
3099 fmt2 = dfmtstr(totalsize)
3111 fmt2 = dfmtstr(totalsize)
3100 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
3112 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
3101 ui.writenoi18n(b' merges : ' + fmt % pcfmt(nummerges, numrevs))
3113 ui.writenoi18n(b' merges : ' + fmt % pcfmt(nummerges, numrevs))
3102 ui.writenoi18n(
3114 ui.writenoi18n(
3103 b' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs)
3115 b' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs)
3104 )
3116 )
3105 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
3117 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
3106 ui.writenoi18n(b' empty : ' + fmt % pcfmt(numempty, numrevs))
3118 ui.writenoi18n(b' empty : ' + fmt % pcfmt(numempty, numrevs))
3107 ui.writenoi18n(
3119 ui.writenoi18n(
3108 b' text : '
3120 b' text : '
3109 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta)
3121 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta)
3110 )
3122 )
3111 ui.writenoi18n(
3123 ui.writenoi18n(
3112 b' delta : '
3124 b' delta : '
3113 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta)
3125 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta)
3114 )
3126 )
3115 ui.writenoi18n(
3127 ui.writenoi18n(
3116 b' snapshot : ' + fmt % pcfmt(numfull + numsemi, numrevs)
3128 b' snapshot : ' + fmt % pcfmt(numfull + numsemi, numrevs)
3117 )
3129 )
3118 for depth in sorted(numsnapdepth):
3130 for depth in sorted(numsnapdepth):
3119 ui.write(
3131 ui.write(
3120 (b' lvl-%-3d : ' % depth)
3132 (b' lvl-%-3d : ' % depth)
3121 + fmt % pcfmt(numsnapdepth[depth], numrevs)
3133 + fmt % pcfmt(numsnapdepth[depth], numrevs)
3122 )
3134 )
3123 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
3135 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
3124 ui.writenoi18n(b'revision size : ' + fmt2 % totalsize)
3136 ui.writenoi18n(b'revision size : ' + fmt2 % totalsize)
3125 ui.writenoi18n(
3137 ui.writenoi18n(
3126 b' snapshot : ' + fmt % pcfmt(fulltotal + semitotal, totalsize)
3138 b' snapshot : ' + fmt % pcfmt(fulltotal + semitotal, totalsize)
3127 )
3139 )
3128 for depth in sorted(numsnapdepth):
3140 for depth in sorted(numsnapdepth):
3129 ui.write(
3141 ui.write(
3130 (b' lvl-%-3d : ' % depth)
3142 (b' lvl-%-3d : ' % depth)
3131 + fmt % pcfmt(snaptotal[depth], totalsize)
3143 + fmt % pcfmt(snaptotal[depth], totalsize)
3132 )
3144 )
3133 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
3145 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
3134
3146
3135 def fmtchunktype(chunktype):
3147 def fmtchunktype(chunktype):
3136 if chunktype == b'empty':
3148 if chunktype == b'empty':
3137 return b' %s : ' % chunktype
3149 return b' %s : ' % chunktype
3138 elif chunktype in pycompat.bytestr(string.ascii_letters):
3150 elif chunktype in pycompat.bytestr(string.ascii_letters):
3139 return b' 0x%s (%s) : ' % (hex(chunktype), chunktype)
3151 return b' 0x%s (%s) : ' % (hex(chunktype), chunktype)
3140 else:
3152 else:
3141 return b' 0x%s : ' % hex(chunktype)
3153 return b' 0x%s : ' % hex(chunktype)
3142
3154
3143 ui.write(b'\n')
3155 ui.write(b'\n')
3144 ui.writenoi18n(b'chunks : ' + fmt2 % numrevs)
3156 ui.writenoi18n(b'chunks : ' + fmt2 % numrevs)
3145 for chunktype in sorted(chunktypecounts):
3157 for chunktype in sorted(chunktypecounts):
3146 ui.write(fmtchunktype(chunktype))
3158 ui.write(fmtchunktype(chunktype))
3147 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
3159 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
3148 ui.writenoi18n(b'chunks size : ' + fmt2 % totalsize)
3160 ui.writenoi18n(b'chunks size : ' + fmt2 % totalsize)
3149 for chunktype in sorted(chunktypecounts):
3161 for chunktype in sorted(chunktypecounts):
3150 ui.write(fmtchunktype(chunktype))
3162 ui.write(fmtchunktype(chunktype))
3151 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
3163 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
3152
3164
3153 ui.write(b'\n')
3165 ui.write(b'\n')
3154 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
3166 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
3155 ui.writenoi18n(b'avg chain length : ' + fmt % avgchainlen)
3167 ui.writenoi18n(b'avg chain length : ' + fmt % avgchainlen)
3156 ui.writenoi18n(b'max chain length : ' + fmt % maxchainlen)
3168 ui.writenoi18n(b'max chain length : ' + fmt % maxchainlen)
3157 ui.writenoi18n(b'max chain reach : ' + fmt % maxchainspan)
3169 ui.writenoi18n(b'max chain reach : ' + fmt % maxchainspan)
3158 ui.writenoi18n(b'compression ratio : ' + fmt % compratio)
3170 ui.writenoi18n(b'compression ratio : ' + fmt % compratio)
3159
3171
3160 if format > 0:
3172 if format > 0:
3161 ui.write(b'\n')
3173 ui.write(b'\n')
3162 ui.writenoi18n(
3174 ui.writenoi18n(
3163 b'uncompressed data size (min/max/avg) : %d / %d / %d\n'
3175 b'uncompressed data size (min/max/avg) : %d / %d / %d\n'
3164 % tuple(datasize)
3176 % tuple(datasize)
3165 )
3177 )
3166 ui.writenoi18n(
3178 ui.writenoi18n(
3167 b'full revision size (min/max/avg) : %d / %d / %d\n'
3179 b'full revision size (min/max/avg) : %d / %d / %d\n'
3168 % tuple(fullsize)
3180 % tuple(fullsize)
3169 )
3181 )
3170 ui.writenoi18n(
3182 ui.writenoi18n(
3171 b'inter-snapshot size (min/max/avg) : %d / %d / %d\n'
3183 b'inter-snapshot size (min/max/avg) : %d / %d / %d\n'
3172 % tuple(semisize)
3184 % tuple(semisize)
3173 )
3185 )
3174 for depth in sorted(snapsizedepth):
3186 for depth in sorted(snapsizedepth):
3175 if depth == 0:
3187 if depth == 0:
3176 continue
3188 continue
3177 ui.writenoi18n(
3189 ui.writenoi18n(
3178 b' level-%-3d (min/max/avg) : %d / %d / %d\n'
3190 b' level-%-3d (min/max/avg) : %d / %d / %d\n'
3179 % ((depth,) + tuple(snapsizedepth[depth]))
3191 % ((depth,) + tuple(snapsizedepth[depth]))
3180 )
3192 )
3181 ui.writenoi18n(
3193 ui.writenoi18n(
3182 b'delta size (min/max/avg) : %d / %d / %d\n'
3194 b'delta size (min/max/avg) : %d / %d / %d\n'
3183 % tuple(deltasize)
3195 % tuple(deltasize)
3184 )
3196 )
3185
3197
3186 if numdeltas > 0:
3198 if numdeltas > 0:
3187 ui.write(b'\n')
3199 ui.write(b'\n')
3188 fmt = pcfmtstr(numdeltas)
3200 fmt = pcfmtstr(numdeltas)
3189 fmt2 = pcfmtstr(numdeltas, 4)
3201 fmt2 = pcfmtstr(numdeltas, 4)
3190 ui.writenoi18n(
3202 ui.writenoi18n(
3191 b'deltas against prev : ' + fmt % pcfmt(numprev, numdeltas)
3203 b'deltas against prev : ' + fmt % pcfmt(numprev, numdeltas)
3192 )
3204 )
3193 if numprev > 0:
3205 if numprev > 0:
3194 ui.writenoi18n(
3206 ui.writenoi18n(
3195 b' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev)
3207 b' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev)
3196 )
3208 )
3197 ui.writenoi18n(
3209 ui.writenoi18n(
3198 b' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev)
3210 b' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev)
3199 )
3211 )
3200 ui.writenoi18n(
3212 ui.writenoi18n(
3201 b' other : ' + fmt2 % pcfmt(numoprev, numprev)
3213 b' other : ' + fmt2 % pcfmt(numoprev, numprev)
3202 )
3214 )
3203 if gdelta:
3215 if gdelta:
3204 ui.writenoi18n(
3216 ui.writenoi18n(
3205 b'deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas)
3217 b'deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas)
3206 )
3218 )
3207 ui.writenoi18n(
3219 ui.writenoi18n(
3208 b'deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas)
3220 b'deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas)
3209 )
3221 )
3210 ui.writenoi18n(
3222 ui.writenoi18n(
3211 b'deltas against other : ' + fmt % pcfmt(numother, numdeltas)
3223 b'deltas against other : ' + fmt % pcfmt(numother, numdeltas)
3212 )
3224 )
3213
3225
3214
3226
3215 @command(
3227 @command(
3216 b'debugrevlogindex',
3228 b'debugrevlogindex',
3217 cmdutil.debugrevlogopts
3229 cmdutil.debugrevlogopts
3218 + [(b'f', b'format', 0, _(b'revlog format'), _(b'FORMAT'))],
3230 + [(b'f', b'format', 0, _(b'revlog format'), _(b'FORMAT'))],
3219 _(b'[-f FORMAT] -c|-m|FILE'),
3231 _(b'[-f FORMAT] -c|-m|FILE'),
3220 optionalrepo=True,
3232 optionalrepo=True,
3221 )
3233 )
3222 def debugrevlogindex(ui, repo, file_=None, **opts):
3234 def debugrevlogindex(ui, repo, file_=None, **opts):
3223 """dump the contents of a revlog index"""
3235 """dump the contents of a revlog index"""
3224 opts = pycompat.byteskwargs(opts)
3236 opts = pycompat.byteskwargs(opts)
3225 r = cmdutil.openrevlog(repo, b'debugrevlogindex', file_, opts)
3237 r = cmdutil.openrevlog(repo, b'debugrevlogindex', file_, opts)
3226 format = opts.get(b'format', 0)
3238 format = opts.get(b'format', 0)
3227 if format not in (0, 1):
3239 if format not in (0, 1):
3228 raise error.Abort(_(b"unknown format %d") % format)
3240 raise error.Abort(_(b"unknown format %d") % format)
3229
3241
3230 if ui.debugflag:
3242 if ui.debugflag:
3231 shortfn = hex
3243 shortfn = hex
3232 else:
3244 else:
3233 shortfn = short
3245 shortfn = short
3234
3246
3235 # There might not be anything in r, so have a sane default
3247 # There might not be anything in r, so have a sane default
3236 idlen = 12
3248 idlen = 12
3237 for i in r:
3249 for i in r:
3238 idlen = len(shortfn(r.node(i)))
3250 idlen = len(shortfn(r.node(i)))
3239 break
3251 break
3240
3252
3241 if format == 0:
3253 if format == 0:
3242 if ui.verbose:
3254 if ui.verbose:
3243 ui.writenoi18n(
3255 ui.writenoi18n(
3244 b" rev offset length linkrev %s %s p2\n"
3256 b" rev offset length linkrev %s %s p2\n"
3245 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3257 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3246 )
3258 )
3247 else:
3259 else:
3248 ui.writenoi18n(
3260 ui.writenoi18n(
3249 b" rev linkrev %s %s p2\n"
3261 b" rev linkrev %s %s p2\n"
3250 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3262 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3251 )
3263 )
3252 elif format == 1:
3264 elif format == 1:
3253 if ui.verbose:
3265 if ui.verbose:
3254 ui.writenoi18n(
3266 ui.writenoi18n(
3255 (
3267 (
3256 b" rev flag offset length size link p1"
3268 b" rev flag offset length size link p1"
3257 b" p2 %s\n"
3269 b" p2 %s\n"
3258 )
3270 )
3259 % b"nodeid".rjust(idlen)
3271 % b"nodeid".rjust(idlen)
3260 )
3272 )
3261 else:
3273 else:
3262 ui.writenoi18n(
3274 ui.writenoi18n(
3263 b" rev flag size link p1 p2 %s\n"
3275 b" rev flag size link p1 p2 %s\n"
3264 % b"nodeid".rjust(idlen)
3276 % b"nodeid".rjust(idlen)
3265 )
3277 )
3266
3278
3267 for i in r:
3279 for i in r:
3268 node = r.node(i)
3280 node = r.node(i)
3269 if format == 0:
3281 if format == 0:
3270 try:
3282 try:
3271 pp = r.parents(node)
3283 pp = r.parents(node)
3272 except Exception:
3284 except Exception:
3273 pp = [nullid, nullid]
3285 pp = [nullid, nullid]
3274 if ui.verbose:
3286 if ui.verbose:
3275 ui.write(
3287 ui.write(
3276 b"% 6d % 9d % 7d % 7d %s %s %s\n"
3288 b"% 6d % 9d % 7d % 7d %s %s %s\n"
3277 % (
3289 % (
3278 i,
3290 i,
3279 r.start(i),
3291 r.start(i),
3280 r.length(i),
3292 r.length(i),
3281 r.linkrev(i),
3293 r.linkrev(i),
3282 shortfn(node),
3294 shortfn(node),
3283 shortfn(pp[0]),
3295 shortfn(pp[0]),
3284 shortfn(pp[1]),
3296 shortfn(pp[1]),
3285 )
3297 )
3286 )
3298 )
3287 else:
3299 else:
3288 ui.write(
3300 ui.write(
3289 b"% 6d % 7d %s %s %s\n"
3301 b"% 6d % 7d %s %s %s\n"
3290 % (
3302 % (
3291 i,
3303 i,
3292 r.linkrev(i),
3304 r.linkrev(i),
3293 shortfn(node),
3305 shortfn(node),
3294 shortfn(pp[0]),
3306 shortfn(pp[0]),
3295 shortfn(pp[1]),
3307 shortfn(pp[1]),
3296 )
3308 )
3297 )
3309 )
3298 elif format == 1:
3310 elif format == 1:
3299 pr = r.parentrevs(i)
3311 pr = r.parentrevs(i)
3300 if ui.verbose:
3312 if ui.verbose:
3301 ui.write(
3313 ui.write(
3302 b"% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n"
3314 b"% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n"
3303 % (
3315 % (
3304 i,
3316 i,
3305 r.flags(i),
3317 r.flags(i),
3306 r.start(i),
3318 r.start(i),
3307 r.length(i),
3319 r.length(i),
3308 r.rawsize(i),
3320 r.rawsize(i),
3309 r.linkrev(i),
3321 r.linkrev(i),
3310 pr[0],
3322 pr[0],
3311 pr[1],
3323 pr[1],
3312 shortfn(node),
3324 shortfn(node),
3313 )
3325 )
3314 )
3326 )
3315 else:
3327 else:
3316 ui.write(
3328 ui.write(
3317 b"% 6d %04x % 8d % 6d % 6d % 6d %s\n"
3329 b"% 6d %04x % 8d % 6d % 6d % 6d %s\n"
3318 % (
3330 % (
3319 i,
3331 i,
3320 r.flags(i),
3332 r.flags(i),
3321 r.rawsize(i),
3333 r.rawsize(i),
3322 r.linkrev(i),
3334 r.linkrev(i),
3323 pr[0],
3335 pr[0],
3324 pr[1],
3336 pr[1],
3325 shortfn(node),
3337 shortfn(node),
3326 )
3338 )
3327 )
3339 )
3328
3340
3329
3341
3330 @command(
3342 @command(
3331 b'debugrevspec',
3343 b'debugrevspec',
3332 [
3344 [
3333 (
3345 (
3334 b'',
3346 b'',
3335 b'optimize',
3347 b'optimize',
3336 None,
3348 None,
3337 _(b'print parsed tree after optimizing (DEPRECATED)'),
3349 _(b'print parsed tree after optimizing (DEPRECATED)'),
3338 ),
3350 ),
3339 (
3351 (
3340 b'',
3352 b'',
3341 b'show-revs',
3353 b'show-revs',
3342 True,
3354 True,
3343 _(b'print list of result revisions (default)'),
3355 _(b'print list of result revisions (default)'),
3344 ),
3356 ),
3345 (
3357 (
3346 b's',
3358 b's',
3347 b'show-set',
3359 b'show-set',
3348 None,
3360 None,
3349 _(b'print internal representation of result set'),
3361 _(b'print internal representation of result set'),
3350 ),
3362 ),
3351 (
3363 (
3352 b'p',
3364 b'p',
3353 b'show-stage',
3365 b'show-stage',
3354 [],
3366 [],
3355 _(b'print parsed tree at the given stage'),
3367 _(b'print parsed tree at the given stage'),
3356 _(b'NAME'),
3368 _(b'NAME'),
3357 ),
3369 ),
3358 (b'', b'no-optimized', False, _(b'evaluate tree without optimization')),
3370 (b'', b'no-optimized', False, _(b'evaluate tree without optimization')),
3359 (b'', b'verify-optimized', False, _(b'verify optimized result')),
3371 (b'', b'verify-optimized', False, _(b'verify optimized result')),
3360 ],
3372 ],
3361 b'REVSPEC',
3373 b'REVSPEC',
3362 )
3374 )
3363 def debugrevspec(ui, repo, expr, **opts):
3375 def debugrevspec(ui, repo, expr, **opts):
3364 """parse and apply a revision specification
3376 """parse and apply a revision specification
3365
3377
3366 Use -p/--show-stage option to print the parsed tree at the given stages.
3378 Use -p/--show-stage option to print the parsed tree at the given stages.
3367 Use -p all to print tree at every stage.
3379 Use -p all to print tree at every stage.
3368
3380
3369 Use --no-show-revs option with -s or -p to print only the set
3381 Use --no-show-revs option with -s or -p to print only the set
3370 representation or the parsed tree respectively.
3382 representation or the parsed tree respectively.
3371
3383
3372 Use --verify-optimized to compare the optimized result with the unoptimized
3384 Use --verify-optimized to compare the optimized result with the unoptimized
3373 one. Returns 1 if the optimized result differs.
3385 one. Returns 1 if the optimized result differs.
3374 """
3386 """
3375 opts = pycompat.byteskwargs(opts)
3387 opts = pycompat.byteskwargs(opts)
3376 aliases = ui.configitems(b'revsetalias')
3388 aliases = ui.configitems(b'revsetalias')
3377 stages = [
3389 stages = [
3378 (b'parsed', lambda tree: tree),
3390 (b'parsed', lambda tree: tree),
3379 (
3391 (
3380 b'expanded',
3392 b'expanded',
3381 lambda tree: revsetlang.expandaliases(tree, aliases, ui.warn),
3393 lambda tree: revsetlang.expandaliases(tree, aliases, ui.warn),
3382 ),
3394 ),
3383 (b'concatenated', revsetlang.foldconcat),
3395 (b'concatenated', revsetlang.foldconcat),
3384 (b'analyzed', revsetlang.analyze),
3396 (b'analyzed', revsetlang.analyze),
3385 (b'optimized', revsetlang.optimize),
3397 (b'optimized', revsetlang.optimize),
3386 ]
3398 ]
3387 if opts[b'no_optimized']:
3399 if opts[b'no_optimized']:
3388 stages = stages[:-1]
3400 stages = stages[:-1]
3389 if opts[b'verify_optimized'] and opts[b'no_optimized']:
3401 if opts[b'verify_optimized'] and opts[b'no_optimized']:
3390 raise error.Abort(
3402 raise error.Abort(
3391 _(b'cannot use --verify-optimized with --no-optimized')
3403 _(b'cannot use --verify-optimized with --no-optimized')
3392 )
3404 )
3393 stagenames = {n for n, f in stages}
3405 stagenames = {n for n, f in stages}
3394
3406
3395 showalways = set()
3407 showalways = set()
3396 showchanged = set()
3408 showchanged = set()
3397 if ui.verbose and not opts[b'show_stage']:
3409 if ui.verbose and not opts[b'show_stage']:
3398 # show parsed tree by --verbose (deprecated)
3410 # show parsed tree by --verbose (deprecated)
3399 showalways.add(b'parsed')
3411 showalways.add(b'parsed')
3400 showchanged.update([b'expanded', b'concatenated'])
3412 showchanged.update([b'expanded', b'concatenated'])
3401 if opts[b'optimize']:
3413 if opts[b'optimize']:
3402 showalways.add(b'optimized')
3414 showalways.add(b'optimized')
3403 if opts[b'show_stage'] and opts[b'optimize']:
3415 if opts[b'show_stage'] and opts[b'optimize']:
3404 raise error.Abort(_(b'cannot use --optimize with --show-stage'))
3416 raise error.Abort(_(b'cannot use --optimize with --show-stage'))
3405 if opts[b'show_stage'] == [b'all']:
3417 if opts[b'show_stage'] == [b'all']:
3406 showalways.update(stagenames)
3418 showalways.update(stagenames)
3407 else:
3419 else:
3408 for n in opts[b'show_stage']:
3420 for n in opts[b'show_stage']:
3409 if n not in stagenames:
3421 if n not in stagenames:
3410 raise error.Abort(_(b'invalid stage name: %s') % n)
3422 raise error.Abort(_(b'invalid stage name: %s') % n)
3411 showalways.update(opts[b'show_stage'])
3423 showalways.update(opts[b'show_stage'])
3412
3424
3413 treebystage = {}
3425 treebystage = {}
3414 printedtree = None
3426 printedtree = None
3415 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
3427 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
3416 for n, f in stages:
3428 for n, f in stages:
3417 treebystage[n] = tree = f(tree)
3429 treebystage[n] = tree = f(tree)
3418 if n in showalways or (n in showchanged and tree != printedtree):
3430 if n in showalways or (n in showchanged and tree != printedtree):
3419 if opts[b'show_stage'] or n != b'parsed':
3431 if opts[b'show_stage'] or n != b'parsed':
3420 ui.write(b"* %s:\n" % n)
3432 ui.write(b"* %s:\n" % n)
3421 ui.write(revsetlang.prettyformat(tree), b"\n")
3433 ui.write(revsetlang.prettyformat(tree), b"\n")
3422 printedtree = tree
3434 printedtree = tree
3423
3435
3424 if opts[b'verify_optimized']:
3436 if opts[b'verify_optimized']:
3425 arevs = revset.makematcher(treebystage[b'analyzed'])(repo)
3437 arevs = revset.makematcher(treebystage[b'analyzed'])(repo)
3426 brevs = revset.makematcher(treebystage[b'optimized'])(repo)
3438 brevs = revset.makematcher(treebystage[b'optimized'])(repo)
3427 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3439 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3428 ui.writenoi18n(
3440 ui.writenoi18n(
3429 b"* analyzed set:\n", stringutil.prettyrepr(arevs), b"\n"
3441 b"* analyzed set:\n", stringutil.prettyrepr(arevs), b"\n"
3430 )
3442 )
3431 ui.writenoi18n(
3443 ui.writenoi18n(
3432 b"* optimized set:\n", stringutil.prettyrepr(brevs), b"\n"
3444 b"* optimized set:\n", stringutil.prettyrepr(brevs), b"\n"
3433 )
3445 )
3434 arevs = list(arevs)
3446 arevs = list(arevs)
3435 brevs = list(brevs)
3447 brevs = list(brevs)
3436 if arevs == brevs:
3448 if arevs == brevs:
3437 return 0
3449 return 0
3438 ui.writenoi18n(b'--- analyzed\n', label=b'diff.file_a')
3450 ui.writenoi18n(b'--- analyzed\n', label=b'diff.file_a')
3439 ui.writenoi18n(b'+++ optimized\n', label=b'diff.file_b')
3451 ui.writenoi18n(b'+++ optimized\n', label=b'diff.file_b')
3440 sm = difflib.SequenceMatcher(None, arevs, brevs)
3452 sm = difflib.SequenceMatcher(None, arevs, brevs)
3441 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3453 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3442 if tag in ('delete', 'replace'):
3454 if tag in ('delete', 'replace'):
3443 for c in arevs[alo:ahi]:
3455 for c in arevs[alo:ahi]:
3444 ui.write(b'-%d\n' % c, label=b'diff.deleted')
3456 ui.write(b'-%d\n' % c, label=b'diff.deleted')
3445 if tag in ('insert', 'replace'):
3457 if tag in ('insert', 'replace'):
3446 for c in brevs[blo:bhi]:
3458 for c in brevs[blo:bhi]:
3447 ui.write(b'+%d\n' % c, label=b'diff.inserted')
3459 ui.write(b'+%d\n' % c, label=b'diff.inserted')
3448 if tag == 'equal':
3460 if tag == 'equal':
3449 for c in arevs[alo:ahi]:
3461 for c in arevs[alo:ahi]:
3450 ui.write(b' %d\n' % c)
3462 ui.write(b' %d\n' % c)
3451 return 1
3463 return 1
3452
3464
3453 func = revset.makematcher(tree)
3465 func = revset.makematcher(tree)
3454 revs = func(repo)
3466 revs = func(repo)
3455 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3467 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3456 ui.writenoi18n(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
3468 ui.writenoi18n(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
3457 if not opts[b'show_revs']:
3469 if not opts[b'show_revs']:
3458 return
3470 return
3459 for c in revs:
3471 for c in revs:
3460 ui.write(b"%d\n" % c)
3472 ui.write(b"%d\n" % c)
3461
3473
3462
3474
3463 @command(
3475 @command(
3464 b'debugserve',
3476 b'debugserve',
3465 [
3477 [
3466 (
3478 (
3467 b'',
3479 b'',
3468 b'sshstdio',
3480 b'sshstdio',
3469 False,
3481 False,
3470 _(b'run an SSH server bound to process handles'),
3482 _(b'run an SSH server bound to process handles'),
3471 ),
3483 ),
3472 (b'', b'logiofd', b'', _(b'file descriptor to log server I/O to')),
3484 (b'', b'logiofd', b'', _(b'file descriptor to log server I/O to')),
3473 (b'', b'logiofile', b'', _(b'file to log server I/O to')),
3485 (b'', b'logiofile', b'', _(b'file to log server I/O to')),
3474 ],
3486 ],
3475 b'',
3487 b'',
3476 )
3488 )
3477 def debugserve(ui, repo, **opts):
3489 def debugserve(ui, repo, **opts):
3478 """run a server with advanced settings
3490 """run a server with advanced settings
3479
3491
3480 This command is similar to :hg:`serve`. It exists partially as a
3492 This command is similar to :hg:`serve`. It exists partially as a
3481 workaround to the fact that ``hg serve --stdio`` must have specific
3493 workaround to the fact that ``hg serve --stdio`` must have specific
3482 arguments for security reasons.
3494 arguments for security reasons.
3483 """
3495 """
3484 opts = pycompat.byteskwargs(opts)
3496 opts = pycompat.byteskwargs(opts)
3485
3497
3486 if not opts[b'sshstdio']:
3498 if not opts[b'sshstdio']:
3487 raise error.Abort(_(b'only --sshstdio is currently supported'))
3499 raise error.Abort(_(b'only --sshstdio is currently supported'))
3488
3500
3489 logfh = None
3501 logfh = None
3490
3502
3491 if opts[b'logiofd'] and opts[b'logiofile']:
3503 if opts[b'logiofd'] and opts[b'logiofile']:
3492 raise error.Abort(_(b'cannot use both --logiofd and --logiofile'))
3504 raise error.Abort(_(b'cannot use both --logiofd and --logiofile'))
3493
3505
3494 if opts[b'logiofd']:
3506 if opts[b'logiofd']:
3495 # Ideally we would be line buffered. But line buffering in binary
3507 # Ideally we would be line buffered. But line buffering in binary
3496 # mode isn't supported and emits a warning in Python 3.8+. Disabling
3508 # mode isn't supported and emits a warning in Python 3.8+. Disabling
3497 # buffering could have performance impacts. But since this isn't
3509 # buffering could have performance impacts. But since this isn't
3498 # performance critical code, it should be fine.
3510 # performance critical code, it should be fine.
3499 try:
3511 try:
3500 logfh = os.fdopen(int(opts[b'logiofd']), 'ab', 0)
3512 logfh = os.fdopen(int(opts[b'logiofd']), 'ab', 0)
3501 except OSError as e:
3513 except OSError as e:
3502 if e.errno != errno.ESPIPE:
3514 if e.errno != errno.ESPIPE:
3503 raise
3515 raise
3504 # can't seek a pipe, so `ab` mode fails on py3
3516 # can't seek a pipe, so `ab` mode fails on py3
3505 logfh = os.fdopen(int(opts[b'logiofd']), 'wb', 0)
3517 logfh = os.fdopen(int(opts[b'logiofd']), 'wb', 0)
3506 elif opts[b'logiofile']:
3518 elif opts[b'logiofile']:
3507 logfh = open(opts[b'logiofile'], b'ab', 0)
3519 logfh = open(opts[b'logiofile'], b'ab', 0)
3508
3520
3509 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
3521 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
3510 s.serve_forever()
3522 s.serve_forever()
3511
3523
3512
3524
3513 @command(b'debugsetparents', [], _(b'REV1 [REV2]'))
3525 @command(b'debugsetparents', [], _(b'REV1 [REV2]'))
3514 def debugsetparents(ui, repo, rev1, rev2=None):
3526 def debugsetparents(ui, repo, rev1, rev2=None):
3515 """manually set the parents of the current working directory (DANGEROUS)
3527 """manually set the parents of the current working directory (DANGEROUS)
3516
3528
3517 This command is not what you are looking for and should not be used. Using
3529 This command is not what you are looking for and should not be used. Using
3518 this command will most certainly results in slight corruption of the file
3530 this command will most certainly results in slight corruption of the file
3519 level histories withing your repository. DO NOT USE THIS COMMAND.
3531 level histories withing your repository. DO NOT USE THIS COMMAND.
3520
3532
3521 The command update the p1 and p2 field in the dirstate, and not touching
3533 The command update the p1 and p2 field in the dirstate, and not touching
3522 anything else. This useful for writing repository conversion tools, but
3534 anything else. This useful for writing repository conversion tools, but
3523 should be used with extreme care. For example, neither the working
3535 should be used with extreme care. For example, neither the working
3524 directory nor the dirstate is updated, so file status may be incorrect
3536 directory nor the dirstate is updated, so file status may be incorrect
3525 after running this command. Only used if you are one of the few people that
3537 after running this command. Only used if you are one of the few people that
3526 deeply unstand both conversion tools and file level histories. If you are
3538 deeply unstand both conversion tools and file level histories. If you are
3527 reading this help, you are not one of this people (most of them sailed west
3539 reading this help, you are not one of this people (most of them sailed west
3528 from Mithlond anyway.
3540 from Mithlond anyway.
3529
3541
3530 So one last time DO NOT USE THIS COMMAND.
3542 So one last time DO NOT USE THIS COMMAND.
3531
3543
3532 Returns 0 on success.
3544 Returns 0 on success.
3533 """
3545 """
3534
3546
3535 node1 = scmutil.revsingle(repo, rev1).node()
3547 node1 = scmutil.revsingle(repo, rev1).node()
3536 node2 = scmutil.revsingle(repo, rev2, b'null').node()
3548 node2 = scmutil.revsingle(repo, rev2, b'null').node()
3537
3549
3538 with repo.wlock():
3550 with repo.wlock():
3539 repo.setparents(node1, node2)
3551 repo.setparents(node1, node2)
3540
3552
3541
3553
3542 @command(b'debugsidedata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
3554 @command(b'debugsidedata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
3543 def debugsidedata(ui, repo, file_, rev=None, **opts):
3555 def debugsidedata(ui, repo, file_, rev=None, **opts):
3544 """dump the side data for a cl/manifest/file revision
3556 """dump the side data for a cl/manifest/file revision
3545
3557
3546 Use --verbose to dump the sidedata content."""
3558 Use --verbose to dump the sidedata content."""
3547 opts = pycompat.byteskwargs(opts)
3559 opts = pycompat.byteskwargs(opts)
3548 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
3560 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
3549 if rev is not None:
3561 if rev is not None:
3550 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3562 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3551 file_, rev = None, file_
3563 file_, rev = None, file_
3552 elif rev is None:
3564 elif rev is None:
3553 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3565 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3554 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
3566 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
3555 r = getattr(r, '_revlog', r)
3567 r = getattr(r, '_revlog', r)
3556 try:
3568 try:
3557 sidedata = r.sidedata(r.lookup(rev))
3569 sidedata = r.sidedata(r.lookup(rev))
3558 except KeyError:
3570 except KeyError:
3559 raise error.Abort(_(b'invalid revision identifier %s') % rev)
3571 raise error.Abort(_(b'invalid revision identifier %s') % rev)
3560 if sidedata:
3572 if sidedata:
3561 sidedata = list(sidedata.items())
3573 sidedata = list(sidedata.items())
3562 sidedata.sort()
3574 sidedata.sort()
3563 ui.writenoi18n(b'%d sidedata entries\n' % len(sidedata))
3575 ui.writenoi18n(b'%d sidedata entries\n' % len(sidedata))
3564 for key, value in sidedata:
3576 for key, value in sidedata:
3565 ui.writenoi18n(b' entry-%04o size %d\n' % (key, len(value)))
3577 ui.writenoi18n(b' entry-%04o size %d\n' % (key, len(value)))
3566 if ui.verbose:
3578 if ui.verbose:
3567 ui.writenoi18n(b' %s\n' % stringutil.pprint(value))
3579 ui.writenoi18n(b' %s\n' % stringutil.pprint(value))
3568
3580
3569
3581
3570 @command(b'debugssl', [], b'[SOURCE]', optionalrepo=True)
3582 @command(b'debugssl', [], b'[SOURCE]', optionalrepo=True)
3571 def debugssl(ui, repo, source=None, **opts):
3583 def debugssl(ui, repo, source=None, **opts):
3572 """test a secure connection to a server
3584 """test a secure connection to a server
3573
3585
3574 This builds the certificate chain for the server on Windows, installing the
3586 This builds the certificate chain for the server on Windows, installing the
3575 missing intermediates and trusted root via Windows Update if necessary. It
3587 missing intermediates and trusted root via Windows Update if necessary. It
3576 does nothing on other platforms.
3588 does nothing on other platforms.
3577
3589
3578 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
3590 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
3579 that server is used. See :hg:`help urls` for more information.
3591 that server is used. See :hg:`help urls` for more information.
3580
3592
3581 If the update succeeds, retry the original operation. Otherwise, the cause
3593 If the update succeeds, retry the original operation. Otherwise, the cause
3582 of the SSL error is likely another issue.
3594 of the SSL error is likely another issue.
3583 """
3595 """
3584 if not pycompat.iswindows:
3596 if not pycompat.iswindows:
3585 raise error.Abort(
3597 raise error.Abort(
3586 _(b'certificate chain building is only possible on Windows')
3598 _(b'certificate chain building is only possible on Windows')
3587 )
3599 )
3588
3600
3589 if not source:
3601 if not source:
3590 if not repo:
3602 if not repo:
3591 raise error.Abort(
3603 raise error.Abort(
3592 _(
3604 _(
3593 b"there is no Mercurial repository here, and no "
3605 b"there is no Mercurial repository here, and no "
3594 b"server specified"
3606 b"server specified"
3595 )
3607 )
3596 )
3608 )
3597 source = b"default"
3609 source = b"default"
3598
3610
3599 source, branches = hg.parseurl(ui.expandpath(source))
3611 source, branches = hg.parseurl(ui.expandpath(source))
3600 url = util.url(source)
3612 url = util.url(source)
3601
3613
3602 defaultport = {b'https': 443, b'ssh': 22}
3614 defaultport = {b'https': 443, b'ssh': 22}
3603 if url.scheme in defaultport:
3615 if url.scheme in defaultport:
3604 try:
3616 try:
3605 addr = (url.host, int(url.port or defaultport[url.scheme]))
3617 addr = (url.host, int(url.port or defaultport[url.scheme]))
3606 except ValueError:
3618 except ValueError:
3607 raise error.Abort(_(b"malformed port number in URL"))
3619 raise error.Abort(_(b"malformed port number in URL"))
3608 else:
3620 else:
3609 raise error.Abort(_(b"only https and ssh connections are supported"))
3621 raise error.Abort(_(b"only https and ssh connections are supported"))
3610
3622
3611 from . import win32
3623 from . import win32
3612
3624
3613 s = ssl.wrap_socket(
3625 s = ssl.wrap_socket(
3614 socket.socket(),
3626 socket.socket(),
3615 ssl_version=ssl.PROTOCOL_TLS,
3627 ssl_version=ssl.PROTOCOL_TLS,
3616 cert_reqs=ssl.CERT_NONE,
3628 cert_reqs=ssl.CERT_NONE,
3617 ca_certs=None,
3629 ca_certs=None,
3618 )
3630 )
3619
3631
3620 try:
3632 try:
3621 s.connect(addr)
3633 s.connect(addr)
3622 cert = s.getpeercert(True)
3634 cert = s.getpeercert(True)
3623
3635
3624 ui.status(_(b'checking the certificate chain for %s\n') % url.host)
3636 ui.status(_(b'checking the certificate chain for %s\n') % url.host)
3625
3637
3626 complete = win32.checkcertificatechain(cert, build=False)
3638 complete = win32.checkcertificatechain(cert, build=False)
3627
3639
3628 if not complete:
3640 if not complete:
3629 ui.status(_(b'certificate chain is incomplete, updating... '))
3641 ui.status(_(b'certificate chain is incomplete, updating... '))
3630
3642
3631 if not win32.checkcertificatechain(cert):
3643 if not win32.checkcertificatechain(cert):
3632 ui.status(_(b'failed.\n'))
3644 ui.status(_(b'failed.\n'))
3633 else:
3645 else:
3634 ui.status(_(b'done.\n'))
3646 ui.status(_(b'done.\n'))
3635 else:
3647 else:
3636 ui.status(_(b'full certificate chain is available\n'))
3648 ui.status(_(b'full certificate chain is available\n'))
3637 finally:
3649 finally:
3638 s.close()
3650 s.close()
3639
3651
3640
3652
3641 @command(
3653 @command(
3642 b"debugbackupbundle",
3654 b"debugbackupbundle",
3643 [
3655 [
3644 (
3656 (
3645 b"",
3657 b"",
3646 b"recover",
3658 b"recover",
3647 b"",
3659 b"",
3648 b"brings the specified changeset back into the repository",
3660 b"brings the specified changeset back into the repository",
3649 )
3661 )
3650 ]
3662 ]
3651 + cmdutil.logopts,
3663 + cmdutil.logopts,
3652 _(b"hg debugbackupbundle [--recover HASH]"),
3664 _(b"hg debugbackupbundle [--recover HASH]"),
3653 )
3665 )
3654 def debugbackupbundle(ui, repo, *pats, **opts):
3666 def debugbackupbundle(ui, repo, *pats, **opts):
3655 """lists the changesets available in backup bundles
3667 """lists the changesets available in backup bundles
3656
3668
3657 Without any arguments, this command prints a list of the changesets in each
3669 Without any arguments, this command prints a list of the changesets in each
3658 backup bundle.
3670 backup bundle.
3659
3671
3660 --recover takes a changeset hash and unbundles the first bundle that
3672 --recover takes a changeset hash and unbundles the first bundle that
3661 contains that hash, which puts that changeset back in your repository.
3673 contains that hash, which puts that changeset back in your repository.
3662
3674
3663 --verbose will print the entire commit message and the bundle path for that
3675 --verbose will print the entire commit message and the bundle path for that
3664 backup.
3676 backup.
3665 """
3677 """
3666 backups = list(
3678 backups = list(
3667 filter(
3679 filter(
3668 os.path.isfile, glob.glob(repo.vfs.join(b"strip-backup") + b"/*.hg")
3680 os.path.isfile, glob.glob(repo.vfs.join(b"strip-backup") + b"/*.hg")
3669 )
3681 )
3670 )
3682 )
3671 backups.sort(key=lambda x: os.path.getmtime(x), reverse=True)
3683 backups.sort(key=lambda x: os.path.getmtime(x), reverse=True)
3672
3684
3673 opts = pycompat.byteskwargs(opts)
3685 opts = pycompat.byteskwargs(opts)
3674 opts[b"bundle"] = b""
3686 opts[b"bundle"] = b""
3675 opts[b"force"] = None
3687 opts[b"force"] = None
3676 limit = logcmdutil.getlimit(opts)
3688 limit = logcmdutil.getlimit(opts)
3677
3689
3678 def display(other, chlist, displayer):
3690 def display(other, chlist, displayer):
3679 if opts.get(b"newest_first"):
3691 if opts.get(b"newest_first"):
3680 chlist.reverse()
3692 chlist.reverse()
3681 count = 0
3693 count = 0
3682 for n in chlist:
3694 for n in chlist:
3683 if limit is not None and count >= limit:
3695 if limit is not None and count >= limit:
3684 break
3696 break
3685 parents = [True for p in other.changelog.parents(n) if p != nullid]
3697 parents = [True for p in other.changelog.parents(n) if p != nullid]
3686 if opts.get(b"no_merges") and len(parents) == 2:
3698 if opts.get(b"no_merges") and len(parents) == 2:
3687 continue
3699 continue
3688 count += 1
3700 count += 1
3689 displayer.show(other[n])
3701 displayer.show(other[n])
3690
3702
3691 recovernode = opts.get(b"recover")
3703 recovernode = opts.get(b"recover")
3692 if recovernode:
3704 if recovernode:
3693 if scmutil.isrevsymbol(repo, recovernode):
3705 if scmutil.isrevsymbol(repo, recovernode):
3694 ui.warn(_(b"%s already exists in the repo\n") % recovernode)
3706 ui.warn(_(b"%s already exists in the repo\n") % recovernode)
3695 return
3707 return
3696 elif backups:
3708 elif backups:
3697 msg = _(
3709 msg = _(
3698 b"Recover changesets using: hg debugbackupbundle --recover "
3710 b"Recover changesets using: hg debugbackupbundle --recover "
3699 b"<changeset hash>\n\nAvailable backup changesets:"
3711 b"<changeset hash>\n\nAvailable backup changesets:"
3700 )
3712 )
3701 ui.status(msg, label=b"status.removed")
3713 ui.status(msg, label=b"status.removed")
3702 else:
3714 else:
3703 ui.status(_(b"no backup changesets found\n"))
3715 ui.status(_(b"no backup changesets found\n"))
3704 return
3716 return
3705
3717
3706 for backup in backups:
3718 for backup in backups:
3707 # Much of this is copied from the hg incoming logic
3719 # Much of this is copied from the hg incoming logic
3708 source = ui.expandpath(os.path.relpath(backup, encoding.getcwd()))
3720 source = ui.expandpath(os.path.relpath(backup, encoding.getcwd()))
3709 source, branches = hg.parseurl(source, opts.get(b"branch"))
3721 source, branches = hg.parseurl(source, opts.get(b"branch"))
3710 try:
3722 try:
3711 other = hg.peer(repo, opts, source)
3723 other = hg.peer(repo, opts, source)
3712 except error.LookupError as ex:
3724 except error.LookupError as ex:
3713 msg = _(b"\nwarning: unable to open bundle %s") % source
3725 msg = _(b"\nwarning: unable to open bundle %s") % source
3714 hint = _(b"\n(missing parent rev %s)\n") % short(ex.name)
3726 hint = _(b"\n(missing parent rev %s)\n") % short(ex.name)
3715 ui.warn(msg, hint=hint)
3727 ui.warn(msg, hint=hint)
3716 continue
3728 continue
3717 revs, checkout = hg.addbranchrevs(
3729 revs, checkout = hg.addbranchrevs(
3718 repo, other, branches, opts.get(b"rev")
3730 repo, other, branches, opts.get(b"rev")
3719 )
3731 )
3720
3732
3721 if revs:
3733 if revs:
3722 revs = [other.lookup(rev) for rev in revs]
3734 revs = [other.lookup(rev) for rev in revs]
3723
3735
3724 quiet = ui.quiet
3736 quiet = ui.quiet
3725 try:
3737 try:
3726 ui.quiet = True
3738 ui.quiet = True
3727 other, chlist, cleanupfn = bundlerepo.getremotechanges(
3739 other, chlist, cleanupfn = bundlerepo.getremotechanges(
3728 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
3740 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
3729 )
3741 )
3730 except error.LookupError:
3742 except error.LookupError:
3731 continue
3743 continue
3732 finally:
3744 finally:
3733 ui.quiet = quiet
3745 ui.quiet = quiet
3734
3746
3735 try:
3747 try:
3736 if not chlist:
3748 if not chlist:
3737 continue
3749 continue
3738 if recovernode:
3750 if recovernode:
3739 with repo.lock(), repo.transaction(b"unbundle") as tr:
3751 with repo.lock(), repo.transaction(b"unbundle") as tr:
3740 if scmutil.isrevsymbol(other, recovernode):
3752 if scmutil.isrevsymbol(other, recovernode):
3741 ui.status(_(b"Unbundling %s\n") % (recovernode))
3753 ui.status(_(b"Unbundling %s\n") % (recovernode))
3742 f = hg.openpath(ui, source)
3754 f = hg.openpath(ui, source)
3743 gen = exchange.readbundle(ui, f, source)
3755 gen = exchange.readbundle(ui, f, source)
3744 if isinstance(gen, bundle2.unbundle20):
3756 if isinstance(gen, bundle2.unbundle20):
3745 bundle2.applybundle(
3757 bundle2.applybundle(
3746 repo,
3758 repo,
3747 gen,
3759 gen,
3748 tr,
3760 tr,
3749 source=b"unbundle",
3761 source=b"unbundle",
3750 url=b"bundle:" + source,
3762 url=b"bundle:" + source,
3751 )
3763 )
3752 else:
3764 else:
3753 gen.apply(repo, b"unbundle", b"bundle:" + source)
3765 gen.apply(repo, b"unbundle", b"bundle:" + source)
3754 break
3766 break
3755 else:
3767 else:
3756 backupdate = encoding.strtolocal(
3768 backupdate = encoding.strtolocal(
3757 time.strftime(
3769 time.strftime(
3758 "%a %H:%M, %Y-%m-%d",
3770 "%a %H:%M, %Y-%m-%d",
3759 time.localtime(os.path.getmtime(source)),
3771 time.localtime(os.path.getmtime(source)),
3760 )
3772 )
3761 )
3773 )
3762 ui.status(b"\n%s\n" % (backupdate.ljust(50)))
3774 ui.status(b"\n%s\n" % (backupdate.ljust(50)))
3763 if ui.verbose:
3775 if ui.verbose:
3764 ui.status(b"%s%s\n" % (b"bundle:".ljust(13), source))
3776 ui.status(b"%s%s\n" % (b"bundle:".ljust(13), source))
3765 else:
3777 else:
3766 opts[
3778 opts[
3767 b"template"
3779 b"template"
3768 ] = b"{label('status.modified', node|short)} {desc|firstline}\n"
3780 ] = b"{label('status.modified', node|short)} {desc|firstline}\n"
3769 displayer = logcmdutil.changesetdisplayer(
3781 displayer = logcmdutil.changesetdisplayer(
3770 ui, other, opts, False
3782 ui, other, opts, False
3771 )
3783 )
3772 display(other, chlist, displayer)
3784 display(other, chlist, displayer)
3773 displayer.close()
3785 displayer.close()
3774 finally:
3786 finally:
3775 cleanupfn()
3787 cleanupfn()
3776
3788
3777
3789
3778 @command(
3790 @command(
3779 b'debugsub',
3791 b'debugsub',
3780 [(b'r', b'rev', b'', _(b'revision to check'), _(b'REV'))],
3792 [(b'r', b'rev', b'', _(b'revision to check'), _(b'REV'))],
3781 _(b'[-r REV] [REV]'),
3793 _(b'[-r REV] [REV]'),
3782 )
3794 )
3783 def debugsub(ui, repo, rev=None):
3795 def debugsub(ui, repo, rev=None):
3784 ctx = scmutil.revsingle(repo, rev, None)
3796 ctx = scmutil.revsingle(repo, rev, None)
3785 for k, v in sorted(ctx.substate.items()):
3797 for k, v in sorted(ctx.substate.items()):
3786 ui.writenoi18n(b'path %s\n' % k)
3798 ui.writenoi18n(b'path %s\n' % k)
3787 ui.writenoi18n(b' source %s\n' % v[0])
3799 ui.writenoi18n(b' source %s\n' % v[0])
3788 ui.writenoi18n(b' revision %s\n' % v[1])
3800 ui.writenoi18n(b' revision %s\n' % v[1])
3789
3801
3790
3802
3791 @command(b'debugshell', optionalrepo=True)
3803 @command(b'debugshell', optionalrepo=True)
3792 def debugshell(ui, repo):
3804 def debugshell(ui, repo):
3793 """run an interactive Python interpreter
3805 """run an interactive Python interpreter
3794
3806
3795 The local namespace is provided with a reference to the ui and
3807 The local namespace is provided with a reference to the ui and
3796 the repo instance (if available).
3808 the repo instance (if available).
3797 """
3809 """
3798 import code
3810 import code
3799
3811
3800 imported_objects = {
3812 imported_objects = {
3801 'ui': ui,
3813 'ui': ui,
3802 'repo': repo,
3814 'repo': repo,
3803 }
3815 }
3804
3816
3805 code.interact(local=imported_objects)
3817 code.interact(local=imported_objects)
3806
3818
3807
3819
3808 @command(
3820 @command(
3809 b'debugsuccessorssets',
3821 b'debugsuccessorssets',
3810 [(b'', b'closest', False, _(b'return closest successors sets only'))],
3822 [(b'', b'closest', False, _(b'return closest successors sets only'))],
3811 _(b'[REV]'),
3823 _(b'[REV]'),
3812 )
3824 )
3813 def debugsuccessorssets(ui, repo, *revs, **opts):
3825 def debugsuccessorssets(ui, repo, *revs, **opts):
3814 """show set of successors for revision
3826 """show set of successors for revision
3815
3827
3816 A successors set of changeset A is a consistent group of revisions that
3828 A successors set of changeset A is a consistent group of revisions that
3817 succeed A. It contains non-obsolete changesets only unless closests
3829 succeed A. It contains non-obsolete changesets only unless closests
3818 successors set is set.
3830 successors set is set.
3819
3831
3820 In most cases a changeset A has a single successors set containing a single
3832 In most cases a changeset A has a single successors set containing a single
3821 successor (changeset A replaced by A').
3833 successor (changeset A replaced by A').
3822
3834
3823 A changeset that is made obsolete with no successors are called "pruned".
3835 A changeset that is made obsolete with no successors are called "pruned".
3824 Such changesets have no successors sets at all.
3836 Such changesets have no successors sets at all.
3825
3837
3826 A changeset that has been "split" will have a successors set containing
3838 A changeset that has been "split" will have a successors set containing
3827 more than one successor.
3839 more than one successor.
3828
3840
3829 A changeset that has been rewritten in multiple different ways is called
3841 A changeset that has been rewritten in multiple different ways is called
3830 "divergent". Such changesets have multiple successor sets (each of which
3842 "divergent". Such changesets have multiple successor sets (each of which
3831 may also be split, i.e. have multiple successors).
3843 may also be split, i.e. have multiple successors).
3832
3844
3833 Results are displayed as follows::
3845 Results are displayed as follows::
3834
3846
3835 <rev1>
3847 <rev1>
3836 <successors-1A>
3848 <successors-1A>
3837 <rev2>
3849 <rev2>
3838 <successors-2A>
3850 <successors-2A>
3839 <successors-2B1> <successors-2B2> <successors-2B3>
3851 <successors-2B1> <successors-2B2> <successors-2B3>
3840
3852
3841 Here rev2 has two possible (i.e. divergent) successors sets. The first
3853 Here rev2 has two possible (i.e. divergent) successors sets. The first
3842 holds one element, whereas the second holds three (i.e. the changeset has
3854 holds one element, whereas the second holds three (i.e. the changeset has
3843 been split).
3855 been split).
3844 """
3856 """
3845 # passed to successorssets caching computation from one call to another
3857 # passed to successorssets caching computation from one call to another
3846 cache = {}
3858 cache = {}
3847 ctx2str = bytes
3859 ctx2str = bytes
3848 node2str = short
3860 node2str = short
3849 for rev in scmutil.revrange(repo, revs):
3861 for rev in scmutil.revrange(repo, revs):
3850 ctx = repo[rev]
3862 ctx = repo[rev]
3851 ui.write(b'%s\n' % ctx2str(ctx))
3863 ui.write(b'%s\n' % ctx2str(ctx))
3852 for succsset in obsutil.successorssets(
3864 for succsset in obsutil.successorssets(
3853 repo, ctx.node(), closest=opts['closest'], cache=cache
3865 repo, ctx.node(), closest=opts['closest'], cache=cache
3854 ):
3866 ):
3855 if succsset:
3867 if succsset:
3856 ui.write(b' ')
3868 ui.write(b' ')
3857 ui.write(node2str(succsset[0]))
3869 ui.write(node2str(succsset[0]))
3858 for node in succsset[1:]:
3870 for node in succsset[1:]:
3859 ui.write(b' ')
3871 ui.write(b' ')
3860 ui.write(node2str(node))
3872 ui.write(node2str(node))
3861 ui.write(b'\n')
3873 ui.write(b'\n')
3862
3874
3863
3875
3864 @command(b'debugtagscache', [])
3876 @command(b'debugtagscache', [])
3865 def debugtagscache(ui, repo):
3877 def debugtagscache(ui, repo):
3866 """display the contents of .hg/cache/hgtagsfnodes1"""
3878 """display the contents of .hg/cache/hgtagsfnodes1"""
3867 cache = tagsmod.hgtagsfnodescache(repo.unfiltered())
3879 cache = tagsmod.hgtagsfnodescache(repo.unfiltered())
3868 flog = repo.file(b'.hgtags')
3880 flog = repo.file(b'.hgtags')
3869 for r in repo:
3881 for r in repo:
3870 node = repo[r].node()
3882 node = repo[r].node()
3871 tagsnode = cache.getfnode(node, computemissing=False)
3883 tagsnode = cache.getfnode(node, computemissing=False)
3872 if tagsnode:
3884 if tagsnode:
3873 tagsnodedisplay = hex(tagsnode)
3885 tagsnodedisplay = hex(tagsnode)
3874 if not flog.hasnode(tagsnode):
3886 if not flog.hasnode(tagsnode):
3875 tagsnodedisplay += b' (unknown node)'
3887 tagsnodedisplay += b' (unknown node)'
3876 elif tagsnode is None:
3888 elif tagsnode is None:
3877 tagsnodedisplay = b'missing'
3889 tagsnodedisplay = b'missing'
3878 else:
3890 else:
3879 tagsnodedisplay = b'invalid'
3891 tagsnodedisplay = b'invalid'
3880
3892
3881 ui.write(b'%d %s %s\n' % (r, hex(node), tagsnodedisplay))
3893 ui.write(b'%d %s %s\n' % (r, hex(node), tagsnodedisplay))
3882
3894
3883
3895
3884 @command(
3896 @command(
3885 b'debugtemplate',
3897 b'debugtemplate',
3886 [
3898 [
3887 (b'r', b'rev', [], _(b'apply template on changesets'), _(b'REV')),
3899 (b'r', b'rev', [], _(b'apply template on changesets'), _(b'REV')),
3888 (b'D', b'define', [], _(b'define template keyword'), _(b'KEY=VALUE')),
3900 (b'D', b'define', [], _(b'define template keyword'), _(b'KEY=VALUE')),
3889 ],
3901 ],
3890 _(b'[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3902 _(b'[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3891 optionalrepo=True,
3903 optionalrepo=True,
3892 )
3904 )
3893 def debugtemplate(ui, repo, tmpl, **opts):
3905 def debugtemplate(ui, repo, tmpl, **opts):
3894 """parse and apply a template
3906 """parse and apply a template
3895
3907
3896 If -r/--rev is given, the template is processed as a log template and
3908 If -r/--rev is given, the template is processed as a log template and
3897 applied to the given changesets. Otherwise, it is processed as a generic
3909 applied to the given changesets. Otherwise, it is processed as a generic
3898 template.
3910 template.
3899
3911
3900 Use --verbose to print the parsed tree.
3912 Use --verbose to print the parsed tree.
3901 """
3913 """
3902 revs = None
3914 revs = None
3903 if opts['rev']:
3915 if opts['rev']:
3904 if repo is None:
3916 if repo is None:
3905 raise error.RepoError(
3917 raise error.RepoError(
3906 _(b'there is no Mercurial repository here (.hg not found)')
3918 _(b'there is no Mercurial repository here (.hg not found)')
3907 )
3919 )
3908 revs = scmutil.revrange(repo, opts['rev'])
3920 revs = scmutil.revrange(repo, opts['rev'])
3909
3921
3910 props = {}
3922 props = {}
3911 for d in opts['define']:
3923 for d in opts['define']:
3912 try:
3924 try:
3913 k, v = (e.strip() for e in d.split(b'=', 1))
3925 k, v = (e.strip() for e in d.split(b'=', 1))
3914 if not k or k == b'ui':
3926 if not k or k == b'ui':
3915 raise ValueError
3927 raise ValueError
3916 props[k] = v
3928 props[k] = v
3917 except ValueError:
3929 except ValueError:
3918 raise error.Abort(_(b'malformed keyword definition: %s') % d)
3930 raise error.Abort(_(b'malformed keyword definition: %s') % d)
3919
3931
3920 if ui.verbose:
3932 if ui.verbose:
3921 aliases = ui.configitems(b'templatealias')
3933 aliases = ui.configitems(b'templatealias')
3922 tree = templater.parse(tmpl)
3934 tree = templater.parse(tmpl)
3923 ui.note(templater.prettyformat(tree), b'\n')
3935 ui.note(templater.prettyformat(tree), b'\n')
3924 newtree = templater.expandaliases(tree, aliases)
3936 newtree = templater.expandaliases(tree, aliases)
3925 if newtree != tree:
3937 if newtree != tree:
3926 ui.notenoi18n(
3938 ui.notenoi18n(
3927 b"* expanded:\n", templater.prettyformat(newtree), b'\n'
3939 b"* expanded:\n", templater.prettyformat(newtree), b'\n'
3928 )
3940 )
3929
3941
3930 if revs is None:
3942 if revs is None:
3931 tres = formatter.templateresources(ui, repo)
3943 tres = formatter.templateresources(ui, repo)
3932 t = formatter.maketemplater(ui, tmpl, resources=tres)
3944 t = formatter.maketemplater(ui, tmpl, resources=tres)
3933 if ui.verbose:
3945 if ui.verbose:
3934 kwds, funcs = t.symbolsuseddefault()
3946 kwds, funcs = t.symbolsuseddefault()
3935 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3947 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3936 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3948 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3937 ui.write(t.renderdefault(props))
3949 ui.write(t.renderdefault(props))
3938 else:
3950 else:
3939 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
3951 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
3940 if ui.verbose:
3952 if ui.verbose:
3941 kwds, funcs = displayer.t.symbolsuseddefault()
3953 kwds, funcs = displayer.t.symbolsuseddefault()
3942 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3954 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3943 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3955 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3944 for r in revs:
3956 for r in revs:
3945 displayer.show(repo[r], **pycompat.strkwargs(props))
3957 displayer.show(repo[r], **pycompat.strkwargs(props))
3946 displayer.close()
3958 displayer.close()
3947
3959
3948
3960
3949 @command(
3961 @command(
3950 b'debuguigetpass',
3962 b'debuguigetpass',
3951 [
3963 [
3952 (b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),
3964 (b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),
3953 ],
3965 ],
3954 _(b'[-p TEXT]'),
3966 _(b'[-p TEXT]'),
3955 norepo=True,
3967 norepo=True,
3956 )
3968 )
3957 def debuguigetpass(ui, prompt=b''):
3969 def debuguigetpass(ui, prompt=b''):
3958 """show prompt to type password"""
3970 """show prompt to type password"""
3959 r = ui.getpass(prompt)
3971 r = ui.getpass(prompt)
3960 if r is None:
3972 if r is None:
3961 r = b"<default response>"
3973 r = b"<default response>"
3962 ui.writenoi18n(b'response: %s\n' % r)
3974 ui.writenoi18n(b'response: %s\n' % r)
3963
3975
3964
3976
3965 @command(
3977 @command(
3966 b'debuguiprompt',
3978 b'debuguiprompt',
3967 [
3979 [
3968 (b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),
3980 (b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),
3969 ],
3981 ],
3970 _(b'[-p TEXT]'),
3982 _(b'[-p TEXT]'),
3971 norepo=True,
3983 norepo=True,
3972 )
3984 )
3973 def debuguiprompt(ui, prompt=b''):
3985 def debuguiprompt(ui, prompt=b''):
3974 """show plain prompt"""
3986 """show plain prompt"""
3975 r = ui.prompt(prompt)
3987 r = ui.prompt(prompt)
3976 ui.writenoi18n(b'response: %s\n' % r)
3988 ui.writenoi18n(b'response: %s\n' % r)
3977
3989
3978
3990
3979 @command(b'debugupdatecaches', [])
3991 @command(b'debugupdatecaches', [])
3980 def debugupdatecaches(ui, repo, *pats, **opts):
3992 def debugupdatecaches(ui, repo, *pats, **opts):
3981 """warm all known caches in the repository"""
3993 """warm all known caches in the repository"""
3982 with repo.wlock(), repo.lock():
3994 with repo.wlock(), repo.lock():
3983 repo.updatecaches(full=True)
3995 repo.updatecaches(full=True)
3984
3996
3985
3997
3986 @command(
3998 @command(
3987 b'debugupgraderepo',
3999 b'debugupgraderepo',
3988 [
4000 [
3989 (
4001 (
3990 b'o',
4002 b'o',
3991 b'optimize',
4003 b'optimize',
3992 [],
4004 [],
3993 _(b'extra optimization to perform'),
4005 _(b'extra optimization to perform'),
3994 _(b'NAME'),
4006 _(b'NAME'),
3995 ),
4007 ),
3996 (b'', b'run', False, _(b'performs an upgrade')),
4008 (b'', b'run', False, _(b'performs an upgrade')),
3997 (b'', b'backup', True, _(b'keep the old repository content around')),
4009 (b'', b'backup', True, _(b'keep the old repository content around')),
3998 (b'', b'changelog', None, _(b'select the changelog for upgrade')),
4010 (b'', b'changelog', None, _(b'select the changelog for upgrade')),
3999 (b'', b'manifest', None, _(b'select the manifest for upgrade')),
4011 (b'', b'manifest', None, _(b'select the manifest for upgrade')),
4000 (b'', b'filelogs', None, _(b'select all filelogs for upgrade')),
4012 (b'', b'filelogs', None, _(b'select all filelogs for upgrade')),
4001 ],
4013 ],
4002 )
4014 )
4003 def debugupgraderepo(ui, repo, run=False, optimize=None, backup=True, **opts):
4015 def debugupgraderepo(ui, repo, run=False, optimize=None, backup=True, **opts):
4004 """upgrade a repository to use different features
4016 """upgrade a repository to use different features
4005
4017
4006 If no arguments are specified, the repository is evaluated for upgrade
4018 If no arguments are specified, the repository is evaluated for upgrade
4007 and a list of problems and potential optimizations is printed.
4019 and a list of problems and potential optimizations is printed.
4008
4020
4009 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
4021 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
4010 can be influenced via additional arguments. More details will be provided
4022 can be influenced via additional arguments. More details will be provided
4011 by the command output when run without ``--run``.
4023 by the command output when run without ``--run``.
4012
4024
4013 During the upgrade, the repository will be locked and no writes will be
4025 During the upgrade, the repository will be locked and no writes will be
4014 allowed.
4026 allowed.
4015
4027
4016 At the end of the upgrade, the repository may not be readable while new
4028 At the end of the upgrade, the repository may not be readable while new
4017 repository data is swapped in. This window will be as long as it takes to
4029 repository data is swapped in. This window will be as long as it takes to
4018 rename some directories inside the ``.hg`` directory. On most machines, this
4030 rename some directories inside the ``.hg`` directory. On most machines, this
4019 should complete almost instantaneously and the chances of a consumer being
4031 should complete almost instantaneously and the chances of a consumer being
4020 unable to access the repository should be low.
4032 unable to access the repository should be low.
4021
4033
4022 By default, all revlog will be upgraded. You can restrict this using flag
4034 By default, all revlog will be upgraded. You can restrict this using flag
4023 such as `--manifest`:
4035 such as `--manifest`:
4024
4036
4025 * `--manifest`: only optimize the manifest
4037 * `--manifest`: only optimize the manifest
4026 * `--no-manifest`: optimize all revlog but the manifest
4038 * `--no-manifest`: optimize all revlog but the manifest
4027 * `--changelog`: optimize the changelog only
4039 * `--changelog`: optimize the changelog only
4028 * `--no-changelog --no-manifest`: optimize filelogs only
4040 * `--no-changelog --no-manifest`: optimize filelogs only
4029 * `--filelogs`: optimize the filelogs only
4041 * `--filelogs`: optimize the filelogs only
4030 * `--no-changelog --no-manifest --no-filelogs`: skip all revlog optimizations
4042 * `--no-changelog --no-manifest --no-filelogs`: skip all revlog optimizations
4031 """
4043 """
4032 return upgrade.upgraderepo(
4044 return upgrade.upgraderepo(
4033 ui, repo, run=run, optimize=set(optimize), backup=backup, **opts
4045 ui, repo, run=run, optimize=set(optimize), backup=backup, **opts
4034 )
4046 )
4035
4047
4036
4048
4037 @command(
4049 @command(
4038 b'debugwalk', cmdutil.walkopts, _(b'[OPTION]... [FILE]...'), inferrepo=True
4050 b'debugwalk', cmdutil.walkopts, _(b'[OPTION]... [FILE]...'), inferrepo=True
4039 )
4051 )
4040 def debugwalk(ui, repo, *pats, **opts):
4052 def debugwalk(ui, repo, *pats, **opts):
4041 """show how files match on given patterns"""
4053 """show how files match on given patterns"""
4042 opts = pycompat.byteskwargs(opts)
4054 opts = pycompat.byteskwargs(opts)
4043 m = scmutil.match(repo[None], pats, opts)
4055 m = scmutil.match(repo[None], pats, opts)
4044 if ui.verbose:
4056 if ui.verbose:
4045 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
4057 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
4046 items = list(repo[None].walk(m))
4058 items = list(repo[None].walk(m))
4047 if not items:
4059 if not items:
4048 return
4060 return
4049 f = lambda fn: fn
4061 f = lambda fn: fn
4050 if ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/':
4062 if ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/':
4051 f = lambda fn: util.normpath(fn)
4063 f = lambda fn: util.normpath(fn)
4052 fmt = b'f %%-%ds %%-%ds %%s' % (
4064 fmt = b'f %%-%ds %%-%ds %%s' % (
4053 max([len(abs) for abs in items]),
4065 max([len(abs) for abs in items]),
4054 max([len(repo.pathto(abs)) for abs in items]),
4066 max([len(repo.pathto(abs)) for abs in items]),
4055 )
4067 )
4056 for abs in items:
4068 for abs in items:
4057 line = fmt % (
4069 line = fmt % (
4058 abs,
4070 abs,
4059 f(repo.pathto(abs)),
4071 f(repo.pathto(abs)),
4060 m.exact(abs) and b'exact' or b'',
4072 m.exact(abs) and b'exact' or b'',
4061 )
4073 )
4062 ui.write(b"%s\n" % line.rstrip())
4074 ui.write(b"%s\n" % line.rstrip())
4063
4075
4064
4076
4065 @command(b'debugwhyunstable', [], _(b'REV'))
4077 @command(b'debugwhyunstable', [], _(b'REV'))
4066 def debugwhyunstable(ui, repo, rev):
4078 def debugwhyunstable(ui, repo, rev):
4067 """explain instabilities of a changeset"""
4079 """explain instabilities of a changeset"""
4068 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
4080 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
4069 dnodes = b''
4081 dnodes = b''
4070 if entry.get(b'divergentnodes'):
4082 if entry.get(b'divergentnodes'):
4071 dnodes = (
4083 dnodes = (
4072 b' '.join(
4084 b' '.join(
4073 b'%s (%s)' % (ctx.hex(), ctx.phasestr())
4085 b'%s (%s)' % (ctx.hex(), ctx.phasestr())
4074 for ctx in entry[b'divergentnodes']
4086 for ctx in entry[b'divergentnodes']
4075 )
4087 )
4076 + b' '
4088 + b' '
4077 )
4089 )
4078 ui.write(
4090 ui.write(
4079 b'%s: %s%s %s\n'
4091 b'%s: %s%s %s\n'
4080 % (entry[b'instability'], dnodes, entry[b'reason'], entry[b'node'])
4092 % (entry[b'instability'], dnodes, entry[b'reason'], entry[b'node'])
4081 )
4093 )
4082
4094
4083
4095
4084 @command(
4096 @command(
4085 b'debugwireargs',
4097 b'debugwireargs',
4086 [
4098 [
4087 (b'', b'three', b'', b'three'),
4099 (b'', b'three', b'', b'three'),
4088 (b'', b'four', b'', b'four'),
4100 (b'', b'four', b'', b'four'),
4089 (b'', b'five', b'', b'five'),
4101 (b'', b'five', b'', b'five'),
4090 ]
4102 ]
4091 + cmdutil.remoteopts,
4103 + cmdutil.remoteopts,
4092 _(b'REPO [OPTIONS]... [ONE [TWO]]'),
4104 _(b'REPO [OPTIONS]... [ONE [TWO]]'),
4093 norepo=True,
4105 norepo=True,
4094 )
4106 )
4095 def debugwireargs(ui, repopath, *vals, **opts):
4107 def debugwireargs(ui, repopath, *vals, **opts):
4096 opts = pycompat.byteskwargs(opts)
4108 opts = pycompat.byteskwargs(opts)
4097 repo = hg.peer(ui, opts, repopath)
4109 repo = hg.peer(ui, opts, repopath)
4098 for opt in cmdutil.remoteopts:
4110 try:
4099 del opts[opt[1]]
4111 for opt in cmdutil.remoteopts:
4100 args = {}
4112 del opts[opt[1]]
4101 for k, v in pycompat.iteritems(opts):
4113 args = {}
4102 if v:
4114 for k, v in pycompat.iteritems(opts):
4103 args[k] = v
4115 if v:
4104 args = pycompat.strkwargs(args)
4116 args[k] = v
4105 # run twice to check that we don't mess up the stream for the next command
4117 args = pycompat.strkwargs(args)
4106 res1 = repo.debugwireargs(*vals, **args)
4118 # run twice to check that we don't mess up the stream for the next command
4107 res2 = repo.debugwireargs(*vals, **args)
4119 res1 = repo.debugwireargs(*vals, **args)
4108 ui.write(b"%s\n" % res1)
4120 res2 = repo.debugwireargs(*vals, **args)
4109 if res1 != res2:
4121 ui.write(b"%s\n" % res1)
4110 ui.warn(b"%s\n" % res2)
4122 if res1 != res2:
4123 ui.warn(b"%s\n" % res2)
4124 finally:
4125 repo.close()
4111
4126
4112
4127
4113 def _parsewirelangblocks(fh):
4128 def _parsewirelangblocks(fh):
4114 activeaction = None
4129 activeaction = None
4115 blocklines = []
4130 blocklines = []
4116 lastindent = 0
4131 lastindent = 0
4117
4132
4118 for line in fh:
4133 for line in fh:
4119 line = line.rstrip()
4134 line = line.rstrip()
4120 if not line:
4135 if not line:
4121 continue
4136 continue
4122
4137
4123 if line.startswith(b'#'):
4138 if line.startswith(b'#'):
4124 continue
4139 continue
4125
4140
4126 if not line.startswith(b' '):
4141 if not line.startswith(b' '):
4127 # New block. Flush previous one.
4142 # New block. Flush previous one.
4128 if activeaction:
4143 if activeaction:
4129 yield activeaction, blocklines
4144 yield activeaction, blocklines
4130
4145
4131 activeaction = line
4146 activeaction = line
4132 blocklines = []
4147 blocklines = []
4133 lastindent = 0
4148 lastindent = 0
4134 continue
4149 continue
4135
4150
4136 # Else we start with an indent.
4151 # Else we start with an indent.
4137
4152
4138 if not activeaction:
4153 if not activeaction:
4139 raise error.Abort(_(b'indented line outside of block'))
4154 raise error.Abort(_(b'indented line outside of block'))
4140
4155
4141 indent = len(line) - len(line.lstrip())
4156 indent = len(line) - len(line.lstrip())
4142
4157
4143 # If this line is indented more than the last line, concatenate it.
4158 # If this line is indented more than the last line, concatenate it.
4144 if indent > lastindent and blocklines:
4159 if indent > lastindent and blocklines:
4145 blocklines[-1] += line.lstrip()
4160 blocklines[-1] += line.lstrip()
4146 else:
4161 else:
4147 blocklines.append(line)
4162 blocklines.append(line)
4148 lastindent = indent
4163 lastindent = indent
4149
4164
4150 # Flush last block.
4165 # Flush last block.
4151 if activeaction:
4166 if activeaction:
4152 yield activeaction, blocklines
4167 yield activeaction, blocklines
4153
4168
4154
4169
4155 @command(
4170 @command(
4156 b'debugwireproto',
4171 b'debugwireproto',
4157 [
4172 [
4158 (b'', b'localssh', False, _(b'start an SSH server for this repo')),
4173 (b'', b'localssh', False, _(b'start an SSH server for this repo')),
4159 (b'', b'peer', b'', _(b'construct a specific version of the peer')),
4174 (b'', b'peer', b'', _(b'construct a specific version of the peer')),
4160 (
4175 (
4161 b'',
4176 b'',
4162 b'noreadstderr',
4177 b'noreadstderr',
4163 False,
4178 False,
4164 _(b'do not read from stderr of the remote'),
4179 _(b'do not read from stderr of the remote'),
4165 ),
4180 ),
4166 (
4181 (
4167 b'',
4182 b'',
4168 b'nologhandshake',
4183 b'nologhandshake',
4169 False,
4184 False,
4170 _(b'do not log I/O related to the peer handshake'),
4185 _(b'do not log I/O related to the peer handshake'),
4171 ),
4186 ),
4172 ]
4187 ]
4173 + cmdutil.remoteopts,
4188 + cmdutil.remoteopts,
4174 _(b'[PATH]'),
4189 _(b'[PATH]'),
4175 optionalrepo=True,
4190 optionalrepo=True,
4176 )
4191 )
4177 def debugwireproto(ui, repo, path=None, **opts):
4192 def debugwireproto(ui, repo, path=None, **opts):
4178 """send wire protocol commands to a server
4193 """send wire protocol commands to a server
4179
4194
4180 This command can be used to issue wire protocol commands to remote
4195 This command can be used to issue wire protocol commands to remote
4181 peers and to debug the raw data being exchanged.
4196 peers and to debug the raw data being exchanged.
4182
4197
4183 ``--localssh`` will start an SSH server against the current repository
4198 ``--localssh`` will start an SSH server against the current repository
4184 and connect to that. By default, the connection will perform a handshake
4199 and connect to that. By default, the connection will perform a handshake
4185 and establish an appropriate peer instance.
4200 and establish an appropriate peer instance.
4186
4201
4187 ``--peer`` can be used to bypass the handshake protocol and construct a
4202 ``--peer`` can be used to bypass the handshake protocol and construct a
4188 peer instance using the specified class type. Valid values are ``raw``,
4203 peer instance using the specified class type. Valid values are ``raw``,
4189 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
4204 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
4190 raw data payloads and don't support higher-level command actions.
4205 raw data payloads and don't support higher-level command actions.
4191
4206
4192 ``--noreadstderr`` can be used to disable automatic reading from stderr
4207 ``--noreadstderr`` can be used to disable automatic reading from stderr
4193 of the peer (for SSH connections only). Disabling automatic reading of
4208 of the peer (for SSH connections only). Disabling automatic reading of
4194 stderr is useful for making output more deterministic.
4209 stderr is useful for making output more deterministic.
4195
4210
4196 Commands are issued via a mini language which is specified via stdin.
4211 Commands are issued via a mini language which is specified via stdin.
4197 The language consists of individual actions to perform. An action is
4212 The language consists of individual actions to perform. An action is
4198 defined by a block. A block is defined as a line with no leading
4213 defined by a block. A block is defined as a line with no leading
4199 space followed by 0 or more lines with leading space. Blocks are
4214 space followed by 0 or more lines with leading space. Blocks are
4200 effectively a high-level command with additional metadata.
4215 effectively a high-level command with additional metadata.
4201
4216
4202 Lines beginning with ``#`` are ignored.
4217 Lines beginning with ``#`` are ignored.
4203
4218
4204 The following sections denote available actions.
4219 The following sections denote available actions.
4205
4220
4206 raw
4221 raw
4207 ---
4222 ---
4208
4223
4209 Send raw data to the server.
4224 Send raw data to the server.
4210
4225
4211 The block payload contains the raw data to send as one atomic send
4226 The block payload contains the raw data to send as one atomic send
4212 operation. The data may not actually be delivered in a single system
4227 operation. The data may not actually be delivered in a single system
4213 call: it depends on the abilities of the transport being used.
4228 call: it depends on the abilities of the transport being used.
4214
4229
4215 Each line in the block is de-indented and concatenated. Then, that
4230 Each line in the block is de-indented and concatenated. Then, that
4216 value is evaluated as a Python b'' literal. This allows the use of
4231 value is evaluated as a Python b'' literal. This allows the use of
4217 backslash escaping, etc.
4232 backslash escaping, etc.
4218
4233
4219 raw+
4234 raw+
4220 ----
4235 ----
4221
4236
4222 Behaves like ``raw`` except flushes output afterwards.
4237 Behaves like ``raw`` except flushes output afterwards.
4223
4238
4224 command <X>
4239 command <X>
4225 -----------
4240 -----------
4226
4241
4227 Send a request to run a named command, whose name follows the ``command``
4242 Send a request to run a named command, whose name follows the ``command``
4228 string.
4243 string.
4229
4244
4230 Arguments to the command are defined as lines in this block. The format of
4245 Arguments to the command are defined as lines in this block. The format of
4231 each line is ``<key> <value>``. e.g.::
4246 each line is ``<key> <value>``. e.g.::
4232
4247
4233 command listkeys
4248 command listkeys
4234 namespace bookmarks
4249 namespace bookmarks
4235
4250
4236 If the value begins with ``eval:``, it will be interpreted as a Python
4251 If the value begins with ``eval:``, it will be interpreted as a Python
4237 literal expression. Otherwise values are interpreted as Python b'' literals.
4252 literal expression. Otherwise values are interpreted as Python b'' literals.
4238 This allows sending complex types and encoding special byte sequences via
4253 This allows sending complex types and encoding special byte sequences via
4239 backslash escaping.
4254 backslash escaping.
4240
4255
4241 The following arguments have special meaning:
4256 The following arguments have special meaning:
4242
4257
4243 ``PUSHFILE``
4258 ``PUSHFILE``
4244 When defined, the *push* mechanism of the peer will be used instead
4259 When defined, the *push* mechanism of the peer will be used instead
4245 of the static request-response mechanism and the content of the
4260 of the static request-response mechanism and the content of the
4246 file specified in the value of this argument will be sent as the
4261 file specified in the value of this argument will be sent as the
4247 command payload.
4262 command payload.
4248
4263
4249 This can be used to submit a local bundle file to the remote.
4264 This can be used to submit a local bundle file to the remote.
4250
4265
4251 batchbegin
4266 batchbegin
4252 ----------
4267 ----------
4253
4268
4254 Instruct the peer to begin a batched send.
4269 Instruct the peer to begin a batched send.
4255
4270
4256 All ``command`` blocks are queued for execution until the next
4271 All ``command`` blocks are queued for execution until the next
4257 ``batchsubmit`` block.
4272 ``batchsubmit`` block.
4258
4273
4259 batchsubmit
4274 batchsubmit
4260 -----------
4275 -----------
4261
4276
4262 Submit previously queued ``command`` blocks as a batch request.
4277 Submit previously queued ``command`` blocks as a batch request.
4263
4278
4264 This action MUST be paired with a ``batchbegin`` action.
4279 This action MUST be paired with a ``batchbegin`` action.
4265
4280
4266 httprequest <method> <path>
4281 httprequest <method> <path>
4267 ---------------------------
4282 ---------------------------
4268
4283
4269 (HTTP peer only)
4284 (HTTP peer only)
4270
4285
4271 Send an HTTP request to the peer.
4286 Send an HTTP request to the peer.
4272
4287
4273 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
4288 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
4274
4289
4275 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
4290 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
4276 headers to add to the request. e.g. ``Accept: foo``.
4291 headers to add to the request. e.g. ``Accept: foo``.
4277
4292
4278 The following arguments are special:
4293 The following arguments are special:
4279
4294
4280 ``BODYFILE``
4295 ``BODYFILE``
4281 The content of the file defined as the value to this argument will be
4296 The content of the file defined as the value to this argument will be
4282 transferred verbatim as the HTTP request body.
4297 transferred verbatim as the HTTP request body.
4283
4298
4284 ``frame <type> <flags> <payload>``
4299 ``frame <type> <flags> <payload>``
4285 Send a unified protocol frame as part of the request body.
4300 Send a unified protocol frame as part of the request body.
4286
4301
4287 All frames will be collected and sent as the body to the HTTP
4302 All frames will be collected and sent as the body to the HTTP
4288 request.
4303 request.
4289
4304
4290 close
4305 close
4291 -----
4306 -----
4292
4307
4293 Close the connection to the server.
4308 Close the connection to the server.
4294
4309
4295 flush
4310 flush
4296 -----
4311 -----
4297
4312
4298 Flush data written to the server.
4313 Flush data written to the server.
4299
4314
4300 readavailable
4315 readavailable
4301 -------------
4316 -------------
4302
4317
4303 Close the write end of the connection and read all available data from
4318 Close the write end of the connection and read all available data from
4304 the server.
4319 the server.
4305
4320
4306 If the connection to the server encompasses multiple pipes, we poll both
4321 If the connection to the server encompasses multiple pipes, we poll both
4307 pipes and read available data.
4322 pipes and read available data.
4308
4323
4309 readline
4324 readline
4310 --------
4325 --------
4311
4326
4312 Read a line of output from the server. If there are multiple output
4327 Read a line of output from the server. If there are multiple output
4313 pipes, reads only the main pipe.
4328 pipes, reads only the main pipe.
4314
4329
4315 ereadline
4330 ereadline
4316 ---------
4331 ---------
4317
4332
4318 Like ``readline``, but read from the stderr pipe, if available.
4333 Like ``readline``, but read from the stderr pipe, if available.
4319
4334
4320 read <X>
4335 read <X>
4321 --------
4336 --------
4322
4337
4323 ``read()`` N bytes from the server's main output pipe.
4338 ``read()`` N bytes from the server's main output pipe.
4324
4339
4325 eread <X>
4340 eread <X>
4326 ---------
4341 ---------
4327
4342
4328 ``read()`` N bytes from the server's stderr pipe, if available.
4343 ``read()`` N bytes from the server's stderr pipe, if available.
4329
4344
4330 Specifying Unified Frame-Based Protocol Frames
4345 Specifying Unified Frame-Based Protocol Frames
4331 ----------------------------------------------
4346 ----------------------------------------------
4332
4347
4333 It is possible to emit a *Unified Frame-Based Protocol* by using special
4348 It is possible to emit a *Unified Frame-Based Protocol* by using special
4334 syntax.
4349 syntax.
4335
4350
4336 A frame is composed as a type, flags, and payload. These can be parsed
4351 A frame is composed as a type, flags, and payload. These can be parsed
4337 from a string of the form:
4352 from a string of the form:
4338
4353
4339 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
4354 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
4340
4355
4341 ``request-id`` and ``stream-id`` are integers defining the request and
4356 ``request-id`` and ``stream-id`` are integers defining the request and
4342 stream identifiers.
4357 stream identifiers.
4343
4358
4344 ``type`` can be an integer value for the frame type or the string name
4359 ``type`` can be an integer value for the frame type or the string name
4345 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
4360 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
4346 ``command-name``.
4361 ``command-name``.
4347
4362
4348 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
4363 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
4349 components. Each component (and there can be just one) can be an integer
4364 components. Each component (and there can be just one) can be an integer
4350 or a flag name for stream flags or frame flags, respectively. Values are
4365 or a flag name for stream flags or frame flags, respectively. Values are
4351 resolved to integers and then bitwise OR'd together.
4366 resolved to integers and then bitwise OR'd together.
4352
4367
4353 ``payload`` represents the raw frame payload. If it begins with
4368 ``payload`` represents the raw frame payload. If it begins with
4354 ``cbor:``, the following string is evaluated as Python code and the
4369 ``cbor:``, the following string is evaluated as Python code and the
4355 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
4370 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
4356 as a Python byte string literal.
4371 as a Python byte string literal.
4357 """
4372 """
4358 opts = pycompat.byteskwargs(opts)
4373 opts = pycompat.byteskwargs(opts)
4359
4374
4360 if opts[b'localssh'] and not repo:
4375 if opts[b'localssh'] and not repo:
4361 raise error.Abort(_(b'--localssh requires a repository'))
4376 raise error.Abort(_(b'--localssh requires a repository'))
4362
4377
4363 if opts[b'peer'] and opts[b'peer'] not in (
4378 if opts[b'peer'] and opts[b'peer'] not in (
4364 b'raw',
4379 b'raw',
4365 b'http2',
4380 b'http2',
4366 b'ssh1',
4381 b'ssh1',
4367 b'ssh2',
4382 b'ssh2',
4368 ):
4383 ):
4369 raise error.Abort(
4384 raise error.Abort(
4370 _(b'invalid value for --peer'),
4385 _(b'invalid value for --peer'),
4371 hint=_(b'valid values are "raw", "ssh1", and "ssh2"'),
4386 hint=_(b'valid values are "raw", "ssh1", and "ssh2"'),
4372 )
4387 )
4373
4388
4374 if path and opts[b'localssh']:
4389 if path and opts[b'localssh']:
4375 raise error.Abort(_(b'cannot specify --localssh with an explicit path'))
4390 raise error.Abort(_(b'cannot specify --localssh with an explicit path'))
4376
4391
4377 if ui.interactive():
4392 if ui.interactive():
4378 ui.write(_(b'(waiting for commands on stdin)\n'))
4393 ui.write(_(b'(waiting for commands on stdin)\n'))
4379
4394
4380 blocks = list(_parsewirelangblocks(ui.fin))
4395 blocks = list(_parsewirelangblocks(ui.fin))
4381
4396
4382 proc = None
4397 proc = None
4383 stdin = None
4398 stdin = None
4384 stdout = None
4399 stdout = None
4385 stderr = None
4400 stderr = None
4386 opener = None
4401 opener = None
4387
4402
4388 if opts[b'localssh']:
4403 if opts[b'localssh']:
4389 # We start the SSH server in its own process so there is process
4404 # We start the SSH server in its own process so there is process
4390 # separation. This prevents a whole class of potential bugs around
4405 # separation. This prevents a whole class of potential bugs around
4391 # shared state from interfering with server operation.
4406 # shared state from interfering with server operation.
4392 args = procutil.hgcmd() + [
4407 args = procutil.hgcmd() + [
4393 b'-R',
4408 b'-R',
4394 repo.root,
4409 repo.root,
4395 b'debugserve',
4410 b'debugserve',
4396 b'--sshstdio',
4411 b'--sshstdio',
4397 ]
4412 ]
4398 proc = subprocess.Popen(
4413 proc = subprocess.Popen(
4399 pycompat.rapply(procutil.tonativestr, args),
4414 pycompat.rapply(procutil.tonativestr, args),
4400 stdin=subprocess.PIPE,
4415 stdin=subprocess.PIPE,
4401 stdout=subprocess.PIPE,
4416 stdout=subprocess.PIPE,
4402 stderr=subprocess.PIPE,
4417 stderr=subprocess.PIPE,
4403 bufsize=0,
4418 bufsize=0,
4404 )
4419 )
4405
4420
4406 stdin = proc.stdin
4421 stdin = proc.stdin
4407 stdout = proc.stdout
4422 stdout = proc.stdout
4408 stderr = proc.stderr
4423 stderr = proc.stderr
4409
4424
4410 # We turn the pipes into observers so we can log I/O.
4425 # We turn the pipes into observers so we can log I/O.
4411 if ui.verbose or opts[b'peer'] == b'raw':
4426 if ui.verbose or opts[b'peer'] == b'raw':
4412 stdin = util.makeloggingfileobject(
4427 stdin = util.makeloggingfileobject(
4413 ui, proc.stdin, b'i', logdata=True
4428 ui, proc.stdin, b'i', logdata=True
4414 )
4429 )
4415 stdout = util.makeloggingfileobject(
4430 stdout = util.makeloggingfileobject(
4416 ui, proc.stdout, b'o', logdata=True
4431 ui, proc.stdout, b'o', logdata=True
4417 )
4432 )
4418 stderr = util.makeloggingfileobject(
4433 stderr = util.makeloggingfileobject(
4419 ui, proc.stderr, b'e', logdata=True
4434 ui, proc.stderr, b'e', logdata=True
4420 )
4435 )
4421
4436
4422 # --localssh also implies the peer connection settings.
4437 # --localssh also implies the peer connection settings.
4423
4438
4424 url = b'ssh://localserver'
4439 url = b'ssh://localserver'
4425 autoreadstderr = not opts[b'noreadstderr']
4440 autoreadstderr = not opts[b'noreadstderr']
4426
4441
4427 if opts[b'peer'] == b'ssh1':
4442 if opts[b'peer'] == b'ssh1':
4428 ui.write(_(b'creating ssh peer for wire protocol version 1\n'))
4443 ui.write(_(b'creating ssh peer for wire protocol version 1\n'))
4429 peer = sshpeer.sshv1peer(
4444 peer = sshpeer.sshv1peer(
4430 ui,
4445 ui,
4431 url,
4446 url,
4432 proc,
4447 proc,
4433 stdin,
4448 stdin,
4434 stdout,
4449 stdout,
4435 stderr,
4450 stderr,
4436 None,
4451 None,
4437 autoreadstderr=autoreadstderr,
4452 autoreadstderr=autoreadstderr,
4438 )
4453 )
4439 elif opts[b'peer'] == b'ssh2':
4454 elif opts[b'peer'] == b'ssh2':
4440 ui.write(_(b'creating ssh peer for wire protocol version 2\n'))
4455 ui.write(_(b'creating ssh peer for wire protocol version 2\n'))
4441 peer = sshpeer.sshv2peer(
4456 peer = sshpeer.sshv2peer(
4442 ui,
4457 ui,
4443 url,
4458 url,
4444 proc,
4459 proc,
4445 stdin,
4460 stdin,
4446 stdout,
4461 stdout,
4447 stderr,
4462 stderr,
4448 None,
4463 None,
4449 autoreadstderr=autoreadstderr,
4464 autoreadstderr=autoreadstderr,
4450 )
4465 )
4451 elif opts[b'peer'] == b'raw':
4466 elif opts[b'peer'] == b'raw':
4452 ui.write(_(b'using raw connection to peer\n'))
4467 ui.write(_(b'using raw connection to peer\n'))
4453 peer = None
4468 peer = None
4454 else:
4469 else:
4455 ui.write(_(b'creating ssh peer from handshake results\n'))
4470 ui.write(_(b'creating ssh peer from handshake results\n'))
4456 peer = sshpeer.makepeer(
4471 peer = sshpeer.makepeer(
4457 ui,
4472 ui,
4458 url,
4473 url,
4459 proc,
4474 proc,
4460 stdin,
4475 stdin,
4461 stdout,
4476 stdout,
4462 stderr,
4477 stderr,
4463 autoreadstderr=autoreadstderr,
4478 autoreadstderr=autoreadstderr,
4464 )
4479 )
4465
4480
4466 elif path:
4481 elif path:
4467 # We bypass hg.peer() so we can proxy the sockets.
4482 # We bypass hg.peer() so we can proxy the sockets.
4468 # TODO consider not doing this because we skip
4483 # TODO consider not doing this because we skip
4469 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
4484 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
4470 u = util.url(path)
4485 u = util.url(path)
4471 if u.scheme != b'http':
4486 if u.scheme != b'http':
4472 raise error.Abort(_(b'only http:// paths are currently supported'))
4487 raise error.Abort(_(b'only http:// paths are currently supported'))
4473
4488
4474 url, authinfo = u.authinfo()
4489 url, authinfo = u.authinfo()
4475 openerargs = {
4490 openerargs = {
4476 'useragent': b'Mercurial debugwireproto',
4491 'useragent': b'Mercurial debugwireproto',
4477 }
4492 }
4478
4493
4479 # Turn pipes/sockets into observers so we can log I/O.
4494 # Turn pipes/sockets into observers so we can log I/O.
4480 if ui.verbose:
4495 if ui.verbose:
4481 openerargs.update(
4496 openerargs.update(
4482 {
4497 {
4483 'loggingfh': ui,
4498 'loggingfh': ui,
4484 'loggingname': b's',
4499 'loggingname': b's',
4485 'loggingopts': {
4500 'loggingopts': {
4486 'logdata': True,
4501 'logdata': True,
4487 'logdataapis': False,
4502 'logdataapis': False,
4488 },
4503 },
4489 }
4504 }
4490 )
4505 )
4491
4506
4492 if ui.debugflag:
4507 if ui.debugflag:
4493 openerargs['loggingopts']['logdataapis'] = True
4508 openerargs['loggingopts']['logdataapis'] = True
4494
4509
4495 # Don't send default headers when in raw mode. This allows us to
4510 # Don't send default headers when in raw mode. This allows us to
4496 # bypass most of the behavior of our URL handling code so we can
4511 # bypass most of the behavior of our URL handling code so we can
4497 # have near complete control over what's sent on the wire.
4512 # have near complete control over what's sent on the wire.
4498 if opts[b'peer'] == b'raw':
4513 if opts[b'peer'] == b'raw':
4499 openerargs['sendaccept'] = False
4514 openerargs['sendaccept'] = False
4500
4515
4501 opener = urlmod.opener(ui, authinfo, **openerargs)
4516 opener = urlmod.opener(ui, authinfo, **openerargs)
4502
4517
4503 if opts[b'peer'] == b'http2':
4518 if opts[b'peer'] == b'http2':
4504 ui.write(_(b'creating http peer for wire protocol version 2\n'))
4519 ui.write(_(b'creating http peer for wire protocol version 2\n'))
4505 # We go through makepeer() because we need an API descriptor for
4520 # We go through makepeer() because we need an API descriptor for
4506 # the peer instance to be useful.
4521 # the peer instance to be useful.
4507 with ui.configoverride(
4522 with ui.configoverride(
4508 {(b'experimental', b'httppeer.advertise-v2'): True}
4523 {(b'experimental', b'httppeer.advertise-v2'): True}
4509 ):
4524 ):
4510 if opts[b'nologhandshake']:
4525 if opts[b'nologhandshake']:
4511 ui.pushbuffer()
4526 ui.pushbuffer()
4512
4527
4513 peer = httppeer.makepeer(ui, path, opener=opener)
4528 peer = httppeer.makepeer(ui, path, opener=opener)
4514
4529
4515 if opts[b'nologhandshake']:
4530 if opts[b'nologhandshake']:
4516 ui.popbuffer()
4531 ui.popbuffer()
4517
4532
4518 if not isinstance(peer, httppeer.httpv2peer):
4533 if not isinstance(peer, httppeer.httpv2peer):
4519 raise error.Abort(
4534 raise error.Abort(
4520 _(
4535 _(
4521 b'could not instantiate HTTP peer for '
4536 b'could not instantiate HTTP peer for '
4522 b'wire protocol version 2'
4537 b'wire protocol version 2'
4523 ),
4538 ),
4524 hint=_(
4539 hint=_(
4525 b'the server may not have the feature '
4540 b'the server may not have the feature '
4526 b'enabled or is not allowing this '
4541 b'enabled or is not allowing this '
4527 b'client version'
4542 b'client version'
4528 ),
4543 ),
4529 )
4544 )
4530
4545
4531 elif opts[b'peer'] == b'raw':
4546 elif opts[b'peer'] == b'raw':
4532 ui.write(_(b'using raw connection to peer\n'))
4547 ui.write(_(b'using raw connection to peer\n'))
4533 peer = None
4548 peer = None
4534 elif opts[b'peer']:
4549 elif opts[b'peer']:
4535 raise error.Abort(
4550 raise error.Abort(
4536 _(b'--peer %s not supported with HTTP peers') % opts[b'peer']
4551 _(b'--peer %s not supported with HTTP peers') % opts[b'peer']
4537 )
4552 )
4538 else:
4553 else:
4539 peer = httppeer.makepeer(ui, path, opener=opener)
4554 peer = httppeer.makepeer(ui, path, opener=opener)
4540
4555
4541 # We /could/ populate stdin/stdout with sock.makefile()...
4556 # We /could/ populate stdin/stdout with sock.makefile()...
4542 else:
4557 else:
4543 raise error.Abort(_(b'unsupported connection configuration'))
4558 raise error.Abort(_(b'unsupported connection configuration'))
4544
4559
4545 batchedcommands = None
4560 batchedcommands = None
4546
4561
4547 # Now perform actions based on the parsed wire language instructions.
4562 # Now perform actions based on the parsed wire language instructions.
4548 for action, lines in blocks:
4563 for action, lines in blocks:
4549 if action in (b'raw', b'raw+'):
4564 if action in (b'raw', b'raw+'):
4550 if not stdin:
4565 if not stdin:
4551 raise error.Abort(_(b'cannot call raw/raw+ on this peer'))
4566 raise error.Abort(_(b'cannot call raw/raw+ on this peer'))
4552
4567
4553 # Concatenate the data together.
4568 # Concatenate the data together.
4554 data = b''.join(l.lstrip() for l in lines)
4569 data = b''.join(l.lstrip() for l in lines)
4555 data = stringutil.unescapestr(data)
4570 data = stringutil.unescapestr(data)
4556 stdin.write(data)
4571 stdin.write(data)
4557
4572
4558 if action == b'raw+':
4573 if action == b'raw+':
4559 stdin.flush()
4574 stdin.flush()
4560 elif action == b'flush':
4575 elif action == b'flush':
4561 if not stdin:
4576 if not stdin:
4562 raise error.Abort(_(b'cannot call flush on this peer'))
4577 raise error.Abort(_(b'cannot call flush on this peer'))
4563 stdin.flush()
4578 stdin.flush()
4564 elif action.startswith(b'command'):
4579 elif action.startswith(b'command'):
4565 if not peer:
4580 if not peer:
4566 raise error.Abort(
4581 raise error.Abort(
4567 _(
4582 _(
4568 b'cannot send commands unless peer instance '
4583 b'cannot send commands unless peer instance '
4569 b'is available'
4584 b'is available'
4570 )
4585 )
4571 )
4586 )
4572
4587
4573 command = action.split(b' ', 1)[1]
4588 command = action.split(b' ', 1)[1]
4574
4589
4575 args = {}
4590 args = {}
4576 for line in lines:
4591 for line in lines:
4577 # We need to allow empty values.
4592 # We need to allow empty values.
4578 fields = line.lstrip().split(b' ', 1)
4593 fields = line.lstrip().split(b' ', 1)
4579 if len(fields) == 1:
4594 if len(fields) == 1:
4580 key = fields[0]
4595 key = fields[0]
4581 value = b''
4596 value = b''
4582 else:
4597 else:
4583 key, value = fields
4598 key, value = fields
4584
4599
4585 if value.startswith(b'eval:'):
4600 if value.startswith(b'eval:'):
4586 value = stringutil.evalpythonliteral(value[5:])
4601 value = stringutil.evalpythonliteral(value[5:])
4587 else:
4602 else:
4588 value = stringutil.unescapestr(value)
4603 value = stringutil.unescapestr(value)
4589
4604
4590 args[key] = value
4605 args[key] = value
4591
4606
4592 if batchedcommands is not None:
4607 if batchedcommands is not None:
4593 batchedcommands.append((command, args))
4608 batchedcommands.append((command, args))
4594 continue
4609 continue
4595
4610
4596 ui.status(_(b'sending %s command\n') % command)
4611 ui.status(_(b'sending %s command\n') % command)
4597
4612
4598 if b'PUSHFILE' in args:
4613 if b'PUSHFILE' in args:
4599 with open(args[b'PUSHFILE'], 'rb') as fh:
4614 with open(args[b'PUSHFILE'], 'rb') as fh:
4600 del args[b'PUSHFILE']
4615 del args[b'PUSHFILE']
4601 res, output = peer._callpush(
4616 res, output = peer._callpush(
4602 command, fh, **pycompat.strkwargs(args)
4617 command, fh, **pycompat.strkwargs(args)
4603 )
4618 )
4604 ui.status(_(b'result: %s\n') % stringutil.escapestr(res))
4619 ui.status(_(b'result: %s\n') % stringutil.escapestr(res))
4605 ui.status(
4620 ui.status(
4606 _(b'remote output: %s\n') % stringutil.escapestr(output)
4621 _(b'remote output: %s\n') % stringutil.escapestr(output)
4607 )
4622 )
4608 else:
4623 else:
4609 with peer.commandexecutor() as e:
4624 with peer.commandexecutor() as e:
4610 res = e.callcommand(command, args).result()
4625 res = e.callcommand(command, args).result()
4611
4626
4612 if isinstance(res, wireprotov2peer.commandresponse):
4627 if isinstance(res, wireprotov2peer.commandresponse):
4613 val = res.objects()
4628 val = res.objects()
4614 ui.status(
4629 ui.status(
4615 _(b'response: %s\n')
4630 _(b'response: %s\n')
4616 % stringutil.pprint(val, bprefix=True, indent=2)
4631 % stringutil.pprint(val, bprefix=True, indent=2)
4617 )
4632 )
4618 else:
4633 else:
4619 ui.status(
4634 ui.status(
4620 _(b'response: %s\n')
4635 _(b'response: %s\n')
4621 % stringutil.pprint(res, bprefix=True, indent=2)
4636 % stringutil.pprint(res, bprefix=True, indent=2)
4622 )
4637 )
4623
4638
4624 elif action == b'batchbegin':
4639 elif action == b'batchbegin':
4625 if batchedcommands is not None:
4640 if batchedcommands is not None:
4626 raise error.Abort(_(b'nested batchbegin not allowed'))
4641 raise error.Abort(_(b'nested batchbegin not allowed'))
4627
4642
4628 batchedcommands = []
4643 batchedcommands = []
4629 elif action == b'batchsubmit':
4644 elif action == b'batchsubmit':
4630 # There is a batching API we could go through. But it would be
4645 # There is a batching API we could go through. But it would be
4631 # difficult to normalize requests into function calls. It is easier
4646 # difficult to normalize requests into function calls. It is easier
4632 # to bypass this layer and normalize to commands + args.
4647 # to bypass this layer and normalize to commands + args.
4633 ui.status(
4648 ui.status(
4634 _(b'sending batch with %d sub-commands\n')
4649 _(b'sending batch with %d sub-commands\n')
4635 % len(batchedcommands)
4650 % len(batchedcommands)
4636 )
4651 )
4637 assert peer is not None
4652 assert peer is not None
4638 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
4653 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
4639 ui.status(
4654 ui.status(
4640 _(b'response #%d: %s\n') % (i, stringutil.escapestr(chunk))
4655 _(b'response #%d: %s\n') % (i, stringutil.escapestr(chunk))
4641 )
4656 )
4642
4657
4643 batchedcommands = None
4658 batchedcommands = None
4644
4659
4645 elif action.startswith(b'httprequest '):
4660 elif action.startswith(b'httprequest '):
4646 if not opener:
4661 if not opener:
4647 raise error.Abort(
4662 raise error.Abort(
4648 _(b'cannot use httprequest without an HTTP peer')
4663 _(b'cannot use httprequest without an HTTP peer')
4649 )
4664 )
4650
4665
4651 request = action.split(b' ', 2)
4666 request = action.split(b' ', 2)
4652 if len(request) != 3:
4667 if len(request) != 3:
4653 raise error.Abort(
4668 raise error.Abort(
4654 _(
4669 _(
4655 b'invalid httprequest: expected format is '
4670 b'invalid httprequest: expected format is '
4656 b'"httprequest <method> <path>'
4671 b'"httprequest <method> <path>'
4657 )
4672 )
4658 )
4673 )
4659
4674
4660 method, httppath = request[1:]
4675 method, httppath = request[1:]
4661 headers = {}
4676 headers = {}
4662 body = None
4677 body = None
4663 frames = []
4678 frames = []
4664 for line in lines:
4679 for line in lines:
4665 line = line.lstrip()
4680 line = line.lstrip()
4666 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
4681 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
4667 if m:
4682 if m:
4668 # Headers need to use native strings.
4683 # Headers need to use native strings.
4669 key = pycompat.strurl(m.group(1))
4684 key = pycompat.strurl(m.group(1))
4670 value = pycompat.strurl(m.group(2))
4685 value = pycompat.strurl(m.group(2))
4671 headers[key] = value
4686 headers[key] = value
4672 continue
4687 continue
4673
4688
4674 if line.startswith(b'BODYFILE '):
4689 if line.startswith(b'BODYFILE '):
4675 with open(line.split(b' ', 1), b'rb') as fh:
4690 with open(line.split(b' ', 1), b'rb') as fh:
4676 body = fh.read()
4691 body = fh.read()
4677 elif line.startswith(b'frame '):
4692 elif line.startswith(b'frame '):
4678 frame = wireprotoframing.makeframefromhumanstring(
4693 frame = wireprotoframing.makeframefromhumanstring(
4679 line[len(b'frame ') :]
4694 line[len(b'frame ') :]
4680 )
4695 )
4681
4696
4682 frames.append(frame)
4697 frames.append(frame)
4683 else:
4698 else:
4684 raise error.Abort(
4699 raise error.Abort(
4685 _(b'unknown argument to httprequest: %s') % line
4700 _(b'unknown argument to httprequest: %s') % line
4686 )
4701 )
4687
4702
4688 url = path + httppath
4703 url = path + httppath
4689
4704
4690 if frames:
4705 if frames:
4691 body = b''.join(bytes(f) for f in frames)
4706 body = b''.join(bytes(f) for f in frames)
4692
4707
4693 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
4708 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
4694
4709
4695 # urllib.Request insists on using has_data() as a proxy for
4710 # urllib.Request insists on using has_data() as a proxy for
4696 # determining the request method. Override that to use our
4711 # determining the request method. Override that to use our
4697 # explicitly requested method.
4712 # explicitly requested method.
4698 req.get_method = lambda: pycompat.sysstr(method)
4713 req.get_method = lambda: pycompat.sysstr(method)
4699
4714
4700 try:
4715 try:
4701 res = opener.open(req)
4716 res = opener.open(req)
4702 body = res.read()
4717 body = res.read()
4703 except util.urlerr.urlerror as e:
4718 except util.urlerr.urlerror as e:
4704 # read() method must be called, but only exists in Python 2
4719 # read() method must be called, but only exists in Python 2
4705 getattr(e, 'read', lambda: None)()
4720 getattr(e, 'read', lambda: None)()
4706 continue
4721 continue
4707
4722
4708 ct = res.headers.get('Content-Type')
4723 ct = res.headers.get('Content-Type')
4709 if ct == 'application/mercurial-cbor':
4724 if ct == 'application/mercurial-cbor':
4710 ui.write(
4725 ui.write(
4711 _(b'cbor> %s\n')
4726 _(b'cbor> %s\n')
4712 % stringutil.pprint(
4727 % stringutil.pprint(
4713 cborutil.decodeall(body), bprefix=True, indent=2
4728 cborutil.decodeall(body), bprefix=True, indent=2
4714 )
4729 )
4715 )
4730 )
4716
4731
4717 elif action == b'close':
4732 elif action == b'close':
4718 assert peer is not None
4733 assert peer is not None
4719 peer.close()
4734 peer.close()
4720 elif action == b'readavailable':
4735 elif action == b'readavailable':
4721 if not stdout or not stderr:
4736 if not stdout or not stderr:
4722 raise error.Abort(
4737 raise error.Abort(
4723 _(b'readavailable not available on this peer')
4738 _(b'readavailable not available on this peer')
4724 )
4739 )
4725
4740
4726 stdin.close()
4741 stdin.close()
4727 stdout.read()
4742 stdout.read()
4728 stderr.read()
4743 stderr.read()
4729
4744
4730 elif action == b'readline':
4745 elif action == b'readline':
4731 if not stdout:
4746 if not stdout:
4732 raise error.Abort(_(b'readline not available on this peer'))
4747 raise error.Abort(_(b'readline not available on this peer'))
4733 stdout.readline()
4748 stdout.readline()
4734 elif action == b'ereadline':
4749 elif action == b'ereadline':
4735 if not stderr:
4750 if not stderr:
4736 raise error.Abort(_(b'ereadline not available on this peer'))
4751 raise error.Abort(_(b'ereadline not available on this peer'))
4737 stderr.readline()
4752 stderr.readline()
4738 elif action.startswith(b'read '):
4753 elif action.startswith(b'read '):
4739 count = int(action.split(b' ', 1)[1])
4754 count = int(action.split(b' ', 1)[1])
4740 if not stdout:
4755 if not stdout:
4741 raise error.Abort(_(b'read not available on this peer'))
4756 raise error.Abort(_(b'read not available on this peer'))
4742 stdout.read(count)
4757 stdout.read(count)
4743 elif action.startswith(b'eread '):
4758 elif action.startswith(b'eread '):
4744 count = int(action.split(b' ', 1)[1])
4759 count = int(action.split(b' ', 1)[1])
4745 if not stderr:
4760 if not stderr:
4746 raise error.Abort(_(b'eread not available on this peer'))
4761 raise error.Abort(_(b'eread not available on this peer'))
4747 stderr.read(count)
4762 stderr.read(count)
4748 else:
4763 else:
4749 raise error.Abort(_(b'unknown action: %s') % action)
4764 raise error.Abort(_(b'unknown action: %s') % action)
4750
4765
4751 if batchedcommands is not None:
4766 if batchedcommands is not None:
4752 raise error.Abort(_(b'unclosed "batchbegin" request'))
4767 raise error.Abort(_(b'unclosed "batchbegin" request'))
4753
4768
4754 if peer:
4769 if peer:
4755 peer.close()
4770 peer.close()
4756
4771
4757 if proc:
4772 if proc:
4758 proc.kill()
4773 proc.kill()
@@ -1,1514 +1,1533 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import os
12 import os
13 import shutil
13 import shutil
14 import stat
14 import stat
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullhex,
19 nullhex,
20 nullid,
20 nullid,
21 short,
21 short,
22 )
22 )
23 from .pycompat import getattr
23 from .pycompat import getattr
24
24
25 from . import (
25 from . import (
26 bookmarks,
26 bookmarks,
27 bundlerepo,
27 bundlerepo,
28 cacheutil,
28 cacheutil,
29 cmdutil,
29 cmdutil,
30 destutil,
30 destutil,
31 discovery,
31 discovery,
32 error,
32 error,
33 exchange,
33 exchange,
34 extensions,
34 extensions,
35 httppeer,
35 httppeer,
36 localrepo,
36 localrepo,
37 lock,
37 lock,
38 logcmdutil,
38 logcmdutil,
39 logexchange,
39 logexchange,
40 merge as mergemod,
40 merge as mergemod,
41 mergestate as mergestatemod,
41 mergestate as mergestatemod,
42 narrowspec,
42 narrowspec,
43 phases,
43 phases,
44 pycompat,
44 pycompat,
45 requirements,
45 requirements,
46 scmutil,
46 scmutil,
47 sshpeer,
47 sshpeer,
48 statichttprepo,
48 statichttprepo,
49 ui as uimod,
49 ui as uimod,
50 unionrepo,
50 unionrepo,
51 url,
51 url,
52 util,
52 util,
53 verify as verifymod,
53 verify as verifymod,
54 vfs as vfsmod,
54 vfs as vfsmod,
55 )
55 )
56 from .utils import hashutil
56 from .utils import hashutil
57
57
58 release = lock.release
58 release = lock.release
59
59
60 # shared features
60 # shared features
61 sharedbookmarks = b'bookmarks'
61 sharedbookmarks = b'bookmarks'
62
62
63
63
64 def _local(path):
64 def _local(path):
65 path = util.expandpath(util.urllocalpath(path))
65 path = util.expandpath(util.urllocalpath(path))
66
66
67 try:
67 try:
68 # we use os.stat() directly here instead of os.path.isfile()
68 # we use os.stat() directly here instead of os.path.isfile()
69 # because the latter started returning `False` on invalid path
69 # because the latter started returning `False` on invalid path
70 # exceptions starting in 3.8 and we care about handling
70 # exceptions starting in 3.8 and we care about handling
71 # invalid paths specially here.
71 # invalid paths specially here.
72 st = os.stat(path)
72 st = os.stat(path)
73 isfile = stat.S_ISREG(st.st_mode)
73 isfile = stat.S_ISREG(st.st_mode)
74 # Python 2 raises TypeError, Python 3 ValueError.
74 # Python 2 raises TypeError, Python 3 ValueError.
75 except (TypeError, ValueError) as e:
75 except (TypeError, ValueError) as e:
76 raise error.Abort(
76 raise error.Abort(
77 _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
77 _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
78 )
78 )
79 except OSError:
79 except OSError:
80 isfile = False
80 isfile = False
81
81
82 return isfile and bundlerepo or localrepo
82 return isfile and bundlerepo or localrepo
83
83
84
84
85 def addbranchrevs(lrepo, other, branches, revs):
85 def addbranchrevs(lrepo, other, branches, revs):
86 peer = other.peer() # a courtesy to callers using a localrepo for other
86 peer = other.peer() # a courtesy to callers using a localrepo for other
87 hashbranch, branches = branches
87 hashbranch, branches = branches
88 if not hashbranch and not branches:
88 if not hashbranch and not branches:
89 x = revs or None
89 x = revs or None
90 if revs:
90 if revs:
91 y = revs[0]
91 y = revs[0]
92 else:
92 else:
93 y = None
93 y = None
94 return x, y
94 return x, y
95 if revs:
95 if revs:
96 revs = list(revs)
96 revs = list(revs)
97 else:
97 else:
98 revs = []
98 revs = []
99
99
100 if not peer.capable(b'branchmap'):
100 if not peer.capable(b'branchmap'):
101 if branches:
101 if branches:
102 raise error.Abort(_(b"remote branch lookup not supported"))
102 raise error.Abort(_(b"remote branch lookup not supported"))
103 revs.append(hashbranch)
103 revs.append(hashbranch)
104 return revs, revs[0]
104 return revs, revs[0]
105
105
106 with peer.commandexecutor() as e:
106 with peer.commandexecutor() as e:
107 branchmap = e.callcommand(b'branchmap', {}).result()
107 branchmap = e.callcommand(b'branchmap', {}).result()
108
108
109 def primary(branch):
109 def primary(branch):
110 if branch == b'.':
110 if branch == b'.':
111 if not lrepo:
111 if not lrepo:
112 raise error.Abort(_(b"dirstate branch not accessible"))
112 raise error.Abort(_(b"dirstate branch not accessible"))
113 branch = lrepo.dirstate.branch()
113 branch = lrepo.dirstate.branch()
114 if branch in branchmap:
114 if branch in branchmap:
115 revs.extend(hex(r) for r in reversed(branchmap[branch]))
115 revs.extend(hex(r) for r in reversed(branchmap[branch]))
116 return True
116 return True
117 else:
117 else:
118 return False
118 return False
119
119
120 for branch in branches:
120 for branch in branches:
121 if not primary(branch):
121 if not primary(branch):
122 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
122 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
123 if hashbranch:
123 if hashbranch:
124 if not primary(hashbranch):
124 if not primary(hashbranch):
125 revs.append(hashbranch)
125 revs.append(hashbranch)
126 return revs, revs[0]
126 return revs, revs[0]
127
127
128
128
129 def parseurl(path, branches=None):
129 def parseurl(path, branches=None):
130 '''parse url#branch, returning (url, (branch, branches))'''
130 '''parse url#branch, returning (url, (branch, branches))'''
131
131
132 u = util.url(path)
132 u = util.url(path)
133 branch = None
133 branch = None
134 if u.fragment:
134 if u.fragment:
135 branch = u.fragment
135 branch = u.fragment
136 u.fragment = None
136 u.fragment = None
137 return bytes(u), (branch, branches or [])
137 return bytes(u), (branch, branches or [])
138
138
139
139
140 schemes = {
140 schemes = {
141 b'bundle': bundlerepo,
141 b'bundle': bundlerepo,
142 b'union': unionrepo,
142 b'union': unionrepo,
143 b'file': _local,
143 b'file': _local,
144 b'http': httppeer,
144 b'http': httppeer,
145 b'https': httppeer,
145 b'https': httppeer,
146 b'ssh': sshpeer,
146 b'ssh': sshpeer,
147 b'static-http': statichttprepo,
147 b'static-http': statichttprepo,
148 }
148 }
149
149
150
150
151 def _peerlookup(path):
151 def _peerlookup(path):
152 u = util.url(path)
152 u = util.url(path)
153 scheme = u.scheme or b'file'
153 scheme = u.scheme or b'file'
154 thing = schemes.get(scheme) or schemes[b'file']
154 thing = schemes.get(scheme) or schemes[b'file']
155 try:
155 try:
156 return thing(path)
156 return thing(path)
157 except TypeError:
157 except TypeError:
158 # we can't test callable(thing) because 'thing' can be an unloaded
158 # we can't test callable(thing) because 'thing' can be an unloaded
159 # module that implements __call__
159 # module that implements __call__
160 if not util.safehasattr(thing, b'instance'):
160 if not util.safehasattr(thing, b'instance'):
161 raise
161 raise
162 return thing
162 return thing
163
163
164
164
165 def islocal(repo):
165 def islocal(repo):
166 '''return true if repo (or path pointing to repo) is local'''
166 '''return true if repo (or path pointing to repo) is local'''
167 if isinstance(repo, bytes):
167 if isinstance(repo, bytes):
168 try:
168 try:
169 return _peerlookup(repo).islocal(repo)
169 return _peerlookup(repo).islocal(repo)
170 except AttributeError:
170 except AttributeError:
171 return False
171 return False
172 return repo.local()
172 return repo.local()
173
173
174
174
175 def openpath(ui, path, sendaccept=True):
175 def openpath(ui, path, sendaccept=True):
176 '''open path with open if local, url.open if remote'''
176 '''open path with open if local, url.open if remote'''
177 pathurl = util.url(path, parsequery=False, parsefragment=False)
177 pathurl = util.url(path, parsequery=False, parsefragment=False)
178 if pathurl.islocal():
178 if pathurl.islocal():
179 return util.posixfile(pathurl.localpath(), b'rb')
179 return util.posixfile(pathurl.localpath(), b'rb')
180 else:
180 else:
181 return url.open(ui, path, sendaccept=sendaccept)
181 return url.open(ui, path, sendaccept=sendaccept)
182
182
183
183
184 # a list of (ui, repo) functions called for wire peer initialization
184 # a list of (ui, repo) functions called for wire peer initialization
185 wirepeersetupfuncs = []
185 wirepeersetupfuncs = []
186
186
187
187
188 def _peerorrepo(
188 def _peerorrepo(
189 ui, path, create=False, presetupfuncs=None, intents=None, createopts=None
189 ui, path, create=False, presetupfuncs=None, intents=None, createopts=None
190 ):
190 ):
191 """return a repository object for the specified path"""
191 """return a repository object for the specified path"""
192 obj = _peerlookup(path).instance(
192 obj = _peerlookup(path).instance(
193 ui, path, create, intents=intents, createopts=createopts
193 ui, path, create, intents=intents, createopts=createopts
194 )
194 )
195 ui = getattr(obj, "ui", ui)
195 ui = getattr(obj, "ui", ui)
196 for f in presetupfuncs or []:
196 for f in presetupfuncs or []:
197 f(ui, obj)
197 f(ui, obj)
198 ui.log(b'extension', b'- executing reposetup hooks\n')
198 ui.log(b'extension', b'- executing reposetup hooks\n')
199 with util.timedcm('all reposetup') as allreposetupstats:
199 with util.timedcm('all reposetup') as allreposetupstats:
200 for name, module in extensions.extensions(ui):
200 for name, module in extensions.extensions(ui):
201 ui.log(b'extension', b' - running reposetup for %s\n', name)
201 ui.log(b'extension', b' - running reposetup for %s\n', name)
202 hook = getattr(module, 'reposetup', None)
202 hook = getattr(module, 'reposetup', None)
203 if hook:
203 if hook:
204 with util.timedcm('reposetup %r', name) as stats:
204 with util.timedcm('reposetup %r', name) as stats:
205 hook(ui, obj)
205 hook(ui, obj)
206 ui.log(
206 ui.log(
207 b'extension', b' > reposetup for %s took %s\n', name, stats
207 b'extension', b' > reposetup for %s took %s\n', name, stats
208 )
208 )
209 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
209 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
210 if not obj.local():
210 if not obj.local():
211 for f in wirepeersetupfuncs:
211 for f in wirepeersetupfuncs:
212 f(ui, obj)
212 f(ui, obj)
213 return obj
213 return obj
214
214
215
215
216 def repository(
216 def repository(
217 ui,
217 ui,
218 path=b'',
218 path=b'',
219 create=False,
219 create=False,
220 presetupfuncs=None,
220 presetupfuncs=None,
221 intents=None,
221 intents=None,
222 createopts=None,
222 createopts=None,
223 ):
223 ):
224 """return a repository object for the specified path"""
224 """return a repository object for the specified path"""
225 peer = _peerorrepo(
225 peer = _peerorrepo(
226 ui,
226 ui,
227 path,
227 path,
228 create,
228 create,
229 presetupfuncs=presetupfuncs,
229 presetupfuncs=presetupfuncs,
230 intents=intents,
230 intents=intents,
231 createopts=createopts,
231 createopts=createopts,
232 )
232 )
233 repo = peer.local()
233 repo = peer.local()
234 if not repo:
234 if not repo:
235 raise error.Abort(
235 raise error.Abort(
236 _(b"repository '%s' is not local") % (path or peer.url())
236 _(b"repository '%s' is not local") % (path or peer.url())
237 )
237 )
238 return repo.filtered(b'visible')
238 return repo.filtered(b'visible')
239
239
240
240
241 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
241 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
242 '''return a repository peer for the specified path'''
242 '''return a repository peer for the specified path'''
243 rui = remoteui(uiorrepo, opts)
243 rui = remoteui(uiorrepo, opts)
244 return _peerorrepo(
244 return _peerorrepo(
245 rui, path, create, intents=intents, createopts=createopts
245 rui, path, create, intents=intents, createopts=createopts
246 ).peer()
246 ).peer()
247
247
248
248
249 def defaultdest(source):
249 def defaultdest(source):
250 """return default destination of clone if none is given
250 """return default destination of clone if none is given
251
251
252 >>> defaultdest(b'foo')
252 >>> defaultdest(b'foo')
253 'foo'
253 'foo'
254 >>> defaultdest(b'/foo/bar')
254 >>> defaultdest(b'/foo/bar')
255 'bar'
255 'bar'
256 >>> defaultdest(b'/')
256 >>> defaultdest(b'/')
257 ''
257 ''
258 >>> defaultdest(b'')
258 >>> defaultdest(b'')
259 ''
259 ''
260 >>> defaultdest(b'http://example.org/')
260 >>> defaultdest(b'http://example.org/')
261 ''
261 ''
262 >>> defaultdest(b'http://example.org/foo/')
262 >>> defaultdest(b'http://example.org/foo/')
263 'foo'
263 'foo'
264 """
264 """
265 path = util.url(source).path
265 path = util.url(source).path
266 if not path:
266 if not path:
267 return b''
267 return b''
268 return os.path.basename(os.path.normpath(path))
268 return os.path.basename(os.path.normpath(path))
269
269
270
270
271 def sharedreposource(repo):
271 def sharedreposource(repo):
272 """Returns repository object for source repository of a shared repo.
272 """Returns repository object for source repository of a shared repo.
273
273
274 If repo is not a shared repository, returns None.
274 If repo is not a shared repository, returns None.
275 """
275 """
276 if repo.sharedpath == repo.path:
276 if repo.sharedpath == repo.path:
277 return None
277 return None
278
278
279 if util.safehasattr(repo, b'srcrepo') and repo.srcrepo:
279 if util.safehasattr(repo, b'srcrepo') and repo.srcrepo:
280 return repo.srcrepo
280 return repo.srcrepo
281
281
282 # the sharedpath always ends in the .hg; we want the path to the repo
282 # the sharedpath always ends in the .hg; we want the path to the repo
283 source = repo.vfs.split(repo.sharedpath)[0]
283 source = repo.vfs.split(repo.sharedpath)[0]
284 srcurl, branches = parseurl(source)
284 srcurl, branches = parseurl(source)
285 srcrepo = repository(repo.ui, srcurl)
285 srcrepo = repository(repo.ui, srcurl)
286 repo.srcrepo = srcrepo
286 repo.srcrepo = srcrepo
287 return srcrepo
287 return srcrepo
288
288
289
289
290 def share(
290 def share(
291 ui,
291 ui,
292 source,
292 source,
293 dest=None,
293 dest=None,
294 update=True,
294 update=True,
295 bookmarks=True,
295 bookmarks=True,
296 defaultpath=None,
296 defaultpath=None,
297 relative=False,
297 relative=False,
298 ):
298 ):
299 '''create a shared repository'''
299 '''create a shared repository'''
300
300
301 if not islocal(source):
301 if not islocal(source):
302 raise error.Abort(_(b'can only share local repositories'))
302 raise error.Abort(_(b'can only share local repositories'))
303
303
304 if not dest:
304 if not dest:
305 dest = defaultdest(source)
305 dest = defaultdest(source)
306 else:
306 else:
307 dest = ui.expandpath(dest)
307 dest = ui.expandpath(dest)
308
308
309 if isinstance(source, bytes):
309 if isinstance(source, bytes):
310 origsource = ui.expandpath(source)
310 origsource = ui.expandpath(source)
311 source, branches = parseurl(origsource)
311 source, branches = parseurl(origsource)
312 srcrepo = repository(ui, source)
312 srcrepo = repository(ui, source)
313 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
313 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
314 else:
314 else:
315 srcrepo = source.local()
315 srcrepo = source.local()
316 checkout = None
316 checkout = None
317
317
318 shareditems = set()
318 shareditems = set()
319 if bookmarks:
319 if bookmarks:
320 shareditems.add(sharedbookmarks)
320 shareditems.add(sharedbookmarks)
321
321
322 r = repository(
322 r = repository(
323 ui,
323 ui,
324 dest,
324 dest,
325 create=True,
325 create=True,
326 createopts={
326 createopts={
327 b'sharedrepo': srcrepo,
327 b'sharedrepo': srcrepo,
328 b'sharedrelative': relative,
328 b'sharedrelative': relative,
329 b'shareditems': shareditems,
329 b'shareditems': shareditems,
330 },
330 },
331 )
331 )
332
332
333 postshare(srcrepo, r, defaultpath=defaultpath)
333 postshare(srcrepo, r, defaultpath=defaultpath)
334 r = repository(ui, dest)
334 r = repository(ui, dest)
335 _postshareupdate(r, update, checkout=checkout)
335 _postshareupdate(r, update, checkout=checkout)
336 return r
336 return r
337
337
338
338
339 def _prependsourcehgrc(repo):
339 def _prependsourcehgrc(repo):
340 """copies the source repo config and prepend it in current repo .hg/hgrc
340 """copies the source repo config and prepend it in current repo .hg/hgrc
341 on unshare. This is only done if the share was perfomed using share safe
341 on unshare. This is only done if the share was perfomed using share safe
342 method where we share config of source in shares"""
342 method where we share config of source in shares"""
343 srcvfs = vfsmod.vfs(repo.sharedpath)
343 srcvfs = vfsmod.vfs(repo.sharedpath)
344 dstvfs = vfsmod.vfs(repo.path)
344 dstvfs = vfsmod.vfs(repo.path)
345
345
346 if not srcvfs.exists(b'hgrc'):
346 if not srcvfs.exists(b'hgrc'):
347 return
347 return
348
348
349 currentconfig = b''
349 currentconfig = b''
350 if dstvfs.exists(b'hgrc'):
350 if dstvfs.exists(b'hgrc'):
351 currentconfig = dstvfs.read(b'hgrc')
351 currentconfig = dstvfs.read(b'hgrc')
352
352
353 with dstvfs(b'hgrc', b'wb') as fp:
353 with dstvfs(b'hgrc', b'wb') as fp:
354 sourceconfig = srcvfs.read(b'hgrc')
354 sourceconfig = srcvfs.read(b'hgrc')
355 fp.write(b"# Config copied from shared source\n")
355 fp.write(b"# Config copied from shared source\n")
356 fp.write(sourceconfig)
356 fp.write(sourceconfig)
357 fp.write(b'\n')
357 fp.write(b'\n')
358 fp.write(currentconfig)
358 fp.write(currentconfig)
359
359
360
360
361 def unshare(ui, repo):
361 def unshare(ui, repo):
362 """convert a shared repository to a normal one
362 """convert a shared repository to a normal one
363
363
364 Copy the store data to the repo and remove the sharedpath data.
364 Copy the store data to the repo and remove the sharedpath data.
365
365
366 Returns a new repository object representing the unshared repository.
366 Returns a new repository object representing the unshared repository.
367
367
368 The passed repository object is not usable after this function is
368 The passed repository object is not usable after this function is
369 called.
369 called.
370 """
370 """
371
371
372 with repo.lock():
372 with repo.lock():
373 # we use locks here because if we race with commit, we
373 # we use locks here because if we race with commit, we
374 # can end up with extra data in the cloned revlogs that's
374 # can end up with extra data in the cloned revlogs that's
375 # not pointed to by changesets, thus causing verify to
375 # not pointed to by changesets, thus causing verify to
376 # fail
376 # fail
377 destlock = copystore(ui, repo, repo.path)
377 destlock = copystore(ui, repo, repo.path)
378 with destlock or util.nullcontextmanager():
378 with destlock or util.nullcontextmanager():
379 if requirements.SHARESAFE_REQUIREMENT in repo.requirements:
379 if requirements.SHARESAFE_REQUIREMENT in repo.requirements:
380 # we were sharing .hg/hgrc of the share source with the current
380 # we were sharing .hg/hgrc of the share source with the current
381 # repo. We need to copy that while unsharing otherwise it can
381 # repo. We need to copy that while unsharing otherwise it can
382 # disable hooks and other checks
382 # disable hooks and other checks
383 _prependsourcehgrc(repo)
383 _prependsourcehgrc(repo)
384
384
385 sharefile = repo.vfs.join(b'sharedpath')
385 sharefile = repo.vfs.join(b'sharedpath')
386 util.rename(sharefile, sharefile + b'.old')
386 util.rename(sharefile, sharefile + b'.old')
387
387
388 repo.requirements.discard(requirements.SHARED_REQUIREMENT)
388 repo.requirements.discard(requirements.SHARED_REQUIREMENT)
389 repo.requirements.discard(requirements.RELATIVE_SHARED_REQUIREMENT)
389 repo.requirements.discard(requirements.RELATIVE_SHARED_REQUIREMENT)
390 scmutil.writereporequirements(repo)
390 scmutil.writereporequirements(repo)
391
391
392 # Removing share changes some fundamental properties of the repo instance.
392 # Removing share changes some fundamental properties of the repo instance.
393 # So we instantiate a new repo object and operate on it rather than
393 # So we instantiate a new repo object and operate on it rather than
394 # try to keep the existing repo usable.
394 # try to keep the existing repo usable.
395 newrepo = repository(repo.baseui, repo.root, create=False)
395 newrepo = repository(repo.baseui, repo.root, create=False)
396
396
397 # TODO: figure out how to access subrepos that exist, but were previously
397 # TODO: figure out how to access subrepos that exist, but were previously
398 # removed from .hgsub
398 # removed from .hgsub
399 c = newrepo[b'.']
399 c = newrepo[b'.']
400 subs = c.substate
400 subs = c.substate
401 for s in sorted(subs):
401 for s in sorted(subs):
402 c.sub(s).unshare()
402 c.sub(s).unshare()
403
403
404 localrepo.poisonrepository(repo)
404 localrepo.poisonrepository(repo)
405
405
406 return newrepo
406 return newrepo
407
407
408
408
409 def postshare(sourcerepo, destrepo, defaultpath=None):
409 def postshare(sourcerepo, destrepo, defaultpath=None):
410 """Called after a new shared repo is created.
410 """Called after a new shared repo is created.
411
411
412 The new repo only has a requirements file and pointer to the source.
412 The new repo only has a requirements file and pointer to the source.
413 This function configures additional shared data.
413 This function configures additional shared data.
414
414
415 Extensions can wrap this function and write additional entries to
415 Extensions can wrap this function and write additional entries to
416 destrepo/.hg/shared to indicate additional pieces of data to be shared.
416 destrepo/.hg/shared to indicate additional pieces of data to be shared.
417 """
417 """
418 default = defaultpath or sourcerepo.ui.config(b'paths', b'default')
418 default = defaultpath or sourcerepo.ui.config(b'paths', b'default')
419 if default:
419 if default:
420 template = b'[paths]\ndefault = %s\n'
420 template = b'[paths]\ndefault = %s\n'
421 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % default))
421 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % default))
422 if requirements.NARROW_REQUIREMENT in sourcerepo.requirements:
422 if requirements.NARROW_REQUIREMENT in sourcerepo.requirements:
423 with destrepo.wlock():
423 with destrepo.wlock():
424 narrowspec.copytoworkingcopy(destrepo)
424 narrowspec.copytoworkingcopy(destrepo)
425
425
426
426
427 def _postshareupdate(repo, update, checkout=None):
427 def _postshareupdate(repo, update, checkout=None):
428 """Maybe perform a working directory update after a shared repo is created.
428 """Maybe perform a working directory update after a shared repo is created.
429
429
430 ``update`` can be a boolean or a revision to update to.
430 ``update`` can be a boolean or a revision to update to.
431 """
431 """
432 if not update:
432 if not update:
433 return
433 return
434
434
435 repo.ui.status(_(b"updating working directory\n"))
435 repo.ui.status(_(b"updating working directory\n"))
436 if update is not True:
436 if update is not True:
437 checkout = update
437 checkout = update
438 for test in (checkout, b'default', b'tip'):
438 for test in (checkout, b'default', b'tip'):
439 if test is None:
439 if test is None:
440 continue
440 continue
441 try:
441 try:
442 uprev = repo.lookup(test)
442 uprev = repo.lookup(test)
443 break
443 break
444 except error.RepoLookupError:
444 except error.RepoLookupError:
445 continue
445 continue
446 _update(repo, uprev)
446 _update(repo, uprev)
447
447
448
448
449 def copystore(ui, srcrepo, destpath):
449 def copystore(ui, srcrepo, destpath):
450 """copy files from store of srcrepo in destpath
450 """copy files from store of srcrepo in destpath
451
451
452 returns destlock
452 returns destlock
453 """
453 """
454 destlock = None
454 destlock = None
455 try:
455 try:
456 hardlink = None
456 hardlink = None
457 topic = _(b'linking') if hardlink else _(b'copying')
457 topic = _(b'linking') if hardlink else _(b'copying')
458 with ui.makeprogress(topic, unit=_(b'files')) as progress:
458 with ui.makeprogress(topic, unit=_(b'files')) as progress:
459 num = 0
459 num = 0
460 srcpublishing = srcrepo.publishing()
460 srcpublishing = srcrepo.publishing()
461 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
461 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
462 dstvfs = vfsmod.vfs(destpath)
462 dstvfs = vfsmod.vfs(destpath)
463 for f in srcrepo.store.copylist():
463 for f in srcrepo.store.copylist():
464 if srcpublishing and f.endswith(b'phaseroots'):
464 if srcpublishing and f.endswith(b'phaseroots'):
465 continue
465 continue
466 dstbase = os.path.dirname(f)
466 dstbase = os.path.dirname(f)
467 if dstbase and not dstvfs.exists(dstbase):
467 if dstbase and not dstvfs.exists(dstbase):
468 dstvfs.mkdir(dstbase)
468 dstvfs.mkdir(dstbase)
469 if srcvfs.exists(f):
469 if srcvfs.exists(f):
470 if f.endswith(b'data'):
470 if f.endswith(b'data'):
471 # 'dstbase' may be empty (e.g. revlog format 0)
471 # 'dstbase' may be empty (e.g. revlog format 0)
472 lockfile = os.path.join(dstbase, b"lock")
472 lockfile = os.path.join(dstbase, b"lock")
473 # lock to avoid premature writing to the target
473 # lock to avoid premature writing to the target
474 destlock = lock.lock(dstvfs, lockfile)
474 destlock = lock.lock(dstvfs, lockfile)
475 hardlink, n = util.copyfiles(
475 hardlink, n = util.copyfiles(
476 srcvfs.join(f), dstvfs.join(f), hardlink, progress
476 srcvfs.join(f), dstvfs.join(f), hardlink, progress
477 )
477 )
478 num += n
478 num += n
479 if hardlink:
479 if hardlink:
480 ui.debug(b"linked %d files\n" % num)
480 ui.debug(b"linked %d files\n" % num)
481 else:
481 else:
482 ui.debug(b"copied %d files\n" % num)
482 ui.debug(b"copied %d files\n" % num)
483 return destlock
483 return destlock
484 except: # re-raises
484 except: # re-raises
485 release(destlock)
485 release(destlock)
486 raise
486 raise
487
487
488
488
489 def clonewithshare(
489 def clonewithshare(
490 ui,
490 ui,
491 peeropts,
491 peeropts,
492 sharepath,
492 sharepath,
493 source,
493 source,
494 srcpeer,
494 srcpeer,
495 dest,
495 dest,
496 pull=False,
496 pull=False,
497 rev=None,
497 rev=None,
498 update=True,
498 update=True,
499 stream=False,
499 stream=False,
500 ):
500 ):
501 """Perform a clone using a shared repo.
501 """Perform a clone using a shared repo.
502
502
503 The store for the repository will be located at <sharepath>/.hg. The
503 The store for the repository will be located at <sharepath>/.hg. The
504 specified revisions will be cloned or pulled from "source". A shared repo
504 specified revisions will be cloned or pulled from "source". A shared repo
505 will be created at "dest" and a working copy will be created if "update" is
505 will be created at "dest" and a working copy will be created if "update" is
506 True.
506 True.
507 """
507 """
508 revs = None
508 revs = None
509 if rev:
509 if rev:
510 if not srcpeer.capable(b'lookup'):
510 if not srcpeer.capable(b'lookup'):
511 raise error.Abort(
511 raise error.Abort(
512 _(
512 _(
513 b"src repository does not support "
513 b"src repository does not support "
514 b"revision lookup and so doesn't "
514 b"revision lookup and so doesn't "
515 b"support clone by revision"
515 b"support clone by revision"
516 )
516 )
517 )
517 )
518
518
519 # TODO this is batchable.
519 # TODO this is batchable.
520 remoterevs = []
520 remoterevs = []
521 for r in rev:
521 for r in rev:
522 with srcpeer.commandexecutor() as e:
522 with srcpeer.commandexecutor() as e:
523 remoterevs.append(
523 remoterevs.append(
524 e.callcommand(
524 e.callcommand(
525 b'lookup',
525 b'lookup',
526 {
526 {
527 b'key': r,
527 b'key': r,
528 },
528 },
529 ).result()
529 ).result()
530 )
530 )
531 revs = remoterevs
531 revs = remoterevs
532
532
533 # Obtain a lock before checking for or cloning the pooled repo otherwise
533 # Obtain a lock before checking for or cloning the pooled repo otherwise
534 # 2 clients may race creating or populating it.
534 # 2 clients may race creating or populating it.
535 pooldir = os.path.dirname(sharepath)
535 pooldir = os.path.dirname(sharepath)
536 # lock class requires the directory to exist.
536 # lock class requires the directory to exist.
537 try:
537 try:
538 util.makedir(pooldir, False)
538 util.makedir(pooldir, False)
539 except OSError as e:
539 except OSError as e:
540 if e.errno != errno.EEXIST:
540 if e.errno != errno.EEXIST:
541 raise
541 raise
542
542
543 poolvfs = vfsmod.vfs(pooldir)
543 poolvfs = vfsmod.vfs(pooldir)
544 basename = os.path.basename(sharepath)
544 basename = os.path.basename(sharepath)
545
545
546 with lock.lock(poolvfs, b'%s.lock' % basename):
546 with lock.lock(poolvfs, b'%s.lock' % basename):
547 if os.path.exists(sharepath):
547 if os.path.exists(sharepath):
548 ui.status(
548 ui.status(
549 _(b'(sharing from existing pooled repository %s)\n') % basename
549 _(b'(sharing from existing pooled repository %s)\n') % basename
550 )
550 )
551 else:
551 else:
552 ui.status(
552 ui.status(
553 _(b'(sharing from new pooled repository %s)\n') % basename
553 _(b'(sharing from new pooled repository %s)\n') % basename
554 )
554 )
555 # Always use pull mode because hardlinks in share mode don't work
555 # Always use pull mode because hardlinks in share mode don't work
556 # well. Never update because working copies aren't necessary in
556 # well. Never update because working copies aren't necessary in
557 # share mode.
557 # share mode.
558 clone(
558 clone(
559 ui,
559 ui,
560 peeropts,
560 peeropts,
561 source,
561 source,
562 dest=sharepath,
562 dest=sharepath,
563 pull=True,
563 pull=True,
564 revs=rev,
564 revs=rev,
565 update=False,
565 update=False,
566 stream=stream,
566 stream=stream,
567 )
567 )
568
568
569 # Resolve the value to put in [paths] section for the source.
569 # Resolve the value to put in [paths] section for the source.
570 if islocal(source):
570 if islocal(source):
571 defaultpath = os.path.abspath(util.urllocalpath(source))
571 defaultpath = os.path.abspath(util.urllocalpath(source))
572 else:
572 else:
573 defaultpath = source
573 defaultpath = source
574
574
575 sharerepo = repository(ui, path=sharepath)
575 sharerepo = repository(ui, path=sharepath)
576 destrepo = share(
576 destrepo = share(
577 ui,
577 ui,
578 sharerepo,
578 sharerepo,
579 dest=dest,
579 dest=dest,
580 update=False,
580 update=False,
581 bookmarks=False,
581 bookmarks=False,
582 defaultpath=defaultpath,
582 defaultpath=defaultpath,
583 )
583 )
584
584
585 # We need to perform a pull against the dest repo to fetch bookmarks
585 # We need to perform a pull against the dest repo to fetch bookmarks
586 # and other non-store data that isn't shared by default. In the case of
586 # and other non-store data that isn't shared by default. In the case of
587 # non-existing shared repo, this means we pull from the remote twice. This
587 # non-existing shared repo, this means we pull from the remote twice. This
588 # is a bit weird. But at the time it was implemented, there wasn't an easy
588 # is a bit weird. But at the time it was implemented, there wasn't an easy
589 # way to pull just non-changegroup data.
589 # way to pull just non-changegroup data.
590 exchange.pull(destrepo, srcpeer, heads=revs)
590 exchange.pull(destrepo, srcpeer, heads=revs)
591
591
592 _postshareupdate(destrepo, update)
592 _postshareupdate(destrepo, update)
593
593
594 return srcpeer, peer(ui, peeropts, dest)
594 return srcpeer, peer(ui, peeropts, dest)
595
595
596
596
597 # Recomputing caches is often slow on big repos, so copy them.
597 # Recomputing caches is often slow on big repos, so copy them.
598 def _copycache(srcrepo, dstcachedir, fname):
598 def _copycache(srcrepo, dstcachedir, fname):
599 """copy a cache from srcrepo to destcachedir (if it exists)"""
599 """copy a cache from srcrepo to destcachedir (if it exists)"""
600 srcfname = srcrepo.cachevfs.join(fname)
600 srcfname = srcrepo.cachevfs.join(fname)
601 dstfname = os.path.join(dstcachedir, fname)
601 dstfname = os.path.join(dstcachedir, fname)
602 if os.path.exists(srcfname):
602 if os.path.exists(srcfname):
603 if not os.path.exists(dstcachedir):
603 if not os.path.exists(dstcachedir):
604 os.mkdir(dstcachedir)
604 os.mkdir(dstcachedir)
605 util.copyfile(srcfname, dstfname)
605 util.copyfile(srcfname, dstfname)
606
606
607
607
608 def clone(
608 def clone(
609 ui,
609 ui,
610 peeropts,
610 peeropts,
611 source,
611 source,
612 dest=None,
612 dest=None,
613 pull=False,
613 pull=False,
614 revs=None,
614 revs=None,
615 update=True,
615 update=True,
616 stream=False,
616 stream=False,
617 branch=None,
617 branch=None,
618 shareopts=None,
618 shareopts=None,
619 storeincludepats=None,
619 storeincludepats=None,
620 storeexcludepats=None,
620 storeexcludepats=None,
621 depth=None,
621 depth=None,
622 ):
622 ):
623 """Make a copy of an existing repository.
623 """Make a copy of an existing repository.
624
624
625 Create a copy of an existing repository in a new directory. The
625 Create a copy of an existing repository in a new directory. The
626 source and destination are URLs, as passed to the repository
626 source and destination are URLs, as passed to the repository
627 function. Returns a pair of repository peers, the source and
627 function. Returns a pair of repository peers, the source and
628 newly created destination.
628 newly created destination.
629
629
630 The location of the source is added to the new repository's
630 The location of the source is added to the new repository's
631 .hg/hgrc file, as the default to be used for future pulls and
631 .hg/hgrc file, as the default to be used for future pulls and
632 pushes.
632 pushes.
633
633
634 If an exception is raised, the partly cloned/updated destination
634 If an exception is raised, the partly cloned/updated destination
635 repository will be deleted.
635 repository will be deleted.
636
636
637 Arguments:
637 Arguments:
638
638
639 source: repository object or URL
639 source: repository object or URL
640
640
641 dest: URL of destination repository to create (defaults to base
641 dest: URL of destination repository to create (defaults to base
642 name of source repository)
642 name of source repository)
643
643
644 pull: always pull from source repository, even in local case or if the
644 pull: always pull from source repository, even in local case or if the
645 server prefers streaming
645 server prefers streaming
646
646
647 stream: stream raw data uncompressed from repository (fast over
647 stream: stream raw data uncompressed from repository (fast over
648 LAN, slow over WAN)
648 LAN, slow over WAN)
649
649
650 revs: revision to clone up to (implies pull=True)
650 revs: revision to clone up to (implies pull=True)
651
651
652 update: update working directory after clone completes, if
652 update: update working directory after clone completes, if
653 destination is local repository (True means update to default rev,
653 destination is local repository (True means update to default rev,
654 anything else is treated as a revision)
654 anything else is treated as a revision)
655
655
656 branch: branches to clone
656 branch: branches to clone
657
657
658 shareopts: dict of options to control auto sharing behavior. The "pool" key
658 shareopts: dict of options to control auto sharing behavior. The "pool" key
659 activates auto sharing mode and defines the directory for stores. The
659 activates auto sharing mode and defines the directory for stores. The
660 "mode" key determines how to construct the directory name of the shared
660 "mode" key determines how to construct the directory name of the shared
661 repository. "identity" means the name is derived from the node of the first
661 repository. "identity" means the name is derived from the node of the first
662 changeset in the repository. "remote" means the name is derived from the
662 changeset in the repository. "remote" means the name is derived from the
663 remote's path/URL. Defaults to "identity."
663 remote's path/URL. Defaults to "identity."
664
664
665 storeincludepats and storeexcludepats: sets of file patterns to include and
665 storeincludepats and storeexcludepats: sets of file patterns to include and
666 exclude in the repository copy, respectively. If not defined, all files
666 exclude in the repository copy, respectively. If not defined, all files
667 will be included (a "full" clone). Otherwise a "narrow" clone containing
667 will be included (a "full" clone). Otherwise a "narrow" clone containing
668 only the requested files will be performed. If ``storeincludepats`` is not
668 only the requested files will be performed. If ``storeincludepats`` is not
669 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
669 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
670 ``path:.``. If both are empty sets, no files will be cloned.
670 ``path:.``. If both are empty sets, no files will be cloned.
671 """
671 """
672
672
673 if isinstance(source, bytes):
673 if isinstance(source, bytes):
674 origsource = ui.expandpath(source)
674 origsource = ui.expandpath(source)
675 source, branches = parseurl(origsource, branch)
675 source, branches = parseurl(origsource, branch)
676 srcpeer = peer(ui, peeropts, source)
676 srcpeer = peer(ui, peeropts, source)
677 else:
677 else:
678 srcpeer = source.peer() # in case we were called with a localrepo
678 srcpeer = source.peer() # in case we were called with a localrepo
679 branches = (None, branch or [])
679 branches = (None, branch or [])
680 origsource = source = srcpeer.url()
680 origsource = source = srcpeer.url()
681 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
681 srclock = destlock = cleandir = None
682 destpeer = None
683 try:
684 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
682
685
683 if dest is None:
686 if dest is None:
684 dest = defaultdest(source)
687 dest = defaultdest(source)
685 if dest:
688 if dest:
686 ui.status(_(b"destination directory: %s\n") % dest)
689 ui.status(_(b"destination directory: %s\n") % dest)
687 else:
690 else:
688 dest = ui.expandpath(dest)
691 dest = ui.expandpath(dest)
689
692
690 dest = util.urllocalpath(dest)
693 dest = util.urllocalpath(dest)
691 source = util.urllocalpath(source)
694 source = util.urllocalpath(source)
692
695
693 if not dest:
696 if not dest:
694 raise error.InputError(_(b"empty destination path is not valid"))
697 raise error.InputError(_(b"empty destination path is not valid"))
695
698
696 destvfs = vfsmod.vfs(dest, expandpath=True)
699 destvfs = vfsmod.vfs(dest, expandpath=True)
697 if destvfs.lexists():
700 if destvfs.lexists():
698 if not destvfs.isdir():
701 if not destvfs.isdir():
699 raise error.InputError(_(b"destination '%s' already exists") % dest)
702 raise error.InputError(
700 elif destvfs.listdir():
703 _(b"destination '%s' already exists") % dest
701 raise error.InputError(_(b"destination '%s' is not empty") % dest)
704 )
705 elif destvfs.listdir():
706 raise error.InputError(
707 _(b"destination '%s' is not empty") % dest
708 )
702
709
703 createopts = {}
710 createopts = {}
704 narrow = False
711 narrow = False
705
706 if storeincludepats is not None:
707 narrowspec.validatepatterns(storeincludepats)
708 narrow = True
709
712
710 if storeexcludepats is not None:
713 if storeincludepats is not None:
711 narrowspec.validatepatterns(storeexcludepats)
714 narrowspec.validatepatterns(storeincludepats)
712 narrow = True
715 narrow = True
716
717 if storeexcludepats is not None:
718 narrowspec.validatepatterns(storeexcludepats)
719 narrow = True
713
720
714 if narrow:
721 if narrow:
715 # Include everything by default if only exclusion patterns defined.
722 # Include everything by default if only exclusion patterns defined.
716 if storeexcludepats and not storeincludepats:
723 if storeexcludepats and not storeincludepats:
717 storeincludepats = {b'path:.'}
724 storeincludepats = {b'path:.'}
718
725
719 createopts[b'narrowfiles'] = True
726 createopts[b'narrowfiles'] = True
720
727
721 if depth:
728 if depth:
722 createopts[b'shallowfilestore'] = True
729 createopts[b'shallowfilestore'] = True
723
730
724 if srcpeer.capable(b'lfs-serve'):
731 if srcpeer.capable(b'lfs-serve'):
725 # Repository creation honors the config if it disabled the extension, so
732 # Repository creation honors the config if it disabled the extension, so
726 # we can't just announce that lfs will be enabled. This check avoids
733 # we can't just announce that lfs will be enabled. This check avoids
727 # saying that lfs will be enabled, and then saying it's an unknown
734 # saying that lfs will be enabled, and then saying it's an unknown
728 # feature. The lfs creation option is set in either case so that a
735 # feature. The lfs creation option is set in either case so that a
729 # requirement is added. If the extension is explicitly disabled but the
736 # requirement is added. If the extension is explicitly disabled but the
730 # requirement is set, the clone aborts early, before transferring any
737 # requirement is set, the clone aborts early, before transferring any
731 # data.
738 # data.
732 createopts[b'lfs'] = True
739 createopts[b'lfs'] = True
733
740
734 if extensions.disabled_help(b'lfs'):
741 if extensions.disabled_help(b'lfs'):
735 ui.status(
742 ui.status(
736 _(
743 _(
737 b'(remote is using large file support (lfs), but it is '
744 b'(remote is using large file support (lfs), but it is '
738 b'explicitly disabled in the local configuration)\n'
745 b'explicitly disabled in the local configuration)\n'
746 )
739 )
747 )
740 )
748 else:
741 else:
749 ui.status(
742 ui.status(
750 _(
743 _(
751 b'(remote is using large file support (lfs); lfs will '
744 b'(remote is using large file support (lfs); lfs will '
752 b'be enabled for this repository)\n'
745 b'be enabled for this repository)\n'
753 )
746 )
754 )
747 )
748
755
749 shareopts = shareopts or {}
756 shareopts = shareopts or {}
750 sharepool = shareopts.get(b'pool')
757 sharepool = shareopts.get(b'pool')
751 sharenamemode = shareopts.get(b'mode')
758 sharenamemode = shareopts.get(b'mode')
752 if sharepool and islocal(dest):
759 if sharepool and islocal(dest):
753 sharepath = None
760 sharepath = None
754 if sharenamemode == b'identity':
761 if sharenamemode == b'identity':
755 # Resolve the name from the initial changeset in the remote
762 # Resolve the name from the initial changeset in the remote
756 # repository. This returns nullid when the remote is empty. It
763 # repository. This returns nullid when the remote is empty. It
757 # raises RepoLookupError if revision 0 is filtered or otherwise
764 # raises RepoLookupError if revision 0 is filtered or otherwise
758 # not available. If we fail to resolve, sharing is not enabled.
765 # not available. If we fail to resolve, sharing is not enabled.
759 try:
766 try:
760 with srcpeer.commandexecutor() as e:
767 with srcpeer.commandexecutor() as e:
761 rootnode = e.callcommand(
768 rootnode = e.callcommand(
762 b'lookup',
769 b'lookup',
763 {
770 {
764 b'key': b'0',
771 b'key': b'0',
765 },
772 },
766 ).result()
773 ).result()
767
774
768 if rootnode != nullid:
775 if rootnode != nullid:
769 sharepath = os.path.join(sharepool, hex(rootnode))
776 sharepath = os.path.join(sharepool, hex(rootnode))
770 else:
777 else:
778 ui.status(
779 _(
780 b'(not using pooled storage: '
781 b'remote appears to be empty)\n'
782 )
783 )
784 except error.RepoLookupError:
771 ui.status(
785 ui.status(
772 _(
786 _(
773 b'(not using pooled storage: '
787 b'(not using pooled storage: '
774 b'remote appears to be empty)\n'
788 b'unable to resolve identity of remote)\n'
775 )
789 )
776 )
790 )
777 except error.RepoLookupError:
791 elif sharenamemode == b'remote':
778 ui.status(
792 sharepath = os.path.join(
779 _(
793 sharepool, hex(hashutil.sha1(source).digest())
780 b'(not using pooled storage: '
794 )
781 b'unable to resolve identity of remote)\n'
795 else:
782 )
796 raise error.Abort(
797 _(b'unknown share naming mode: %s') % sharenamemode
783 )
798 )
784 elif sharenamemode == b'remote':
799
785 sharepath = os.path.join(
800 # TODO this is a somewhat arbitrary restriction.
786 sharepool, hex(hashutil.sha1(source).digest())
801 if narrow:
787 )
802 ui.status(
788 else:
803 _(b'(pooled storage not supported for narrow clones)\n')
789 raise error.Abort(
804 )
790 _(b'unknown share naming mode: %s') % sharenamemode
805 sharepath = None
791 )
792
806
793 # TODO this is a somewhat arbitrary restriction.
807 if sharepath:
794 if narrow:
808 return clonewithshare(
795 ui.status(_(b'(pooled storage not supported for narrow clones)\n'))
809 ui,
796 sharepath = None
810 peeropts,
811 sharepath,
812 source,
813 srcpeer,
814 dest,
815 pull=pull,
816 rev=revs,
817 update=update,
818 stream=stream,
819 )
797
820
798 if sharepath:
821 srcrepo = srcpeer.local()
799 return clonewithshare(
800 ui,
801 peeropts,
802 sharepath,
803 source,
804 srcpeer,
805 dest,
806 pull=pull,
807 rev=revs,
808 update=update,
809 stream=stream,
810 )
811
822
812 srclock = destlock = cleandir = None
813 srcrepo = srcpeer.local()
814 try:
815 abspath = origsource
823 abspath = origsource
816 if islocal(origsource):
824 if islocal(origsource):
817 abspath = os.path.abspath(util.urllocalpath(origsource))
825 abspath = os.path.abspath(util.urllocalpath(origsource))
818
826
819 if islocal(dest):
827 if islocal(dest):
820 cleandir = dest
828 cleandir = dest
821
829
822 copy = False
830 copy = False
823 if (
831 if (
824 srcrepo
832 srcrepo
825 and srcrepo.cancopy()
833 and srcrepo.cancopy()
826 and islocal(dest)
834 and islocal(dest)
827 and not phases.hassecret(srcrepo)
835 and not phases.hassecret(srcrepo)
828 ):
836 ):
829 copy = not pull and not revs
837 copy = not pull and not revs
830
838
831 # TODO this is a somewhat arbitrary restriction.
839 # TODO this is a somewhat arbitrary restriction.
832 if narrow:
840 if narrow:
833 copy = False
841 copy = False
834
842
835 if copy:
843 if copy:
836 try:
844 try:
837 # we use a lock here because if we race with commit, we
845 # we use a lock here because if we race with commit, we
838 # can end up with extra data in the cloned revlogs that's
846 # can end up with extra data in the cloned revlogs that's
839 # not pointed to by changesets, thus causing verify to
847 # not pointed to by changesets, thus causing verify to
840 # fail
848 # fail
841 srclock = srcrepo.lock(wait=False)
849 srclock = srcrepo.lock(wait=False)
842 except error.LockError:
850 except error.LockError:
843 copy = False
851 copy = False
844
852
845 if copy:
853 if copy:
846 srcrepo.hook(b'preoutgoing', throw=True, source=b'clone')
854 srcrepo.hook(b'preoutgoing', throw=True, source=b'clone')
847 hgdir = os.path.realpath(os.path.join(dest, b".hg"))
855 hgdir = os.path.realpath(os.path.join(dest, b".hg"))
848 if not os.path.exists(dest):
856 if not os.path.exists(dest):
849 util.makedirs(dest)
857 util.makedirs(dest)
850 else:
858 else:
851 # only clean up directories we create ourselves
859 # only clean up directories we create ourselves
852 cleandir = hgdir
860 cleandir = hgdir
853 try:
861 try:
854 destpath = hgdir
862 destpath = hgdir
855 util.makedir(destpath, notindexed=True)
863 util.makedir(destpath, notindexed=True)
856 except OSError as inst:
864 except OSError as inst:
857 if inst.errno == errno.EEXIST:
865 if inst.errno == errno.EEXIST:
858 cleandir = None
866 cleandir = None
859 raise error.Abort(
867 raise error.Abort(
860 _(b"destination '%s' already exists") % dest
868 _(b"destination '%s' already exists") % dest
861 )
869 )
862 raise
870 raise
863
871
864 destlock = copystore(ui, srcrepo, destpath)
872 destlock = copystore(ui, srcrepo, destpath)
865 # copy bookmarks over
873 # copy bookmarks over
866 srcbookmarks = srcrepo.vfs.join(b'bookmarks')
874 srcbookmarks = srcrepo.vfs.join(b'bookmarks')
867 dstbookmarks = os.path.join(destpath, b'bookmarks')
875 dstbookmarks = os.path.join(destpath, b'bookmarks')
868 if os.path.exists(srcbookmarks):
876 if os.path.exists(srcbookmarks):
869 util.copyfile(srcbookmarks, dstbookmarks)
877 util.copyfile(srcbookmarks, dstbookmarks)
870
878
871 dstcachedir = os.path.join(destpath, b'cache')
879 dstcachedir = os.path.join(destpath, b'cache')
872 for cache in cacheutil.cachetocopy(srcrepo):
880 for cache in cacheutil.cachetocopy(srcrepo):
873 _copycache(srcrepo, dstcachedir, cache)
881 _copycache(srcrepo, dstcachedir, cache)
874
882
875 # we need to re-init the repo after manually copying the data
883 # we need to re-init the repo after manually copying the data
876 # into it
884 # into it
877 destpeer = peer(srcrepo, peeropts, dest)
885 destpeer = peer(srcrepo, peeropts, dest)
878 srcrepo.hook(b'outgoing', source=b'clone', node=nullhex)
886 srcrepo.hook(b'outgoing', source=b'clone', node=nullhex)
879 else:
887 else:
880 try:
888 try:
881 # only pass ui when no srcrepo
889 # only pass ui when no srcrepo
882 destpeer = peer(
890 destpeer = peer(
883 srcrepo or ui,
891 srcrepo or ui,
884 peeropts,
892 peeropts,
885 dest,
893 dest,
886 create=True,
894 create=True,
887 createopts=createopts,
895 createopts=createopts,
888 )
896 )
889 except OSError as inst:
897 except OSError as inst:
890 if inst.errno == errno.EEXIST:
898 if inst.errno == errno.EEXIST:
891 cleandir = None
899 cleandir = None
892 raise error.Abort(
900 raise error.Abort(
893 _(b"destination '%s' already exists") % dest
901 _(b"destination '%s' already exists") % dest
894 )
902 )
895 raise
903 raise
896
904
897 if revs:
905 if revs:
898 if not srcpeer.capable(b'lookup'):
906 if not srcpeer.capable(b'lookup'):
899 raise error.Abort(
907 raise error.Abort(
900 _(
908 _(
901 b"src repository does not support "
909 b"src repository does not support "
902 b"revision lookup and so doesn't "
910 b"revision lookup and so doesn't "
903 b"support clone by revision"
911 b"support clone by revision"
904 )
912 )
905 )
913 )
906
914
907 # TODO this is batchable.
915 # TODO this is batchable.
908 remoterevs = []
916 remoterevs = []
909 for rev in revs:
917 for rev in revs:
910 with srcpeer.commandexecutor() as e:
918 with srcpeer.commandexecutor() as e:
911 remoterevs.append(
919 remoterevs.append(
912 e.callcommand(
920 e.callcommand(
913 b'lookup',
921 b'lookup',
914 {
922 {
915 b'key': rev,
923 b'key': rev,
916 },
924 },
917 ).result()
925 ).result()
918 )
926 )
919 revs = remoterevs
927 revs = remoterevs
920
928
921 checkout = revs[0]
929 checkout = revs[0]
922 else:
930 else:
923 revs = None
931 revs = None
924 local = destpeer.local()
932 local = destpeer.local()
925 if local:
933 if local:
926 if narrow:
934 if narrow:
927 with local.wlock(), local.lock():
935 with local.wlock(), local.lock():
928 local.setnarrowpats(storeincludepats, storeexcludepats)
936 local.setnarrowpats(storeincludepats, storeexcludepats)
929 narrowspec.copytoworkingcopy(local)
937 narrowspec.copytoworkingcopy(local)
930
938
931 u = util.url(abspath)
939 u = util.url(abspath)
932 defaulturl = bytes(u)
940 defaulturl = bytes(u)
933 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
941 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
934 if not stream:
942 if not stream:
935 if pull:
943 if pull:
936 stream = False
944 stream = False
937 else:
945 else:
938 stream = None
946 stream = None
939 # internal config: ui.quietbookmarkmove
947 # internal config: ui.quietbookmarkmove
940 overrides = {(b'ui', b'quietbookmarkmove'): True}
948 overrides = {(b'ui', b'quietbookmarkmove'): True}
941 with local.ui.configoverride(overrides, b'clone'):
949 with local.ui.configoverride(overrides, b'clone'):
942 exchange.pull(
950 exchange.pull(
943 local,
951 local,
944 srcpeer,
952 srcpeer,
945 revs,
953 revs,
946 streamclonerequested=stream,
954 streamclonerequested=stream,
947 includepats=storeincludepats,
955 includepats=storeincludepats,
948 excludepats=storeexcludepats,
956 excludepats=storeexcludepats,
949 depth=depth,
957 depth=depth,
950 )
958 )
951 elif srcrepo:
959 elif srcrepo:
952 # TODO lift restriction once exchange.push() accepts narrow
960 # TODO lift restriction once exchange.push() accepts narrow
953 # push.
961 # push.
954 if narrow:
962 if narrow:
955 raise error.Abort(
963 raise error.Abort(
956 _(
964 _(
957 b'narrow clone not available for '
965 b'narrow clone not available for '
958 b'remote destinations'
966 b'remote destinations'
959 )
967 )
960 )
968 )
961
969
962 exchange.push(
970 exchange.push(
963 srcrepo,
971 srcrepo,
964 destpeer,
972 destpeer,
965 revs=revs,
973 revs=revs,
966 bookmarks=srcrepo._bookmarks.keys(),
974 bookmarks=srcrepo._bookmarks.keys(),
967 )
975 )
968 else:
976 else:
969 raise error.Abort(
977 raise error.Abort(
970 _(b"clone from remote to remote not supported")
978 _(b"clone from remote to remote not supported")
971 )
979 )
972
980
973 cleandir = None
981 cleandir = None
974
982
975 destrepo = destpeer.local()
983 destrepo = destpeer.local()
976 if destrepo:
984 if destrepo:
977 template = uimod.samplehgrcs[b'cloned']
985 template = uimod.samplehgrcs[b'cloned']
978 u = util.url(abspath)
986 u = util.url(abspath)
979 u.passwd = None
987 u.passwd = None
980 defaulturl = bytes(u)
988 defaulturl = bytes(u)
981 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
989 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
982 destrepo.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
990 destrepo.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
983
991
984 if ui.configbool(b'experimental', b'remotenames'):
992 if ui.configbool(b'experimental', b'remotenames'):
985 logexchange.pullremotenames(destrepo, srcpeer)
993 logexchange.pullremotenames(destrepo, srcpeer)
986
994
987 if update:
995 if update:
988 if update is not True:
996 if update is not True:
989 with srcpeer.commandexecutor() as e:
997 with srcpeer.commandexecutor() as e:
990 checkout = e.callcommand(
998 checkout = e.callcommand(
991 b'lookup',
999 b'lookup',
992 {
1000 {
993 b'key': update,
1001 b'key': update,
994 },
1002 },
995 ).result()
1003 ).result()
996
1004
997 uprev = None
1005 uprev = None
998 status = None
1006 status = None
999 if checkout is not None:
1007 if checkout is not None:
1000 # Some extensions (at least hg-git and hg-subversion) have
1008 # Some extensions (at least hg-git and hg-subversion) have
1001 # a peer.lookup() implementation that returns a name instead
1009 # a peer.lookup() implementation that returns a name instead
1002 # of a nodeid. We work around it here until we've figured
1010 # of a nodeid. We work around it here until we've figured
1003 # out a better solution.
1011 # out a better solution.
1004 if len(checkout) == 20 and checkout in destrepo:
1012 if len(checkout) == 20 and checkout in destrepo:
1005 uprev = checkout
1013 uprev = checkout
1006 elif scmutil.isrevsymbol(destrepo, checkout):
1014 elif scmutil.isrevsymbol(destrepo, checkout):
1007 uprev = scmutil.revsymbol(destrepo, checkout).node()
1015 uprev = scmutil.revsymbol(destrepo, checkout).node()
1008 else:
1016 else:
1009 if update is not True:
1017 if update is not True:
1010 try:
1018 try:
1011 uprev = destrepo.lookup(update)
1019 uprev = destrepo.lookup(update)
1012 except error.RepoLookupError:
1020 except error.RepoLookupError:
1013 pass
1021 pass
1014 if uprev is None:
1022 if uprev is None:
1015 try:
1023 try:
1016 if destrepo._activebookmark:
1024 if destrepo._activebookmark:
1017 uprev = destrepo.lookup(destrepo._activebookmark)
1025 uprev = destrepo.lookup(destrepo._activebookmark)
1018 update = destrepo._activebookmark
1026 update = destrepo._activebookmark
1019 else:
1027 else:
1020 uprev = destrepo._bookmarks[b'@']
1028 uprev = destrepo._bookmarks[b'@']
1021 update = b'@'
1029 update = b'@'
1022 bn = destrepo[uprev].branch()
1030 bn = destrepo[uprev].branch()
1023 if bn == b'default':
1031 if bn == b'default':
1024 status = _(b"updating to bookmark %s\n" % update)
1032 status = _(b"updating to bookmark %s\n" % update)
1025 else:
1033 else:
1026 status = (
1034 status = (
1027 _(b"updating to bookmark %s on branch %s\n")
1035 _(b"updating to bookmark %s on branch %s\n")
1028 ) % (update, bn)
1036 ) % (update, bn)
1029 except KeyError:
1037 except KeyError:
1030 try:
1038 try:
1031 uprev = destrepo.branchtip(b'default')
1039 uprev = destrepo.branchtip(b'default')
1032 except error.RepoLookupError:
1040 except error.RepoLookupError:
1033 uprev = destrepo.lookup(b'tip')
1041 uprev = destrepo.lookup(b'tip')
1034 if not status:
1042 if not status:
1035 bn = destrepo[uprev].branch()
1043 bn = destrepo[uprev].branch()
1036 status = _(b"updating to branch %s\n") % bn
1044 status = _(b"updating to branch %s\n") % bn
1037 destrepo.ui.status(status)
1045 destrepo.ui.status(status)
1038 _update(destrepo, uprev)
1046 _update(destrepo, uprev)
1039 if update in destrepo._bookmarks:
1047 if update in destrepo._bookmarks:
1040 bookmarks.activate(destrepo, update)
1048 bookmarks.activate(destrepo, update)
1041 if destlock is not None:
1049 if destlock is not None:
1042 release(destlock)
1050 release(destlock)
1043 # here is a tiny windows were someone could end up writing the
1051 # here is a tiny windows were someone could end up writing the
1044 # repository before the cache are sure to be warm. This is "fine"
1052 # repository before the cache are sure to be warm. This is "fine"
1045 # as the only "bad" outcome would be some slowness. That potential
1053 # as the only "bad" outcome would be some slowness. That potential
1046 # slowness already affect reader.
1054 # slowness already affect reader.
1047 with destrepo.lock():
1055 with destrepo.lock():
1048 destrepo.updatecaches(full=True)
1056 destrepo.updatecaches(full=True)
1049 finally:
1057 finally:
1050 release(srclock, destlock)
1058 release(srclock, destlock)
1051 if cleandir is not None:
1059 if cleandir is not None:
1052 shutil.rmtree(cleandir, True)
1060 shutil.rmtree(cleandir, True)
1053 if srcpeer is not None:
1061 if srcpeer is not None:
1054 srcpeer.close()
1062 srcpeer.close()
1063 if destpeer and destpeer.local() is None:
1064 destpeer.close()
1055 return srcpeer, destpeer
1065 return srcpeer, destpeer
1056
1066
1057
1067
1058 def _showstats(repo, stats, quietempty=False):
1068 def _showstats(repo, stats, quietempty=False):
1059 if quietempty and stats.isempty():
1069 if quietempty and stats.isempty():
1060 return
1070 return
1061 repo.ui.status(
1071 repo.ui.status(
1062 _(
1072 _(
1063 b"%d files updated, %d files merged, "
1073 b"%d files updated, %d files merged, "
1064 b"%d files removed, %d files unresolved\n"
1074 b"%d files removed, %d files unresolved\n"
1065 )
1075 )
1066 % (
1076 % (
1067 stats.updatedcount,
1077 stats.updatedcount,
1068 stats.mergedcount,
1078 stats.mergedcount,
1069 stats.removedcount,
1079 stats.removedcount,
1070 stats.unresolvedcount,
1080 stats.unresolvedcount,
1071 )
1081 )
1072 )
1082 )
1073
1083
1074
1084
1075 def updaterepo(repo, node, overwrite, updatecheck=None):
1085 def updaterepo(repo, node, overwrite, updatecheck=None):
1076 """Update the working directory to node.
1086 """Update the working directory to node.
1077
1087
1078 When overwrite is set, changes are clobbered, merged else
1088 When overwrite is set, changes are clobbered, merged else
1079
1089
1080 returns stats (see pydoc mercurial.merge.applyupdates)"""
1090 returns stats (see pydoc mercurial.merge.applyupdates)"""
1081 repo.ui.deprecwarn(
1091 repo.ui.deprecwarn(
1082 b'prefer merge.update() or merge.clean_update() over hg.updaterepo()',
1092 b'prefer merge.update() or merge.clean_update() over hg.updaterepo()',
1083 b'5.7',
1093 b'5.7',
1084 )
1094 )
1085 return mergemod._update(
1095 return mergemod._update(
1086 repo,
1096 repo,
1087 node,
1097 node,
1088 branchmerge=False,
1098 branchmerge=False,
1089 force=overwrite,
1099 force=overwrite,
1090 labels=[b'working copy', b'destination'],
1100 labels=[b'working copy', b'destination'],
1091 updatecheck=updatecheck,
1101 updatecheck=updatecheck,
1092 )
1102 )
1093
1103
1094
1104
1095 def update(repo, node, quietempty=False, updatecheck=None):
1105 def update(repo, node, quietempty=False, updatecheck=None):
1096 """update the working directory to node"""
1106 """update the working directory to node"""
1097 stats = mergemod.update(repo[node], updatecheck=updatecheck)
1107 stats = mergemod.update(repo[node], updatecheck=updatecheck)
1098 _showstats(repo, stats, quietempty)
1108 _showstats(repo, stats, quietempty)
1099 if stats.unresolvedcount:
1109 if stats.unresolvedcount:
1100 repo.ui.status(_(b"use 'hg resolve' to retry unresolved file merges\n"))
1110 repo.ui.status(_(b"use 'hg resolve' to retry unresolved file merges\n"))
1101 return stats.unresolvedcount > 0
1111 return stats.unresolvedcount > 0
1102
1112
1103
1113
1104 # naming conflict in clone()
1114 # naming conflict in clone()
1105 _update = update
1115 _update = update
1106
1116
1107
1117
1108 def clean(repo, node, show_stats=True, quietempty=False):
1118 def clean(repo, node, show_stats=True, quietempty=False):
1109 """forcibly switch the working directory to node, clobbering changes"""
1119 """forcibly switch the working directory to node, clobbering changes"""
1110 stats = mergemod.clean_update(repo[node])
1120 stats = mergemod.clean_update(repo[node])
1111 assert stats.unresolvedcount == 0
1121 assert stats.unresolvedcount == 0
1112 if show_stats:
1122 if show_stats:
1113 _showstats(repo, stats, quietempty)
1123 _showstats(repo, stats, quietempty)
1114
1124
1115
1125
1116 # naming conflict in updatetotally()
1126 # naming conflict in updatetotally()
1117 _clean = clean
1127 _clean = clean
1118
1128
1119 _VALID_UPDATECHECKS = {
1129 _VALID_UPDATECHECKS = {
1120 mergemod.UPDATECHECK_ABORT,
1130 mergemod.UPDATECHECK_ABORT,
1121 mergemod.UPDATECHECK_NONE,
1131 mergemod.UPDATECHECK_NONE,
1122 mergemod.UPDATECHECK_LINEAR,
1132 mergemod.UPDATECHECK_LINEAR,
1123 mergemod.UPDATECHECK_NO_CONFLICT,
1133 mergemod.UPDATECHECK_NO_CONFLICT,
1124 }
1134 }
1125
1135
1126
1136
1127 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
1137 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
1128 """Update the working directory with extra care for non-file components
1138 """Update the working directory with extra care for non-file components
1129
1139
1130 This takes care of non-file components below:
1140 This takes care of non-file components below:
1131
1141
1132 :bookmark: might be advanced or (in)activated
1142 :bookmark: might be advanced or (in)activated
1133
1143
1134 This takes arguments below:
1144 This takes arguments below:
1135
1145
1136 :checkout: to which revision the working directory is updated
1146 :checkout: to which revision the working directory is updated
1137 :brev: a name, which might be a bookmark to be activated after updating
1147 :brev: a name, which might be a bookmark to be activated after updating
1138 :clean: whether changes in the working directory can be discarded
1148 :clean: whether changes in the working directory can be discarded
1139 :updatecheck: how to deal with a dirty working directory
1149 :updatecheck: how to deal with a dirty working directory
1140
1150
1141 Valid values for updatecheck are the UPDATECHECK_* constants
1151 Valid values for updatecheck are the UPDATECHECK_* constants
1142 defined in the merge module. Passing `None` will result in using the
1152 defined in the merge module. Passing `None` will result in using the
1143 configured default.
1153 configured default.
1144
1154
1145 * ABORT: abort if the working directory is dirty
1155 * ABORT: abort if the working directory is dirty
1146 * NONE: don't check (merge working directory changes into destination)
1156 * NONE: don't check (merge working directory changes into destination)
1147 * LINEAR: check that update is linear before merging working directory
1157 * LINEAR: check that update is linear before merging working directory
1148 changes into destination
1158 changes into destination
1149 * NO_CONFLICT: check that the update does not result in file merges
1159 * NO_CONFLICT: check that the update does not result in file merges
1150
1160
1151 This returns whether conflict is detected at updating or not.
1161 This returns whether conflict is detected at updating or not.
1152 """
1162 """
1153 if updatecheck is None:
1163 if updatecheck is None:
1154 updatecheck = ui.config(b'commands', b'update.check')
1164 updatecheck = ui.config(b'commands', b'update.check')
1155 if updatecheck not in _VALID_UPDATECHECKS:
1165 if updatecheck not in _VALID_UPDATECHECKS:
1156 # If not configured, or invalid value configured
1166 # If not configured, or invalid value configured
1157 updatecheck = mergemod.UPDATECHECK_LINEAR
1167 updatecheck = mergemod.UPDATECHECK_LINEAR
1158 if updatecheck not in _VALID_UPDATECHECKS:
1168 if updatecheck not in _VALID_UPDATECHECKS:
1159 raise ValueError(
1169 raise ValueError(
1160 r'Invalid updatecheck value %r (can accept %r)'
1170 r'Invalid updatecheck value %r (can accept %r)'
1161 % (updatecheck, _VALID_UPDATECHECKS)
1171 % (updatecheck, _VALID_UPDATECHECKS)
1162 )
1172 )
1163 with repo.wlock():
1173 with repo.wlock():
1164 movemarkfrom = None
1174 movemarkfrom = None
1165 warndest = False
1175 warndest = False
1166 if checkout is None:
1176 if checkout is None:
1167 updata = destutil.destupdate(repo, clean=clean)
1177 updata = destutil.destupdate(repo, clean=clean)
1168 checkout, movemarkfrom, brev = updata
1178 checkout, movemarkfrom, brev = updata
1169 warndest = True
1179 warndest = True
1170
1180
1171 if clean:
1181 if clean:
1172 ret = _clean(repo, checkout)
1182 ret = _clean(repo, checkout)
1173 else:
1183 else:
1174 if updatecheck == mergemod.UPDATECHECK_ABORT:
1184 if updatecheck == mergemod.UPDATECHECK_ABORT:
1175 cmdutil.bailifchanged(repo, merge=False)
1185 cmdutil.bailifchanged(repo, merge=False)
1176 updatecheck = mergemod.UPDATECHECK_NONE
1186 updatecheck = mergemod.UPDATECHECK_NONE
1177 ret = _update(repo, checkout, updatecheck=updatecheck)
1187 ret = _update(repo, checkout, updatecheck=updatecheck)
1178
1188
1179 if not ret and movemarkfrom:
1189 if not ret and movemarkfrom:
1180 if movemarkfrom == repo[b'.'].node():
1190 if movemarkfrom == repo[b'.'].node():
1181 pass # no-op update
1191 pass # no-op update
1182 elif bookmarks.update(repo, [movemarkfrom], repo[b'.'].node()):
1192 elif bookmarks.update(repo, [movemarkfrom], repo[b'.'].node()):
1183 b = ui.label(repo._activebookmark, b'bookmarks.active')
1193 b = ui.label(repo._activebookmark, b'bookmarks.active')
1184 ui.status(_(b"updating bookmark %s\n") % b)
1194 ui.status(_(b"updating bookmark %s\n") % b)
1185 else:
1195 else:
1186 # this can happen with a non-linear update
1196 # this can happen with a non-linear update
1187 b = ui.label(repo._activebookmark, b'bookmarks')
1197 b = ui.label(repo._activebookmark, b'bookmarks')
1188 ui.status(_(b"(leaving bookmark %s)\n") % b)
1198 ui.status(_(b"(leaving bookmark %s)\n") % b)
1189 bookmarks.deactivate(repo)
1199 bookmarks.deactivate(repo)
1190 elif brev in repo._bookmarks:
1200 elif brev in repo._bookmarks:
1191 if brev != repo._activebookmark:
1201 if brev != repo._activebookmark:
1192 b = ui.label(brev, b'bookmarks.active')
1202 b = ui.label(brev, b'bookmarks.active')
1193 ui.status(_(b"(activating bookmark %s)\n") % b)
1203 ui.status(_(b"(activating bookmark %s)\n") % b)
1194 bookmarks.activate(repo, brev)
1204 bookmarks.activate(repo, brev)
1195 elif brev:
1205 elif brev:
1196 if repo._activebookmark:
1206 if repo._activebookmark:
1197 b = ui.label(repo._activebookmark, b'bookmarks')
1207 b = ui.label(repo._activebookmark, b'bookmarks')
1198 ui.status(_(b"(leaving bookmark %s)\n") % b)
1208 ui.status(_(b"(leaving bookmark %s)\n") % b)
1199 bookmarks.deactivate(repo)
1209 bookmarks.deactivate(repo)
1200
1210
1201 if warndest:
1211 if warndest:
1202 destutil.statusotherdests(ui, repo)
1212 destutil.statusotherdests(ui, repo)
1203
1213
1204 return ret
1214 return ret
1205
1215
1206
1216
1207 def merge(
1217 def merge(
1208 ctx,
1218 ctx,
1209 force=False,
1219 force=False,
1210 remind=True,
1220 remind=True,
1211 labels=None,
1221 labels=None,
1212 ):
1222 ):
1213 """Branch merge with node, resolving changes. Return true if any
1223 """Branch merge with node, resolving changes. Return true if any
1214 unresolved conflicts."""
1224 unresolved conflicts."""
1215 repo = ctx.repo()
1225 repo = ctx.repo()
1216 stats = mergemod.merge(ctx, force=force, labels=labels)
1226 stats = mergemod.merge(ctx, force=force, labels=labels)
1217 _showstats(repo, stats)
1227 _showstats(repo, stats)
1218 if stats.unresolvedcount:
1228 if stats.unresolvedcount:
1219 repo.ui.status(
1229 repo.ui.status(
1220 _(
1230 _(
1221 b"use 'hg resolve' to retry unresolved file merges "
1231 b"use 'hg resolve' to retry unresolved file merges "
1222 b"or 'hg merge --abort' to abandon\n"
1232 b"or 'hg merge --abort' to abandon\n"
1223 )
1233 )
1224 )
1234 )
1225 elif remind:
1235 elif remind:
1226 repo.ui.status(_(b"(branch merge, don't forget to commit)\n"))
1236 repo.ui.status(_(b"(branch merge, don't forget to commit)\n"))
1227 return stats.unresolvedcount > 0
1237 return stats.unresolvedcount > 0
1228
1238
1229
1239
1230 def abortmerge(ui, repo):
1240 def abortmerge(ui, repo):
1231 ms = mergestatemod.mergestate.read(repo)
1241 ms = mergestatemod.mergestate.read(repo)
1232 if ms.active():
1242 if ms.active():
1233 # there were conflicts
1243 # there were conflicts
1234 node = ms.localctx.hex()
1244 node = ms.localctx.hex()
1235 else:
1245 else:
1236 # there were no conficts, mergestate was not stored
1246 # there were no conficts, mergestate was not stored
1237 node = repo[b'.'].hex()
1247 node = repo[b'.'].hex()
1238
1248
1239 repo.ui.status(_(b"aborting the merge, updating back to %s\n") % node[:12])
1249 repo.ui.status(_(b"aborting the merge, updating back to %s\n") % node[:12])
1240 stats = mergemod.clean_update(repo[node])
1250 stats = mergemod.clean_update(repo[node])
1241 assert stats.unresolvedcount == 0
1251 assert stats.unresolvedcount == 0
1242 _showstats(repo, stats)
1252 _showstats(repo, stats)
1243
1253
1244
1254
1245 def _incoming(
1255 def _incoming(
1246 displaychlist, subreporecurse, ui, repo, source, opts, buffered=False
1256 displaychlist, subreporecurse, ui, repo, source, opts, buffered=False
1247 ):
1257 ):
1248 """
1258 """
1249 Helper for incoming / gincoming.
1259 Helper for incoming / gincoming.
1250 displaychlist gets called with
1260 displaychlist gets called with
1251 (remoterepo, incomingchangesetlist, displayer) parameters,
1261 (remoterepo, incomingchangesetlist, displayer) parameters,
1252 and is supposed to contain only code that can't be unified.
1262 and is supposed to contain only code that can't be unified.
1253 """
1263 """
1254 source, branches = parseurl(ui.expandpath(source), opts.get(b'branch'))
1264 source, branches = parseurl(ui.expandpath(source), opts.get(b'branch'))
1255 other = peer(repo, opts, source)
1265 other = peer(repo, opts, source)
1256 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
1266 cleanupfn = other.close
1257 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1267 try:
1268 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
1269 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1258
1270
1259 if revs:
1271 if revs:
1260 revs = [other.lookup(rev) for rev in revs]
1272 revs = [other.lookup(rev) for rev in revs]
1261 other, chlist, cleanupfn = bundlerepo.getremotechanges(
1273 other, chlist, cleanupfn = bundlerepo.getremotechanges(
1262 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
1274 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
1263 )
1275 )
1264 try:
1276
1265 if not chlist:
1277 if not chlist:
1266 ui.status(_(b"no changes found\n"))
1278 ui.status(_(b"no changes found\n"))
1267 return subreporecurse()
1279 return subreporecurse()
1268 ui.pager(b'incoming')
1280 ui.pager(b'incoming')
1269 displayer = logcmdutil.changesetdisplayer(
1281 displayer = logcmdutil.changesetdisplayer(
1270 ui, other, opts, buffered=buffered
1282 ui, other, opts, buffered=buffered
1271 )
1283 )
1272 displaychlist(other, chlist, displayer)
1284 displaychlist(other, chlist, displayer)
1273 displayer.close()
1285 displayer.close()
1274 finally:
1286 finally:
1275 cleanupfn()
1287 cleanupfn()
1276 subreporecurse()
1288 subreporecurse()
1277 return 0 # exit code is zero since we found incoming changes
1289 return 0 # exit code is zero since we found incoming changes
1278
1290
1279
1291
1280 def incoming(ui, repo, source, opts):
1292 def incoming(ui, repo, source, opts):
1281 def subreporecurse():
1293 def subreporecurse():
1282 ret = 1
1294 ret = 1
1283 if opts.get(b'subrepos'):
1295 if opts.get(b'subrepos'):
1284 ctx = repo[None]
1296 ctx = repo[None]
1285 for subpath in sorted(ctx.substate):
1297 for subpath in sorted(ctx.substate):
1286 sub = ctx.sub(subpath)
1298 sub = ctx.sub(subpath)
1287 ret = min(ret, sub.incoming(ui, source, opts))
1299 ret = min(ret, sub.incoming(ui, source, opts))
1288 return ret
1300 return ret
1289
1301
1290 def display(other, chlist, displayer):
1302 def display(other, chlist, displayer):
1291 limit = logcmdutil.getlimit(opts)
1303 limit = logcmdutil.getlimit(opts)
1292 if opts.get(b'newest_first'):
1304 if opts.get(b'newest_first'):
1293 chlist.reverse()
1305 chlist.reverse()
1294 count = 0
1306 count = 0
1295 for n in chlist:
1307 for n in chlist:
1296 if limit is not None and count >= limit:
1308 if limit is not None and count >= limit:
1297 break
1309 break
1298 parents = [p for p in other.changelog.parents(n) if p != nullid]
1310 parents = [p for p in other.changelog.parents(n) if p != nullid]
1299 if opts.get(b'no_merges') and len(parents) == 2:
1311 if opts.get(b'no_merges') and len(parents) == 2:
1300 continue
1312 continue
1301 count += 1
1313 count += 1
1302 displayer.show(other[n])
1314 displayer.show(other[n])
1303
1315
1304 return _incoming(display, subreporecurse, ui, repo, source, opts)
1316 return _incoming(display, subreporecurse, ui, repo, source, opts)
1305
1317
1306
1318
1307 def _outgoing(ui, repo, dest, opts):
1319 def _outgoing(ui, repo, dest, opts):
1308 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
1320 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
1309 if not path:
1321 if not path:
1310 raise error.Abort(
1322 raise error.Abort(
1311 _(b'default repository not configured!'),
1323 _(b'default repository not configured!'),
1312 hint=_(b"see 'hg help config.paths'"),
1324 hint=_(b"see 'hg help config.paths'"),
1313 )
1325 )
1314 dest = path.pushloc or path.loc
1326 dest = path.pushloc or path.loc
1315 branches = path.branch, opts.get(b'branch') or []
1327 branches = path.branch, opts.get(b'branch') or []
1316
1328
1317 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1329 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1318 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1330 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1319 if revs:
1331 if revs:
1320 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1332 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1321
1333
1322 other = peer(repo, opts, dest)
1334 other = peer(repo, opts, dest)
1323 outgoing = discovery.findcommonoutgoing(
1335 try:
1324 repo, other, revs, force=opts.get(b'force')
1336 outgoing = discovery.findcommonoutgoing(
1325 )
1337 repo, other, revs, force=opts.get(b'force')
1326 o = outgoing.missing
1338 )
1327 if not o:
1339 o = outgoing.missing
1328 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1340 if not o:
1329 return o, other
1341 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1342 return o, other
1343 except: # re-raises
1344 other.close()
1345 raise
1330
1346
1331
1347
1332 def outgoing(ui, repo, dest, opts):
1348 def outgoing(ui, repo, dest, opts):
1333 def recurse():
1349 def recurse():
1334 ret = 1
1350 ret = 1
1335 if opts.get(b'subrepos'):
1351 if opts.get(b'subrepos'):
1336 ctx = repo[None]
1352 ctx = repo[None]
1337 for subpath in sorted(ctx.substate):
1353 for subpath in sorted(ctx.substate):
1338 sub = ctx.sub(subpath)
1354 sub = ctx.sub(subpath)
1339 ret = min(ret, sub.outgoing(ui, dest, opts))
1355 ret = min(ret, sub.outgoing(ui, dest, opts))
1340 return ret
1356 return ret
1341
1357
1342 limit = logcmdutil.getlimit(opts)
1358 limit = logcmdutil.getlimit(opts)
1343 o, other = _outgoing(ui, repo, dest, opts)
1359 o, other = _outgoing(ui, repo, dest, opts)
1344 if not o:
1360 try:
1345 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1361 if not o:
1346 return recurse()
1362 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1363 return recurse()
1347
1364
1348 if opts.get(b'newest_first'):
1365 if opts.get(b'newest_first'):
1349 o.reverse()
1366 o.reverse()
1350 ui.pager(b'outgoing')
1367 ui.pager(b'outgoing')
1351 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1368 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1352 count = 0
1369 count = 0
1353 for n in o:
1370 for n in o:
1354 if limit is not None and count >= limit:
1371 if limit is not None and count >= limit:
1355 break
1372 break
1356 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1373 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1357 if opts.get(b'no_merges') and len(parents) == 2:
1374 if opts.get(b'no_merges') and len(parents) == 2:
1358 continue
1375 continue
1359 count += 1
1376 count += 1
1360 displayer.show(repo[n])
1377 displayer.show(repo[n])
1361 displayer.close()
1378 displayer.close()
1362 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1379 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1363 recurse()
1380 recurse()
1364 return 0 # exit code is zero since we found outgoing changes
1381 return 0 # exit code is zero since we found outgoing changes
1382 finally:
1383 other.close()
1365
1384
1366
1385
1367 def verify(repo, level=None):
1386 def verify(repo, level=None):
1368 """verify the consistency of a repository"""
1387 """verify the consistency of a repository"""
1369 ret = verifymod.verify(repo, level=level)
1388 ret = verifymod.verify(repo, level=level)
1370
1389
1371 # Broken subrepo references in hidden csets don't seem worth worrying about,
1390 # Broken subrepo references in hidden csets don't seem worth worrying about,
1372 # since they can't be pushed/pulled, and --hidden can be used if they are a
1391 # since they can't be pushed/pulled, and --hidden can be used if they are a
1373 # concern.
1392 # concern.
1374
1393
1375 # pathto() is needed for -R case
1394 # pathto() is needed for -R case
1376 revs = repo.revs(
1395 revs = repo.revs(
1377 b"filelog(%s)", util.pathto(repo.root, repo.getcwd(), b'.hgsubstate')
1396 b"filelog(%s)", util.pathto(repo.root, repo.getcwd(), b'.hgsubstate')
1378 )
1397 )
1379
1398
1380 if revs:
1399 if revs:
1381 repo.ui.status(_(b'checking subrepo links\n'))
1400 repo.ui.status(_(b'checking subrepo links\n'))
1382 for rev in revs:
1401 for rev in revs:
1383 ctx = repo[rev]
1402 ctx = repo[rev]
1384 try:
1403 try:
1385 for subpath in ctx.substate:
1404 for subpath in ctx.substate:
1386 try:
1405 try:
1387 ret = (
1406 ret = (
1388 ctx.sub(subpath, allowcreate=False).verify() or ret
1407 ctx.sub(subpath, allowcreate=False).verify() or ret
1389 )
1408 )
1390 except error.RepoError as e:
1409 except error.RepoError as e:
1391 repo.ui.warn(b'%d: %s\n' % (rev, e))
1410 repo.ui.warn(b'%d: %s\n' % (rev, e))
1392 except Exception:
1411 except Exception:
1393 repo.ui.warn(
1412 repo.ui.warn(
1394 _(b'.hgsubstate is corrupt in revision %s\n')
1413 _(b'.hgsubstate is corrupt in revision %s\n')
1395 % short(ctx.node())
1414 % short(ctx.node())
1396 )
1415 )
1397
1416
1398 return ret
1417 return ret
1399
1418
1400
1419
1401 def remoteui(src, opts):
1420 def remoteui(src, opts):
1402 """build a remote ui from ui or repo and opts"""
1421 """build a remote ui from ui or repo and opts"""
1403 if util.safehasattr(src, b'baseui'): # looks like a repository
1422 if util.safehasattr(src, b'baseui'): # looks like a repository
1404 dst = src.baseui.copy() # drop repo-specific config
1423 dst = src.baseui.copy() # drop repo-specific config
1405 src = src.ui # copy target options from repo
1424 src = src.ui # copy target options from repo
1406 else: # assume it's a global ui object
1425 else: # assume it's a global ui object
1407 dst = src.copy() # keep all global options
1426 dst = src.copy() # keep all global options
1408
1427
1409 # copy ssh-specific options
1428 # copy ssh-specific options
1410 for o in b'ssh', b'remotecmd':
1429 for o in b'ssh', b'remotecmd':
1411 v = opts.get(o) or src.config(b'ui', o)
1430 v = opts.get(o) or src.config(b'ui', o)
1412 if v:
1431 if v:
1413 dst.setconfig(b"ui", o, v, b'copied')
1432 dst.setconfig(b"ui", o, v, b'copied')
1414
1433
1415 # copy bundle-specific options
1434 # copy bundle-specific options
1416 r = src.config(b'bundle', b'mainreporoot')
1435 r = src.config(b'bundle', b'mainreporoot')
1417 if r:
1436 if r:
1418 dst.setconfig(b'bundle', b'mainreporoot', r, b'copied')
1437 dst.setconfig(b'bundle', b'mainreporoot', r, b'copied')
1419
1438
1420 # copy selected local settings to the remote ui
1439 # copy selected local settings to the remote ui
1421 for sect in (b'auth', b'hostfingerprints', b'hostsecurity', b'http_proxy'):
1440 for sect in (b'auth', b'hostfingerprints', b'hostsecurity', b'http_proxy'):
1422 for key, val in src.configitems(sect):
1441 for key, val in src.configitems(sect):
1423 dst.setconfig(sect, key, val, b'copied')
1442 dst.setconfig(sect, key, val, b'copied')
1424 v = src.config(b'web', b'cacerts')
1443 v = src.config(b'web', b'cacerts')
1425 if v:
1444 if v:
1426 dst.setconfig(b'web', b'cacerts', util.expandpath(v), b'copied')
1445 dst.setconfig(b'web', b'cacerts', util.expandpath(v), b'copied')
1427
1446
1428 return dst
1447 return dst
1429
1448
1430
1449
1431 # Files of interest
1450 # Files of interest
1432 # Used to check if the repository has changed looking at mtime and size of
1451 # Used to check if the repository has changed looking at mtime and size of
1433 # these files.
1452 # these files.
1434 foi = [
1453 foi = [
1435 (b'spath', b'00changelog.i'),
1454 (b'spath', b'00changelog.i'),
1436 (b'spath', b'phaseroots'), # ! phase can change content at the same size
1455 (b'spath', b'phaseroots'), # ! phase can change content at the same size
1437 (b'spath', b'obsstore'),
1456 (b'spath', b'obsstore'),
1438 (b'path', b'bookmarks'), # ! bookmark can change content at the same size
1457 (b'path', b'bookmarks'), # ! bookmark can change content at the same size
1439 ]
1458 ]
1440
1459
1441
1460
1442 class cachedlocalrepo(object):
1461 class cachedlocalrepo(object):
1443 """Holds a localrepository that can be cached and reused."""
1462 """Holds a localrepository that can be cached and reused."""
1444
1463
1445 def __init__(self, repo):
1464 def __init__(self, repo):
1446 """Create a new cached repo from an existing repo.
1465 """Create a new cached repo from an existing repo.
1447
1466
1448 We assume the passed in repo was recently created. If the
1467 We assume the passed in repo was recently created. If the
1449 repo has changed between when it was created and when it was
1468 repo has changed between when it was created and when it was
1450 turned into a cache, it may not refresh properly.
1469 turned into a cache, it may not refresh properly.
1451 """
1470 """
1452 assert isinstance(repo, localrepo.localrepository)
1471 assert isinstance(repo, localrepo.localrepository)
1453 self._repo = repo
1472 self._repo = repo
1454 self._state, self.mtime = self._repostate()
1473 self._state, self.mtime = self._repostate()
1455 self._filtername = repo.filtername
1474 self._filtername = repo.filtername
1456
1475
1457 def fetch(self):
1476 def fetch(self):
1458 """Refresh (if necessary) and return a repository.
1477 """Refresh (if necessary) and return a repository.
1459
1478
1460 If the cached instance is out of date, it will be recreated
1479 If the cached instance is out of date, it will be recreated
1461 automatically and returned.
1480 automatically and returned.
1462
1481
1463 Returns a tuple of the repo and a boolean indicating whether a new
1482 Returns a tuple of the repo and a boolean indicating whether a new
1464 repo instance was created.
1483 repo instance was created.
1465 """
1484 """
1466 # We compare the mtimes and sizes of some well-known files to
1485 # We compare the mtimes and sizes of some well-known files to
1467 # determine if the repo changed. This is not precise, as mtimes
1486 # determine if the repo changed. This is not precise, as mtimes
1468 # are susceptible to clock skew and imprecise filesystems and
1487 # are susceptible to clock skew and imprecise filesystems and
1469 # file content can change while maintaining the same size.
1488 # file content can change while maintaining the same size.
1470
1489
1471 state, mtime = self._repostate()
1490 state, mtime = self._repostate()
1472 if state == self._state:
1491 if state == self._state:
1473 return self._repo, False
1492 return self._repo, False
1474
1493
1475 repo = repository(self._repo.baseui, self._repo.url())
1494 repo = repository(self._repo.baseui, self._repo.url())
1476 if self._filtername:
1495 if self._filtername:
1477 self._repo = repo.filtered(self._filtername)
1496 self._repo = repo.filtered(self._filtername)
1478 else:
1497 else:
1479 self._repo = repo.unfiltered()
1498 self._repo = repo.unfiltered()
1480 self._state = state
1499 self._state = state
1481 self.mtime = mtime
1500 self.mtime = mtime
1482
1501
1483 return self._repo, True
1502 return self._repo, True
1484
1503
1485 def _repostate(self):
1504 def _repostate(self):
1486 state = []
1505 state = []
1487 maxmtime = -1
1506 maxmtime = -1
1488 for attr, fname in foi:
1507 for attr, fname in foi:
1489 prefix = getattr(self._repo, attr)
1508 prefix = getattr(self._repo, attr)
1490 p = os.path.join(prefix, fname)
1509 p = os.path.join(prefix, fname)
1491 try:
1510 try:
1492 st = os.stat(p)
1511 st = os.stat(p)
1493 except OSError:
1512 except OSError:
1494 st = os.stat(prefix)
1513 st = os.stat(prefix)
1495 state.append((st[stat.ST_MTIME], st.st_size))
1514 state.append((st[stat.ST_MTIME], st.st_size))
1496 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1515 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1497
1516
1498 return tuple(state), maxmtime
1517 return tuple(state), maxmtime
1499
1518
1500 def copy(self):
1519 def copy(self):
1501 """Obtain a copy of this class instance.
1520 """Obtain a copy of this class instance.
1502
1521
1503 A new localrepository instance is obtained. The new instance should be
1522 A new localrepository instance is obtained. The new instance should be
1504 completely independent of the original.
1523 completely independent of the original.
1505 """
1524 """
1506 repo = repository(self._repo.baseui, self._repo.origroot)
1525 repo = repository(self._repo.baseui, self._repo.origroot)
1507 if self._filtername:
1526 if self._filtername:
1508 repo = repo.filtered(self._filtername)
1527 repo = repo.filtered(self._filtername)
1509 else:
1528 else:
1510 repo = repo.unfiltered()
1529 repo = repo.unfiltered()
1511 c = cachedlocalrepo(repo)
1530 c = cachedlocalrepo(repo)
1512 c._state = self._state
1531 c._state = self._state
1513 c.mtime = self.mtime
1532 c.mtime = self.mtime
1514 return c
1533 return c
@@ -1,2779 +1,2782 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 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 re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from .pycompat import getattr
13 from .pycompat import getattr
14 from .node import (
14 from .node import (
15 bin,
15 bin,
16 nullrev,
16 nullrev,
17 wdirrev,
17 wdirrev,
18 )
18 )
19 from . import (
19 from . import (
20 dagop,
20 dagop,
21 destutil,
21 destutil,
22 diffutil,
22 diffutil,
23 encoding,
23 encoding,
24 error,
24 error,
25 grep as grepmod,
25 grep as grepmod,
26 hbisect,
26 hbisect,
27 match as matchmod,
27 match as matchmod,
28 obsolete as obsmod,
28 obsolete as obsmod,
29 obsutil,
29 obsutil,
30 pathutil,
30 pathutil,
31 phases,
31 phases,
32 pycompat,
32 pycompat,
33 registrar,
33 registrar,
34 repoview,
34 repoview,
35 revsetlang,
35 revsetlang,
36 scmutil,
36 scmutil,
37 smartset,
37 smartset,
38 stack as stackmod,
38 stack as stackmod,
39 util,
39 util,
40 )
40 )
41 from .utils import (
41 from .utils import (
42 dateutil,
42 dateutil,
43 stringutil,
43 stringutil,
44 )
44 )
45
45
46 # helpers for processing parsed tree
46 # helpers for processing parsed tree
47 getsymbol = revsetlang.getsymbol
47 getsymbol = revsetlang.getsymbol
48 getstring = revsetlang.getstring
48 getstring = revsetlang.getstring
49 getinteger = revsetlang.getinteger
49 getinteger = revsetlang.getinteger
50 getboolean = revsetlang.getboolean
50 getboolean = revsetlang.getboolean
51 getlist = revsetlang.getlist
51 getlist = revsetlang.getlist
52 getintrange = revsetlang.getintrange
52 getintrange = revsetlang.getintrange
53 getargs = revsetlang.getargs
53 getargs = revsetlang.getargs
54 getargsdict = revsetlang.getargsdict
54 getargsdict = revsetlang.getargsdict
55
55
56 baseset = smartset.baseset
56 baseset = smartset.baseset
57 generatorset = smartset.generatorset
57 generatorset = smartset.generatorset
58 spanset = smartset.spanset
58 spanset = smartset.spanset
59 fullreposet = smartset.fullreposet
59 fullreposet = smartset.fullreposet
60
60
61 # revisions not included in all(), but populated if specified
61 # revisions not included in all(), but populated if specified
62 _virtualrevs = (nullrev, wdirrev)
62 _virtualrevs = (nullrev, wdirrev)
63
63
64 # Constants for ordering requirement, used in getset():
64 # Constants for ordering requirement, used in getset():
65 #
65 #
66 # If 'define', any nested functions and operations MAY change the ordering of
66 # If 'define', any nested functions and operations MAY change the ordering of
67 # the entries in the set (but if changes the ordering, it MUST ALWAYS change
67 # the entries in the set (but if changes the ordering, it MUST ALWAYS change
68 # it). If 'follow', any nested functions and operations MUST take the ordering
68 # it). If 'follow', any nested functions and operations MUST take the ordering
69 # specified by the first operand to the '&' operator.
69 # specified by the first operand to the '&' operator.
70 #
70 #
71 # For instance,
71 # For instance,
72 #
72 #
73 # X & (Y | Z)
73 # X & (Y | Z)
74 # ^ ^^^^^^^
74 # ^ ^^^^^^^
75 # | follow
75 # | follow
76 # define
76 # define
77 #
77 #
78 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
78 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
79 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
79 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
80 #
80 #
81 # 'any' means the order doesn't matter. For instance,
81 # 'any' means the order doesn't matter. For instance,
82 #
82 #
83 # (X & !Y) | ancestors(Z)
83 # (X & !Y) | ancestors(Z)
84 # ^ ^
84 # ^ ^
85 # any any
85 # any any
86 #
86 #
87 # For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
87 # For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
88 # order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
88 # order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
89 # since 'ancestors' does not care about the order of its argument.
89 # since 'ancestors' does not care about the order of its argument.
90 #
90 #
91 # Currently, most revsets do not care about the order, so 'define' is
91 # Currently, most revsets do not care about the order, so 'define' is
92 # equivalent to 'follow' for them, and the resulting order is based on the
92 # equivalent to 'follow' for them, and the resulting order is based on the
93 # 'subset' parameter passed down to them:
93 # 'subset' parameter passed down to them:
94 #
94 #
95 # m = revset.match(...)
95 # m = revset.match(...)
96 # m(repo, subset, order=defineorder)
96 # m(repo, subset, order=defineorder)
97 # ^^^^^^
97 # ^^^^^^
98 # For most revsets, 'define' means using the order this subset provides
98 # For most revsets, 'define' means using the order this subset provides
99 #
99 #
100 # There are a few revsets that always redefine the order if 'define' is
100 # There are a few revsets that always redefine the order if 'define' is
101 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
101 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
102 anyorder = b'any' # don't care the order, could be even random-shuffled
102 anyorder = b'any' # don't care the order, could be even random-shuffled
103 defineorder = b'define' # ALWAYS redefine, or ALWAYS follow the current order
103 defineorder = b'define' # ALWAYS redefine, or ALWAYS follow the current order
104 followorder = b'follow' # MUST follow the current order
104 followorder = b'follow' # MUST follow the current order
105
105
106 # helpers
106 # helpers
107
107
108
108
109 def getset(repo, subset, x, order=defineorder):
109 def getset(repo, subset, x, order=defineorder):
110 if not x:
110 if not x:
111 raise error.ParseError(_(b"missing argument"))
111 raise error.ParseError(_(b"missing argument"))
112 return methods[x[0]](repo, subset, *x[1:], order=order)
112 return methods[x[0]](repo, subset, *x[1:], order=order)
113
113
114
114
115 def _getrevsource(repo, r):
115 def _getrevsource(repo, r):
116 extra = repo[r].extra()
116 extra = repo[r].extra()
117 for label in (b'source', b'transplant_source', b'rebase_source'):
117 for label in (b'source', b'transplant_source', b'rebase_source'):
118 if label in extra:
118 if label in extra:
119 try:
119 try:
120 return repo[extra[label]].rev()
120 return repo[extra[label]].rev()
121 except error.RepoLookupError:
121 except error.RepoLookupError:
122 pass
122 pass
123 return None
123 return None
124
124
125
125
126 def _sortedb(xs):
126 def _sortedb(xs):
127 return sorted(pycompat.rapply(pycompat.maybebytestr, xs))
127 return sorted(pycompat.rapply(pycompat.maybebytestr, xs))
128
128
129
129
130 # operator methods
130 # operator methods
131
131
132
132
133 def stringset(repo, subset, x, order):
133 def stringset(repo, subset, x, order):
134 if not x:
134 if not x:
135 raise error.ParseError(_(b"empty string is not a valid revision"))
135 raise error.ParseError(_(b"empty string is not a valid revision"))
136 x = scmutil.intrev(scmutil.revsymbol(repo, x))
136 x = scmutil.intrev(scmutil.revsymbol(repo, x))
137 if x in subset or x in _virtualrevs and isinstance(subset, fullreposet):
137 if x in subset or x in _virtualrevs and isinstance(subset, fullreposet):
138 return baseset([x])
138 return baseset([x])
139 return baseset()
139 return baseset()
140
140
141
141
142 def rawsmartset(repo, subset, x, order):
142 def rawsmartset(repo, subset, x, order):
143 """argument is already a smartset, use that directly"""
143 """argument is already a smartset, use that directly"""
144 if order == followorder:
144 if order == followorder:
145 return subset & x
145 return subset & x
146 else:
146 else:
147 return x & subset
147 return x & subset
148
148
149
149
150 def rangeset(repo, subset, x, y, order):
150 def rangeset(repo, subset, x, y, order):
151 m = getset(repo, fullreposet(repo), x)
151 m = getset(repo, fullreposet(repo), x)
152 n = getset(repo, fullreposet(repo), y)
152 n = getset(repo, fullreposet(repo), y)
153
153
154 if not m or not n:
154 if not m or not n:
155 return baseset()
155 return baseset()
156 return _makerangeset(repo, subset, m.first(), n.last(), order)
156 return _makerangeset(repo, subset, m.first(), n.last(), order)
157
157
158
158
159 def rangeall(repo, subset, x, order):
159 def rangeall(repo, subset, x, order):
160 assert x is None
160 assert x is None
161 return _makerangeset(repo, subset, 0, repo.changelog.tiprev(), order)
161 return _makerangeset(repo, subset, 0, repo.changelog.tiprev(), order)
162
162
163
163
164 def rangepre(repo, subset, y, order):
164 def rangepre(repo, subset, y, order):
165 # ':y' can't be rewritten to '0:y' since '0' may be hidden
165 # ':y' can't be rewritten to '0:y' since '0' may be hidden
166 n = getset(repo, fullreposet(repo), y)
166 n = getset(repo, fullreposet(repo), y)
167 if not n:
167 if not n:
168 return baseset()
168 return baseset()
169 return _makerangeset(repo, subset, 0, n.last(), order)
169 return _makerangeset(repo, subset, 0, n.last(), order)
170
170
171
171
172 def rangepost(repo, subset, x, order):
172 def rangepost(repo, subset, x, order):
173 m = getset(repo, fullreposet(repo), x)
173 m = getset(repo, fullreposet(repo), x)
174 if not m:
174 if not m:
175 return baseset()
175 return baseset()
176 return _makerangeset(
176 return _makerangeset(
177 repo, subset, m.first(), repo.changelog.tiprev(), order
177 repo, subset, m.first(), repo.changelog.tiprev(), order
178 )
178 )
179
179
180
180
181 def _makerangeset(repo, subset, m, n, order):
181 def _makerangeset(repo, subset, m, n, order):
182 if m == n:
182 if m == n:
183 r = baseset([m])
183 r = baseset([m])
184 elif n == wdirrev:
184 elif n == wdirrev:
185 r = spanset(repo, m, len(repo)) + baseset([n])
185 r = spanset(repo, m, len(repo)) + baseset([n])
186 elif m == wdirrev:
186 elif m == wdirrev:
187 r = baseset([m]) + spanset(repo, repo.changelog.tiprev(), n - 1)
187 r = baseset([m]) + spanset(repo, repo.changelog.tiprev(), n - 1)
188 elif m < n:
188 elif m < n:
189 r = spanset(repo, m, n + 1)
189 r = spanset(repo, m, n + 1)
190 else:
190 else:
191 r = spanset(repo, m, n - 1)
191 r = spanset(repo, m, n - 1)
192
192
193 if order == defineorder:
193 if order == defineorder:
194 return r & subset
194 return r & subset
195 else:
195 else:
196 # carrying the sorting over when possible would be more efficient
196 # carrying the sorting over when possible would be more efficient
197 return subset & r
197 return subset & r
198
198
199
199
200 def dagrange(repo, subset, x, y, order):
200 def dagrange(repo, subset, x, y, order):
201 r = fullreposet(repo)
201 r = fullreposet(repo)
202 xs = dagop.reachableroots(
202 xs = dagop.reachableroots(
203 repo, getset(repo, r, x), getset(repo, r, y), includepath=True
203 repo, getset(repo, r, x), getset(repo, r, y), includepath=True
204 )
204 )
205 return subset & xs
205 return subset & xs
206
206
207
207
208 def andset(repo, subset, x, y, order):
208 def andset(repo, subset, x, y, order):
209 if order == anyorder:
209 if order == anyorder:
210 yorder = anyorder
210 yorder = anyorder
211 else:
211 else:
212 yorder = followorder
212 yorder = followorder
213 return getset(repo, getset(repo, subset, x, order), y, yorder)
213 return getset(repo, getset(repo, subset, x, order), y, yorder)
214
214
215
215
216 def andsmallyset(repo, subset, x, y, order):
216 def andsmallyset(repo, subset, x, y, order):
217 # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
217 # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
218 if order == anyorder:
218 if order == anyorder:
219 yorder = anyorder
219 yorder = anyorder
220 else:
220 else:
221 yorder = followorder
221 yorder = followorder
222 return getset(repo, getset(repo, subset, y, yorder), x, order)
222 return getset(repo, getset(repo, subset, y, yorder), x, order)
223
223
224
224
225 def differenceset(repo, subset, x, y, order):
225 def differenceset(repo, subset, x, y, order):
226 return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
226 return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
227
227
228
228
229 def _orsetlist(repo, subset, xs, order):
229 def _orsetlist(repo, subset, xs, order):
230 assert xs
230 assert xs
231 if len(xs) == 1:
231 if len(xs) == 1:
232 return getset(repo, subset, xs[0], order)
232 return getset(repo, subset, xs[0], order)
233 p = len(xs) // 2
233 p = len(xs) // 2
234 a = _orsetlist(repo, subset, xs[:p], order)
234 a = _orsetlist(repo, subset, xs[:p], order)
235 b = _orsetlist(repo, subset, xs[p:], order)
235 b = _orsetlist(repo, subset, xs[p:], order)
236 return a + b
236 return a + b
237
237
238
238
239 def orset(repo, subset, x, order):
239 def orset(repo, subset, x, order):
240 xs = getlist(x)
240 xs = getlist(x)
241 if not xs:
241 if not xs:
242 return baseset()
242 return baseset()
243 if order == followorder:
243 if order == followorder:
244 # slow path to take the subset order
244 # slow path to take the subset order
245 return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
245 return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
246 else:
246 else:
247 return _orsetlist(repo, subset, xs, order)
247 return _orsetlist(repo, subset, xs, order)
248
248
249
249
250 def notset(repo, subset, x, order):
250 def notset(repo, subset, x, order):
251 return subset - getset(repo, subset, x, anyorder)
251 return subset - getset(repo, subset, x, anyorder)
252
252
253
253
254 def relationset(repo, subset, x, y, order):
254 def relationset(repo, subset, x, y, order):
255 # this is pretty basic implementation of 'x#y' operator, still
255 # this is pretty basic implementation of 'x#y' operator, still
256 # experimental so undocumented. see the wiki for further ideas.
256 # experimental so undocumented. see the wiki for further ideas.
257 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
257 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
258 rel = getsymbol(y)
258 rel = getsymbol(y)
259 if rel in relations:
259 if rel in relations:
260 return relations[rel](repo, subset, x, rel, order)
260 return relations[rel](repo, subset, x, rel, order)
261
261
262 relnames = [r for r in relations.keys() if len(r) > 1]
262 relnames = [r for r in relations.keys() if len(r) > 1]
263 raise error.UnknownIdentifier(rel, relnames)
263 raise error.UnknownIdentifier(rel, relnames)
264
264
265
265
266 def _splitrange(a, b):
266 def _splitrange(a, b):
267 """Split range with bounds a and b into two ranges at 0 and return two
267 """Split range with bounds a and b into two ranges at 0 and return two
268 tuples of numbers for use as startdepth and stopdepth arguments of
268 tuples of numbers for use as startdepth and stopdepth arguments of
269 revancestors and revdescendants.
269 revancestors and revdescendants.
270
270
271 >>> _splitrange(-10, -5) # [-10:-5]
271 >>> _splitrange(-10, -5) # [-10:-5]
272 ((5, 11), (None, None))
272 ((5, 11), (None, None))
273 >>> _splitrange(5, 10) # [5:10]
273 >>> _splitrange(5, 10) # [5:10]
274 ((None, None), (5, 11))
274 ((None, None), (5, 11))
275 >>> _splitrange(-10, 10) # [-10:10]
275 >>> _splitrange(-10, 10) # [-10:10]
276 ((0, 11), (0, 11))
276 ((0, 11), (0, 11))
277 >>> _splitrange(-10, 0) # [-10:0]
277 >>> _splitrange(-10, 0) # [-10:0]
278 ((0, 11), (None, None))
278 ((0, 11), (None, None))
279 >>> _splitrange(0, 10) # [0:10]
279 >>> _splitrange(0, 10) # [0:10]
280 ((None, None), (0, 11))
280 ((None, None), (0, 11))
281 >>> _splitrange(0, 0) # [0:0]
281 >>> _splitrange(0, 0) # [0:0]
282 ((0, 1), (None, None))
282 ((0, 1), (None, None))
283 >>> _splitrange(1, -1) # [1:-1]
283 >>> _splitrange(1, -1) # [1:-1]
284 ((None, None), (None, None))
284 ((None, None), (None, None))
285 """
285 """
286 ancdepths = (None, None)
286 ancdepths = (None, None)
287 descdepths = (None, None)
287 descdepths = (None, None)
288 if a == b == 0:
288 if a == b == 0:
289 ancdepths = (0, 1)
289 ancdepths = (0, 1)
290 if a < 0:
290 if a < 0:
291 ancdepths = (-min(b, 0), -a + 1)
291 ancdepths = (-min(b, 0), -a + 1)
292 if b > 0:
292 if b > 0:
293 descdepths = (max(a, 0), b + 1)
293 descdepths = (max(a, 0), b + 1)
294 return ancdepths, descdepths
294 return ancdepths, descdepths
295
295
296
296
297 def generationsrel(repo, subset, x, rel, order):
297 def generationsrel(repo, subset, x, rel, order):
298 z = (b'rangeall', None)
298 z = (b'rangeall', None)
299 return generationssubrel(repo, subset, x, rel, z, order)
299 return generationssubrel(repo, subset, x, rel, z, order)
300
300
301
301
302 def generationssubrel(repo, subset, x, rel, z, order):
302 def generationssubrel(repo, subset, x, rel, z, order):
303 # TODO: rewrite tests, and drop startdepth argument from ancestors() and
303 # TODO: rewrite tests, and drop startdepth argument from ancestors() and
304 # descendants() predicates
304 # descendants() predicates
305 a, b = getintrange(
305 a, b = getintrange(
306 z,
306 z,
307 _(b'relation subscript must be an integer or a range'),
307 _(b'relation subscript must be an integer or a range'),
308 _(b'relation subscript bounds must be integers'),
308 _(b'relation subscript bounds must be integers'),
309 deffirst=-(dagop.maxlogdepth - 1),
309 deffirst=-(dagop.maxlogdepth - 1),
310 deflast=+(dagop.maxlogdepth - 1),
310 deflast=+(dagop.maxlogdepth - 1),
311 )
311 )
312 (ancstart, ancstop), (descstart, descstop) = _splitrange(a, b)
312 (ancstart, ancstop), (descstart, descstop) = _splitrange(a, b)
313
313
314 if ancstart is None and descstart is None:
314 if ancstart is None and descstart is None:
315 return baseset()
315 return baseset()
316
316
317 revs = getset(repo, fullreposet(repo), x)
317 revs = getset(repo, fullreposet(repo), x)
318 if not revs:
318 if not revs:
319 return baseset()
319 return baseset()
320
320
321 if ancstart is not None and descstart is not None:
321 if ancstart is not None and descstart is not None:
322 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
322 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
323 s += dagop.revdescendants(repo, revs, False, descstart, descstop)
323 s += dagop.revdescendants(repo, revs, False, descstart, descstop)
324 elif ancstart is not None:
324 elif ancstart is not None:
325 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
325 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
326 elif descstart is not None:
326 elif descstart is not None:
327 s = dagop.revdescendants(repo, revs, False, descstart, descstop)
327 s = dagop.revdescendants(repo, revs, False, descstart, descstop)
328
328
329 return subset & s
329 return subset & s
330
330
331
331
332 def relsubscriptset(repo, subset, x, y, z, order):
332 def relsubscriptset(repo, subset, x, y, z, order):
333 # this is pretty basic implementation of 'x#y[z]' operator, still
333 # this is pretty basic implementation of 'x#y[z]' operator, still
334 # experimental so undocumented. see the wiki for further ideas.
334 # experimental so undocumented. see the wiki for further ideas.
335 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
335 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
336 rel = getsymbol(y)
336 rel = getsymbol(y)
337 if rel in subscriptrelations:
337 if rel in subscriptrelations:
338 return subscriptrelations[rel](repo, subset, x, rel, z, order)
338 return subscriptrelations[rel](repo, subset, x, rel, z, order)
339
339
340 relnames = [r for r in subscriptrelations.keys() if len(r) > 1]
340 relnames = [r for r in subscriptrelations.keys() if len(r) > 1]
341 raise error.UnknownIdentifier(rel, relnames)
341 raise error.UnknownIdentifier(rel, relnames)
342
342
343
343
344 def subscriptset(repo, subset, x, y, order):
344 def subscriptset(repo, subset, x, y, order):
345 raise error.ParseError(_(b"can't use a subscript in this context"))
345 raise error.ParseError(_(b"can't use a subscript in this context"))
346
346
347
347
348 def listset(repo, subset, *xs, **opts):
348 def listset(repo, subset, *xs, **opts):
349 raise error.ParseError(
349 raise error.ParseError(
350 _(b"can't use a list in this context"),
350 _(b"can't use a list in this context"),
351 hint=_(b'see \'hg help "revsets.x or y"\''),
351 hint=_(b'see \'hg help "revsets.x or y"\''),
352 )
352 )
353
353
354
354
355 def keyvaluepair(repo, subset, k, v, order):
355 def keyvaluepair(repo, subset, k, v, order):
356 raise error.ParseError(_(b"can't use a key-value pair in this context"))
356 raise error.ParseError(_(b"can't use a key-value pair in this context"))
357
357
358
358
359 def func(repo, subset, a, b, order):
359 def func(repo, subset, a, b, order):
360 f = getsymbol(a)
360 f = getsymbol(a)
361 if f in symbols:
361 if f in symbols:
362 func = symbols[f]
362 func = symbols[f]
363 if getattr(func, '_takeorder', False):
363 if getattr(func, '_takeorder', False):
364 return func(repo, subset, b, order)
364 return func(repo, subset, b, order)
365 return func(repo, subset, b)
365 return func(repo, subset, b)
366
366
367 keep = lambda fn: getattr(fn, '__doc__', None) is not None
367 keep = lambda fn: getattr(fn, '__doc__', None) is not None
368
368
369 syms = [s for (s, fn) in symbols.items() if keep(fn)]
369 syms = [s for (s, fn) in symbols.items() if keep(fn)]
370 raise error.UnknownIdentifier(f, syms)
370 raise error.UnknownIdentifier(f, syms)
371
371
372
372
373 # functions
373 # functions
374
374
375 # symbols are callables like:
375 # symbols are callables like:
376 # fn(repo, subset, x)
376 # fn(repo, subset, x)
377 # with:
377 # with:
378 # repo - current repository instance
378 # repo - current repository instance
379 # subset - of revisions to be examined
379 # subset - of revisions to be examined
380 # x - argument in tree form
380 # x - argument in tree form
381 symbols = revsetlang.symbols
381 symbols = revsetlang.symbols
382
382
383 # symbols which can't be used for a DoS attack for any given input
383 # symbols which can't be used for a DoS attack for any given input
384 # (e.g. those which accept regexes as plain strings shouldn't be included)
384 # (e.g. those which accept regexes as plain strings shouldn't be included)
385 # functions that just return a lot of changesets (like all) don't count here
385 # functions that just return a lot of changesets (like all) don't count here
386 safesymbols = set()
386 safesymbols = set()
387
387
388 predicate = registrar.revsetpredicate()
388 predicate = registrar.revsetpredicate()
389
389
390
390
391 @predicate(b'_destupdate')
391 @predicate(b'_destupdate')
392 def _destupdate(repo, subset, x):
392 def _destupdate(repo, subset, x):
393 # experimental revset for update destination
393 # experimental revset for update destination
394 args = getargsdict(x, b'limit', b'clean')
394 args = getargsdict(x, b'limit', b'clean')
395 return subset & baseset(
395 return subset & baseset(
396 [destutil.destupdate(repo, **pycompat.strkwargs(args))[0]]
396 [destutil.destupdate(repo, **pycompat.strkwargs(args))[0]]
397 )
397 )
398
398
399
399
400 @predicate(b'_destmerge')
400 @predicate(b'_destmerge')
401 def _destmerge(repo, subset, x):
401 def _destmerge(repo, subset, x):
402 # experimental revset for merge destination
402 # experimental revset for merge destination
403 sourceset = None
403 sourceset = None
404 if x is not None:
404 if x is not None:
405 sourceset = getset(repo, fullreposet(repo), x)
405 sourceset = getset(repo, fullreposet(repo), x)
406 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
406 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
407
407
408
408
409 @predicate(b'adds(pattern)', safe=True, weight=30)
409 @predicate(b'adds(pattern)', safe=True, weight=30)
410 def adds(repo, subset, x):
410 def adds(repo, subset, x):
411 """Changesets that add a file matching pattern.
411 """Changesets that add a file matching pattern.
412
412
413 The pattern without explicit kind like ``glob:`` is expected to be
413 The pattern without explicit kind like ``glob:`` is expected to be
414 relative to the current directory and match against a file or a
414 relative to the current directory and match against a file or a
415 directory.
415 directory.
416 """
416 """
417 # i18n: "adds" is a keyword
417 # i18n: "adds" is a keyword
418 pat = getstring(x, _(b"adds requires a pattern"))
418 pat = getstring(x, _(b"adds requires a pattern"))
419 return checkstatus(repo, subset, pat, 'added')
419 return checkstatus(repo, subset, pat, 'added')
420
420
421
421
422 @predicate(b'ancestor(*changeset)', safe=True, weight=0.5)
422 @predicate(b'ancestor(*changeset)', safe=True, weight=0.5)
423 def ancestor(repo, subset, x):
423 def ancestor(repo, subset, x):
424 """A greatest common ancestor of the changesets.
424 """A greatest common ancestor of the changesets.
425
425
426 Accepts 0 or more changesets.
426 Accepts 0 or more changesets.
427 Will return empty list when passed no args.
427 Will return empty list when passed no args.
428 Greatest common ancestor of a single changeset is that changeset.
428 Greatest common ancestor of a single changeset is that changeset.
429 """
429 """
430 reviter = iter(orset(repo, fullreposet(repo), x, order=anyorder))
430 reviter = iter(orset(repo, fullreposet(repo), x, order=anyorder))
431 try:
431 try:
432 anc = repo[next(reviter)]
432 anc = repo[next(reviter)]
433 except StopIteration:
433 except StopIteration:
434 return baseset()
434 return baseset()
435 for r in reviter:
435 for r in reviter:
436 anc = anc.ancestor(repo[r])
436 anc = anc.ancestor(repo[r])
437
437
438 r = scmutil.intrev(anc)
438 r = scmutil.intrev(anc)
439 if r in subset:
439 if r in subset:
440 return baseset([r])
440 return baseset([r])
441 return baseset()
441 return baseset()
442
442
443
443
444 def _ancestors(
444 def _ancestors(
445 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
445 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
446 ):
446 ):
447 heads = getset(repo, fullreposet(repo), x)
447 heads = getset(repo, fullreposet(repo), x)
448 if not heads:
448 if not heads:
449 return baseset()
449 return baseset()
450 s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
450 s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
451 return subset & s
451 return subset & s
452
452
453
453
454 @predicate(b'ancestors(set[, depth])', safe=True)
454 @predicate(b'ancestors(set[, depth])', safe=True)
455 def ancestors(repo, subset, x):
455 def ancestors(repo, subset, x):
456 """Changesets that are ancestors of changesets in set, including the
456 """Changesets that are ancestors of changesets in set, including the
457 given changesets themselves.
457 given changesets themselves.
458
458
459 If depth is specified, the result only includes changesets up to
459 If depth is specified, the result only includes changesets up to
460 the specified generation.
460 the specified generation.
461 """
461 """
462 # startdepth is for internal use only until we can decide the UI
462 # startdepth is for internal use only until we can decide the UI
463 args = getargsdict(x, b'ancestors', b'set depth startdepth')
463 args = getargsdict(x, b'ancestors', b'set depth startdepth')
464 if b'set' not in args:
464 if b'set' not in args:
465 # i18n: "ancestors" is a keyword
465 # i18n: "ancestors" is a keyword
466 raise error.ParseError(_(b'ancestors takes at least 1 argument'))
466 raise error.ParseError(_(b'ancestors takes at least 1 argument'))
467 startdepth = stopdepth = None
467 startdepth = stopdepth = None
468 if b'startdepth' in args:
468 if b'startdepth' in args:
469 n = getinteger(
469 n = getinteger(
470 args[b'startdepth'], b"ancestors expects an integer startdepth"
470 args[b'startdepth'], b"ancestors expects an integer startdepth"
471 )
471 )
472 if n < 0:
472 if n < 0:
473 raise error.ParseError(b"negative startdepth")
473 raise error.ParseError(b"negative startdepth")
474 startdepth = n
474 startdepth = n
475 if b'depth' in args:
475 if b'depth' in args:
476 # i18n: "ancestors" is a keyword
476 # i18n: "ancestors" is a keyword
477 n = getinteger(args[b'depth'], _(b"ancestors expects an integer depth"))
477 n = getinteger(args[b'depth'], _(b"ancestors expects an integer depth"))
478 if n < 0:
478 if n < 0:
479 raise error.ParseError(_(b"negative depth"))
479 raise error.ParseError(_(b"negative depth"))
480 stopdepth = n + 1
480 stopdepth = n + 1
481 return _ancestors(
481 return _ancestors(
482 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
482 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
483 )
483 )
484
484
485
485
486 @predicate(b'_firstancestors', safe=True)
486 @predicate(b'_firstancestors', safe=True)
487 def _firstancestors(repo, subset, x):
487 def _firstancestors(repo, subset, x):
488 # ``_firstancestors(set)``
488 # ``_firstancestors(set)``
489 # Like ``ancestors(set)`` but follows only the first parents.
489 # Like ``ancestors(set)`` but follows only the first parents.
490 return _ancestors(repo, subset, x, followfirst=True)
490 return _ancestors(repo, subset, x, followfirst=True)
491
491
492
492
493 def _childrenspec(repo, subset, x, n, order):
493 def _childrenspec(repo, subset, x, n, order):
494 """Changesets that are the Nth child of a changeset
494 """Changesets that are the Nth child of a changeset
495 in set.
495 in set.
496 """
496 """
497 cs = set()
497 cs = set()
498 for r in getset(repo, fullreposet(repo), x):
498 for r in getset(repo, fullreposet(repo), x):
499 for i in range(n):
499 for i in range(n):
500 c = repo[r].children()
500 c = repo[r].children()
501 if len(c) == 0:
501 if len(c) == 0:
502 break
502 break
503 if len(c) > 1:
503 if len(c) > 1:
504 raise error.RepoLookupError(
504 raise error.RepoLookupError(
505 _(b"revision in set has more than one child")
505 _(b"revision in set has more than one child")
506 )
506 )
507 r = c[0].rev()
507 r = c[0].rev()
508 else:
508 else:
509 cs.add(r)
509 cs.add(r)
510 return subset & cs
510 return subset & cs
511
511
512
512
513 def ancestorspec(repo, subset, x, n, order):
513 def ancestorspec(repo, subset, x, n, order):
514 """``set~n``
514 """``set~n``
515 Changesets that are the Nth ancestor (first parents only) of a changeset
515 Changesets that are the Nth ancestor (first parents only) of a changeset
516 in set.
516 in set.
517 """
517 """
518 n = getinteger(n, _(b"~ expects a number"))
518 n = getinteger(n, _(b"~ expects a number"))
519 if n < 0:
519 if n < 0:
520 # children lookup
520 # children lookup
521 return _childrenspec(repo, subset, x, -n, order)
521 return _childrenspec(repo, subset, x, -n, order)
522 ps = set()
522 ps = set()
523 cl = repo.changelog
523 cl = repo.changelog
524 for r in getset(repo, fullreposet(repo), x):
524 for r in getset(repo, fullreposet(repo), x):
525 for i in range(n):
525 for i in range(n):
526 try:
526 try:
527 r = cl.parentrevs(r)[0]
527 r = cl.parentrevs(r)[0]
528 except error.WdirUnsupported:
528 except error.WdirUnsupported:
529 r = repo[r].p1().rev()
529 r = repo[r].p1().rev()
530 ps.add(r)
530 ps.add(r)
531 return subset & ps
531 return subset & ps
532
532
533
533
534 @predicate(b'author(string)', safe=True, weight=10)
534 @predicate(b'author(string)', safe=True, weight=10)
535 def author(repo, subset, x):
535 def author(repo, subset, x):
536 """Alias for ``user(string)``."""
536 """Alias for ``user(string)``."""
537 # i18n: "author" is a keyword
537 # i18n: "author" is a keyword
538 n = getstring(x, _(b"author requires a string"))
538 n = getstring(x, _(b"author requires a string"))
539 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
539 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
540 return subset.filter(
540 return subset.filter(
541 lambda x: matcher(repo[x].user()), condrepr=(b'<user %r>', n)
541 lambda x: matcher(repo[x].user()), condrepr=(b'<user %r>', n)
542 )
542 )
543
543
544
544
545 @predicate(b'bisect(string)', safe=True)
545 @predicate(b'bisect(string)', safe=True)
546 def bisect(repo, subset, x):
546 def bisect(repo, subset, x):
547 """Changesets marked in the specified bisect status:
547 """Changesets marked in the specified bisect status:
548
548
549 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
549 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
550 - ``goods``, ``bads`` : csets topologically good/bad
550 - ``goods``, ``bads`` : csets topologically good/bad
551 - ``range`` : csets taking part in the bisection
551 - ``range`` : csets taking part in the bisection
552 - ``pruned`` : csets that are goods, bads or skipped
552 - ``pruned`` : csets that are goods, bads or skipped
553 - ``untested`` : csets whose fate is yet unknown
553 - ``untested`` : csets whose fate is yet unknown
554 - ``ignored`` : csets ignored due to DAG topology
554 - ``ignored`` : csets ignored due to DAG topology
555 - ``current`` : the cset currently being bisected
555 - ``current`` : the cset currently being bisected
556 """
556 """
557 # i18n: "bisect" is a keyword
557 # i18n: "bisect" is a keyword
558 status = getstring(x, _(b"bisect requires a string")).lower()
558 status = getstring(x, _(b"bisect requires a string")).lower()
559 state = set(hbisect.get(repo, status))
559 state = set(hbisect.get(repo, status))
560 return subset & state
560 return subset & state
561
561
562
562
563 # Backward-compatibility
563 # Backward-compatibility
564 # - no help entry so that we do not advertise it any more
564 # - no help entry so that we do not advertise it any more
565 @predicate(b'bisected', safe=True)
565 @predicate(b'bisected', safe=True)
566 def bisected(repo, subset, x):
566 def bisected(repo, subset, x):
567 return bisect(repo, subset, x)
567 return bisect(repo, subset, x)
568
568
569
569
570 @predicate(b'bookmark([name])', safe=True)
570 @predicate(b'bookmark([name])', safe=True)
571 def bookmark(repo, subset, x):
571 def bookmark(repo, subset, x):
572 """The named bookmark or all bookmarks.
572 """The named bookmark or all bookmarks.
573
573
574 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
574 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
575 """
575 """
576 # i18n: "bookmark" is a keyword
576 # i18n: "bookmark" is a keyword
577 args = getargs(x, 0, 1, _(b'bookmark takes one or no arguments'))
577 args = getargs(x, 0, 1, _(b'bookmark takes one or no arguments'))
578 if args:
578 if args:
579 bm = getstring(
579 bm = getstring(
580 args[0],
580 args[0],
581 # i18n: "bookmark" is a keyword
581 # i18n: "bookmark" is a keyword
582 _(b'the argument to bookmark must be a string'),
582 _(b'the argument to bookmark must be a string'),
583 )
583 )
584 kind, pattern, matcher = stringutil.stringmatcher(bm)
584 kind, pattern, matcher = stringutil.stringmatcher(bm)
585 bms = set()
585 bms = set()
586 if kind == b'literal':
586 if kind == b'literal':
587 if bm == pattern:
587 if bm == pattern:
588 pattern = repo._bookmarks.expandname(pattern)
588 pattern = repo._bookmarks.expandname(pattern)
589 bmrev = repo._bookmarks.get(pattern, None)
589 bmrev = repo._bookmarks.get(pattern, None)
590 if not bmrev:
590 if not bmrev:
591 raise error.RepoLookupError(
591 raise error.RepoLookupError(
592 _(b"bookmark '%s' does not exist") % pattern
592 _(b"bookmark '%s' does not exist") % pattern
593 )
593 )
594 bms.add(repo[bmrev].rev())
594 bms.add(repo[bmrev].rev())
595 else:
595 else:
596 matchrevs = set()
596 matchrevs = set()
597 for name, bmrev in pycompat.iteritems(repo._bookmarks):
597 for name, bmrev in pycompat.iteritems(repo._bookmarks):
598 if matcher(name):
598 if matcher(name):
599 matchrevs.add(bmrev)
599 matchrevs.add(bmrev)
600 for bmrev in matchrevs:
600 for bmrev in matchrevs:
601 bms.add(repo[bmrev].rev())
601 bms.add(repo[bmrev].rev())
602 else:
602 else:
603 bms = {repo[r].rev() for r in repo._bookmarks.values()}
603 bms = {repo[r].rev() for r in repo._bookmarks.values()}
604 bms -= {nullrev}
604 bms -= {nullrev}
605 return subset & bms
605 return subset & bms
606
606
607
607
608 @predicate(b'branch(string or set)', safe=True, weight=10)
608 @predicate(b'branch(string or set)', safe=True, weight=10)
609 def branch(repo, subset, x):
609 def branch(repo, subset, x):
610 """
610 """
611 All changesets belonging to the given branch or the branches of the given
611 All changesets belonging to the given branch or the branches of the given
612 changesets.
612 changesets.
613
613
614 Pattern matching is supported for `string`. See
614 Pattern matching is supported for `string`. See
615 :hg:`help revisions.patterns`.
615 :hg:`help revisions.patterns`.
616 """
616 """
617 getbi = repo.revbranchcache().branchinfo
617 getbi = repo.revbranchcache().branchinfo
618
618
619 def getbranch(r):
619 def getbranch(r):
620 try:
620 try:
621 return getbi(r)[0]
621 return getbi(r)[0]
622 except error.WdirUnsupported:
622 except error.WdirUnsupported:
623 return repo[r].branch()
623 return repo[r].branch()
624
624
625 try:
625 try:
626 b = getstring(x, b'')
626 b = getstring(x, b'')
627 except error.ParseError:
627 except error.ParseError:
628 # not a string, but another revspec, e.g. tip()
628 # not a string, but another revspec, e.g. tip()
629 pass
629 pass
630 else:
630 else:
631 kind, pattern, matcher = stringutil.stringmatcher(b)
631 kind, pattern, matcher = stringutil.stringmatcher(b)
632 if kind == b'literal':
632 if kind == b'literal':
633 # note: falls through to the revspec case if no branch with
633 # note: falls through to the revspec case if no branch with
634 # this name exists and pattern kind is not specified explicitly
634 # this name exists and pattern kind is not specified explicitly
635 if repo.branchmap().hasbranch(pattern):
635 if repo.branchmap().hasbranch(pattern):
636 return subset.filter(
636 return subset.filter(
637 lambda r: matcher(getbranch(r)),
637 lambda r: matcher(getbranch(r)),
638 condrepr=(b'<branch %r>', b),
638 condrepr=(b'<branch %r>', b),
639 )
639 )
640 if b.startswith(b'literal:'):
640 if b.startswith(b'literal:'):
641 raise error.RepoLookupError(
641 raise error.RepoLookupError(
642 _(b"branch '%s' does not exist") % pattern
642 _(b"branch '%s' does not exist") % pattern
643 )
643 )
644 else:
644 else:
645 return subset.filter(
645 return subset.filter(
646 lambda r: matcher(getbranch(r)), condrepr=(b'<branch %r>', b)
646 lambda r: matcher(getbranch(r)), condrepr=(b'<branch %r>', b)
647 )
647 )
648
648
649 s = getset(repo, fullreposet(repo), x)
649 s = getset(repo, fullreposet(repo), x)
650 b = set()
650 b = set()
651 for r in s:
651 for r in s:
652 b.add(getbranch(r))
652 b.add(getbranch(r))
653 c = s.__contains__
653 c = s.__contains__
654 return subset.filter(
654 return subset.filter(
655 lambda r: c(r) or getbranch(r) in b,
655 lambda r: c(r) or getbranch(r) in b,
656 condrepr=lambda: b'<branch %r>' % _sortedb(b),
656 condrepr=lambda: b'<branch %r>' % _sortedb(b),
657 )
657 )
658
658
659
659
660 @predicate(b'phasedivergent()', safe=True)
660 @predicate(b'phasedivergent()', safe=True)
661 def phasedivergent(repo, subset, x):
661 def phasedivergent(repo, subset, x):
662 """Mutable changesets marked as successors of public changesets.
662 """Mutable changesets marked as successors of public changesets.
663
663
664 Only non-public and non-obsolete changesets can be `phasedivergent`.
664 Only non-public and non-obsolete changesets can be `phasedivergent`.
665 (EXPERIMENTAL)
665 (EXPERIMENTAL)
666 """
666 """
667 # i18n: "phasedivergent" is a keyword
667 # i18n: "phasedivergent" is a keyword
668 getargs(x, 0, 0, _(b"phasedivergent takes no arguments"))
668 getargs(x, 0, 0, _(b"phasedivergent takes no arguments"))
669 phasedivergent = obsmod.getrevs(repo, b'phasedivergent')
669 phasedivergent = obsmod.getrevs(repo, b'phasedivergent')
670 return subset & phasedivergent
670 return subset & phasedivergent
671
671
672
672
673 @predicate(b'bundle()', safe=True)
673 @predicate(b'bundle()', safe=True)
674 def bundle(repo, subset, x):
674 def bundle(repo, subset, x):
675 """Changesets in the bundle.
675 """Changesets in the bundle.
676
676
677 Bundle must be specified by the -R option."""
677 Bundle must be specified by the -R option."""
678
678
679 try:
679 try:
680 bundlerevs = repo.changelog.bundlerevs
680 bundlerevs = repo.changelog.bundlerevs
681 except AttributeError:
681 except AttributeError:
682 raise error.Abort(_(b"no bundle provided - specify with -R"))
682 raise error.Abort(_(b"no bundle provided - specify with -R"))
683 return subset & bundlerevs
683 return subset & bundlerevs
684
684
685
685
686 def checkstatus(repo, subset, pat, field):
686 def checkstatus(repo, subset, pat, field):
687 """Helper for status-related revsets (adds, removes, modifies).
687 """Helper for status-related revsets (adds, removes, modifies).
688 The field parameter says which kind is desired.
688 The field parameter says which kind is desired.
689 """
689 """
690 hasset = matchmod.patkind(pat) == b'set'
690 hasset = matchmod.patkind(pat) == b'set'
691
691
692 mcache = [None]
692 mcache = [None]
693
693
694 def matches(x):
694 def matches(x):
695 c = repo[x]
695 c = repo[x]
696 if not mcache[0] or hasset:
696 if not mcache[0] or hasset:
697 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
697 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
698 m = mcache[0]
698 m = mcache[0]
699 fname = None
699 fname = None
700
700
701 assert m is not None # help pytype
701 assert m is not None # help pytype
702 if not m.anypats() and len(m.files()) == 1:
702 if not m.anypats() and len(m.files()) == 1:
703 fname = m.files()[0]
703 fname = m.files()[0]
704 if fname is not None:
704 if fname is not None:
705 if fname not in c.files():
705 if fname not in c.files():
706 return False
706 return False
707 else:
707 else:
708 if not any(m(f) for f in c.files()):
708 if not any(m(f) for f in c.files()):
709 return False
709 return False
710 files = getattr(repo.status(c.p1().node(), c.node()), field)
710 files = getattr(repo.status(c.p1().node(), c.node()), field)
711 if fname is not None:
711 if fname is not None:
712 if fname in files:
712 if fname in files:
713 return True
713 return True
714 else:
714 else:
715 if any(m(f) for f in files):
715 if any(m(f) for f in files):
716 return True
716 return True
717
717
718 return subset.filter(
718 return subset.filter(
719 matches, condrepr=(b'<status.%s %r>', pycompat.sysbytes(field), pat)
719 matches, condrepr=(b'<status.%s %r>', pycompat.sysbytes(field), pat)
720 )
720 )
721
721
722
722
723 def _children(repo, subset, parentset):
723 def _children(repo, subset, parentset):
724 if not parentset:
724 if not parentset:
725 return baseset()
725 return baseset()
726 cs = set()
726 cs = set()
727 pr = repo.changelog.parentrevs
727 pr = repo.changelog.parentrevs
728 minrev = parentset.min()
728 minrev = parentset.min()
729 for r in subset:
729 for r in subset:
730 if r <= minrev:
730 if r <= minrev:
731 continue
731 continue
732 p1, p2 = pr(r)
732 p1, p2 = pr(r)
733 if p1 in parentset:
733 if p1 in parentset:
734 cs.add(r)
734 cs.add(r)
735 if p2 != nullrev and p2 in parentset:
735 if p2 != nullrev and p2 in parentset:
736 cs.add(r)
736 cs.add(r)
737 return baseset(cs)
737 return baseset(cs)
738
738
739
739
740 @predicate(b'children(set)', safe=True)
740 @predicate(b'children(set)', safe=True)
741 def children(repo, subset, x):
741 def children(repo, subset, x):
742 """Child changesets of changesets in set."""
742 """Child changesets of changesets in set."""
743 s = getset(repo, fullreposet(repo), x)
743 s = getset(repo, fullreposet(repo), x)
744 cs = _children(repo, subset, s)
744 cs = _children(repo, subset, s)
745 return subset & cs
745 return subset & cs
746
746
747
747
748 @predicate(b'closed()', safe=True, weight=10)
748 @predicate(b'closed()', safe=True, weight=10)
749 def closed(repo, subset, x):
749 def closed(repo, subset, x):
750 """Changeset is closed."""
750 """Changeset is closed."""
751 # i18n: "closed" is a keyword
751 # i18n: "closed" is a keyword
752 getargs(x, 0, 0, _(b"closed takes no arguments"))
752 getargs(x, 0, 0, _(b"closed takes no arguments"))
753 return subset.filter(
753 return subset.filter(
754 lambda r: repo[r].closesbranch(), condrepr=b'<branch closed>'
754 lambda r: repo[r].closesbranch(), condrepr=b'<branch closed>'
755 )
755 )
756
756
757
757
758 # for internal use
758 # for internal use
759 @predicate(b'_commonancestorheads(set)', safe=True)
759 @predicate(b'_commonancestorheads(set)', safe=True)
760 def _commonancestorheads(repo, subset, x):
760 def _commonancestorheads(repo, subset, x):
761 # This is an internal method is for quickly calculating "heads(::x and
761 # This is an internal method is for quickly calculating "heads(::x and
762 # ::y)"
762 # ::y)"
763
763
764 # These greatest common ancestors are the same ones that the consensus bid
764 # These greatest common ancestors are the same ones that the consensus bid
765 # merge will find.
765 # merge will find.
766 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
766 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
767
767
768 ancs = repo.changelog._commonancestorsheads(*list(startrevs))
768 ancs = repo.changelog._commonancestorsheads(*list(startrevs))
769 return subset & baseset(ancs)
769 return subset & baseset(ancs)
770
770
771
771
772 @predicate(b'commonancestors(set)', safe=True)
772 @predicate(b'commonancestors(set)', safe=True)
773 def commonancestors(repo, subset, x):
773 def commonancestors(repo, subset, x):
774 """Changesets that are ancestors of every changeset in set."""
774 """Changesets that are ancestors of every changeset in set."""
775 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
775 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
776 if not startrevs:
776 if not startrevs:
777 return baseset()
777 return baseset()
778 for r in startrevs:
778 for r in startrevs:
779 subset &= dagop.revancestors(repo, baseset([r]))
779 subset &= dagop.revancestors(repo, baseset([r]))
780 return subset
780 return subset
781
781
782
782
783 @predicate(b'conflictlocal()', safe=True)
783 @predicate(b'conflictlocal()', safe=True)
784 def conflictlocal(repo, subset, x):
784 def conflictlocal(repo, subset, x):
785 """The local side of the merge, if currently in an unresolved merge.
785 """The local side of the merge, if currently in an unresolved merge.
786
786
787 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
787 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
788 """
788 """
789 getargs(x, 0, 0, _(b"conflictlocal takes no arguments"))
789 getargs(x, 0, 0, _(b"conflictlocal takes no arguments"))
790 from . import mergestate as mergestatemod
790 from . import mergestate as mergestatemod
791
791
792 mergestate = mergestatemod.mergestate.read(repo)
792 mergestate = mergestatemod.mergestate.read(repo)
793 if mergestate.active() and repo.changelog.hasnode(mergestate.local):
793 if mergestate.active() and repo.changelog.hasnode(mergestate.local):
794 return subset & {repo.changelog.rev(mergestate.local)}
794 return subset & {repo.changelog.rev(mergestate.local)}
795
795
796 return baseset()
796 return baseset()
797
797
798
798
799 @predicate(b'conflictother()', safe=True)
799 @predicate(b'conflictother()', safe=True)
800 def conflictother(repo, subset, x):
800 def conflictother(repo, subset, x):
801 """The other side of the merge, if currently in an unresolved merge.
801 """The other side of the merge, if currently in an unresolved merge.
802
802
803 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
803 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
804 """
804 """
805 getargs(x, 0, 0, _(b"conflictother takes no arguments"))
805 getargs(x, 0, 0, _(b"conflictother takes no arguments"))
806 from . import mergestate as mergestatemod
806 from . import mergestate as mergestatemod
807
807
808 mergestate = mergestatemod.mergestate.read(repo)
808 mergestate = mergestatemod.mergestate.read(repo)
809 if mergestate.active() and repo.changelog.hasnode(mergestate.other):
809 if mergestate.active() and repo.changelog.hasnode(mergestate.other):
810 return subset & {repo.changelog.rev(mergestate.other)}
810 return subset & {repo.changelog.rev(mergestate.other)}
811
811
812 return baseset()
812 return baseset()
813
813
814
814
815 @predicate(b'contains(pattern)', weight=100)
815 @predicate(b'contains(pattern)', weight=100)
816 def contains(repo, subset, x):
816 def contains(repo, subset, x):
817 """The revision's manifest contains a file matching pattern (but might not
817 """The revision's manifest contains a file matching pattern (but might not
818 modify it). See :hg:`help patterns` for information about file patterns.
818 modify it). See :hg:`help patterns` for information about file patterns.
819
819
820 The pattern without explicit kind like ``glob:`` is expected to be
820 The pattern without explicit kind like ``glob:`` is expected to be
821 relative to the current directory and match against a file exactly
821 relative to the current directory and match against a file exactly
822 for efficiency.
822 for efficiency.
823 """
823 """
824 # i18n: "contains" is a keyword
824 # i18n: "contains" is a keyword
825 pat = getstring(x, _(b"contains requires a pattern"))
825 pat = getstring(x, _(b"contains requires a pattern"))
826
826
827 def matches(x):
827 def matches(x):
828 if not matchmod.patkind(pat):
828 if not matchmod.patkind(pat):
829 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
829 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
830 if pats in repo[x]:
830 if pats in repo[x]:
831 return True
831 return True
832 else:
832 else:
833 c = repo[x]
833 c = repo[x]
834 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
834 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
835 for f in c.manifest():
835 for f in c.manifest():
836 if m(f):
836 if m(f):
837 return True
837 return True
838 return False
838 return False
839
839
840 return subset.filter(matches, condrepr=(b'<contains %r>', pat))
840 return subset.filter(matches, condrepr=(b'<contains %r>', pat))
841
841
842
842
843 @predicate(b'converted([id])', safe=True)
843 @predicate(b'converted([id])', safe=True)
844 def converted(repo, subset, x):
844 def converted(repo, subset, x):
845 """Changesets converted from the given identifier in the old repository if
845 """Changesets converted from the given identifier in the old repository if
846 present, or all converted changesets if no identifier is specified.
846 present, or all converted changesets if no identifier is specified.
847 """
847 """
848
848
849 # There is exactly no chance of resolving the revision, so do a simple
849 # There is exactly no chance of resolving the revision, so do a simple
850 # string compare and hope for the best
850 # string compare and hope for the best
851
851
852 rev = None
852 rev = None
853 # i18n: "converted" is a keyword
853 # i18n: "converted" is a keyword
854 l = getargs(x, 0, 1, _(b'converted takes one or no arguments'))
854 l = getargs(x, 0, 1, _(b'converted takes one or no arguments'))
855 if l:
855 if l:
856 # i18n: "converted" is a keyword
856 # i18n: "converted" is a keyword
857 rev = getstring(l[0], _(b'converted requires a revision'))
857 rev = getstring(l[0], _(b'converted requires a revision'))
858
858
859 def _matchvalue(r):
859 def _matchvalue(r):
860 source = repo[r].extra().get(b'convert_revision', None)
860 source = repo[r].extra().get(b'convert_revision', None)
861 return source is not None and (rev is None or source.startswith(rev))
861 return source is not None and (rev is None or source.startswith(rev))
862
862
863 return subset.filter(
863 return subset.filter(
864 lambda r: _matchvalue(r), condrepr=(b'<converted %r>', rev)
864 lambda r: _matchvalue(r), condrepr=(b'<converted %r>', rev)
865 )
865 )
866
866
867
867
868 @predicate(b'date(interval)', safe=True, weight=10)
868 @predicate(b'date(interval)', safe=True, weight=10)
869 def date(repo, subset, x):
869 def date(repo, subset, x):
870 """Changesets within the interval, see :hg:`help dates`."""
870 """Changesets within the interval, see :hg:`help dates`."""
871 # i18n: "date" is a keyword
871 # i18n: "date" is a keyword
872 ds = getstring(x, _(b"date requires a string"))
872 ds = getstring(x, _(b"date requires a string"))
873 dm = dateutil.matchdate(ds)
873 dm = dateutil.matchdate(ds)
874 return subset.filter(
874 return subset.filter(
875 lambda x: dm(repo[x].date()[0]), condrepr=(b'<date %r>', ds)
875 lambda x: dm(repo[x].date()[0]), condrepr=(b'<date %r>', ds)
876 )
876 )
877
877
878
878
879 @predicate(b'desc(string)', safe=True, weight=10)
879 @predicate(b'desc(string)', safe=True, weight=10)
880 def desc(repo, subset, x):
880 def desc(repo, subset, x):
881 """Search commit message for string. The match is case-insensitive.
881 """Search commit message for string. The match is case-insensitive.
882
882
883 Pattern matching is supported for `string`. See
883 Pattern matching is supported for `string`. See
884 :hg:`help revisions.patterns`.
884 :hg:`help revisions.patterns`.
885 """
885 """
886 # i18n: "desc" is a keyword
886 # i18n: "desc" is a keyword
887 ds = getstring(x, _(b"desc requires a string"))
887 ds = getstring(x, _(b"desc requires a string"))
888
888
889 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
889 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
890
890
891 return subset.filter(
891 return subset.filter(
892 lambda r: matcher(repo[r].description()), condrepr=(b'<desc %r>', ds)
892 lambda r: matcher(repo[r].description()), condrepr=(b'<desc %r>', ds)
893 )
893 )
894
894
895
895
896 def _descendants(
896 def _descendants(
897 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
897 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
898 ):
898 ):
899 roots = getset(repo, fullreposet(repo), x)
899 roots = getset(repo, fullreposet(repo), x)
900 if not roots:
900 if not roots:
901 return baseset()
901 return baseset()
902 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
902 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
903 return subset & s
903 return subset & s
904
904
905
905
906 @predicate(b'descendants(set[, depth])', safe=True)
906 @predicate(b'descendants(set[, depth])', safe=True)
907 def descendants(repo, subset, x):
907 def descendants(repo, subset, x):
908 """Changesets which are descendants of changesets in set, including the
908 """Changesets which are descendants of changesets in set, including the
909 given changesets themselves.
909 given changesets themselves.
910
910
911 If depth is specified, the result only includes changesets up to
911 If depth is specified, the result only includes changesets up to
912 the specified generation.
912 the specified generation.
913 """
913 """
914 # startdepth is for internal use only until we can decide the UI
914 # startdepth is for internal use only until we can decide the UI
915 args = getargsdict(x, b'descendants', b'set depth startdepth')
915 args = getargsdict(x, b'descendants', b'set depth startdepth')
916 if b'set' not in args:
916 if b'set' not in args:
917 # i18n: "descendants" is a keyword
917 # i18n: "descendants" is a keyword
918 raise error.ParseError(_(b'descendants takes at least 1 argument'))
918 raise error.ParseError(_(b'descendants takes at least 1 argument'))
919 startdepth = stopdepth = None
919 startdepth = stopdepth = None
920 if b'startdepth' in args:
920 if b'startdepth' in args:
921 n = getinteger(
921 n = getinteger(
922 args[b'startdepth'], b"descendants expects an integer startdepth"
922 args[b'startdepth'], b"descendants expects an integer startdepth"
923 )
923 )
924 if n < 0:
924 if n < 0:
925 raise error.ParseError(b"negative startdepth")
925 raise error.ParseError(b"negative startdepth")
926 startdepth = n
926 startdepth = n
927 if b'depth' in args:
927 if b'depth' in args:
928 # i18n: "descendants" is a keyword
928 # i18n: "descendants" is a keyword
929 n = getinteger(
929 n = getinteger(
930 args[b'depth'], _(b"descendants expects an integer depth")
930 args[b'depth'], _(b"descendants expects an integer depth")
931 )
931 )
932 if n < 0:
932 if n < 0:
933 raise error.ParseError(_(b"negative depth"))
933 raise error.ParseError(_(b"negative depth"))
934 stopdepth = n + 1
934 stopdepth = n + 1
935 return _descendants(
935 return _descendants(
936 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
936 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
937 )
937 )
938
938
939
939
940 @predicate(b'_firstdescendants', safe=True)
940 @predicate(b'_firstdescendants', safe=True)
941 def _firstdescendants(repo, subset, x):
941 def _firstdescendants(repo, subset, x):
942 # ``_firstdescendants(set)``
942 # ``_firstdescendants(set)``
943 # Like ``descendants(set)`` but follows only the first parents.
943 # Like ``descendants(set)`` but follows only the first parents.
944 return _descendants(repo, subset, x, followfirst=True)
944 return _descendants(repo, subset, x, followfirst=True)
945
945
946
946
947 @predicate(b'destination([set])', safe=True, weight=10)
947 @predicate(b'destination([set])', safe=True, weight=10)
948 def destination(repo, subset, x):
948 def destination(repo, subset, x):
949 """Changesets that were created by a graft, transplant or rebase operation,
949 """Changesets that were created by a graft, transplant or rebase operation,
950 with the given revisions specified as the source. Omitting the optional set
950 with the given revisions specified as the source. Omitting the optional set
951 is the same as passing all().
951 is the same as passing all().
952 """
952 """
953 if x is not None:
953 if x is not None:
954 sources = getset(repo, fullreposet(repo), x)
954 sources = getset(repo, fullreposet(repo), x)
955 else:
955 else:
956 sources = fullreposet(repo)
956 sources = fullreposet(repo)
957
957
958 dests = set()
958 dests = set()
959
959
960 # subset contains all of the possible destinations that can be returned, so
960 # subset contains all of the possible destinations that can be returned, so
961 # iterate over them and see if their source(s) were provided in the arg set.
961 # iterate over them and see if their source(s) were provided in the arg set.
962 # Even if the immediate src of r is not in the arg set, src's source (or
962 # Even if the immediate src of r is not in the arg set, src's source (or
963 # further back) may be. Scanning back further than the immediate src allows
963 # further back) may be. Scanning back further than the immediate src allows
964 # transitive transplants and rebases to yield the same results as transitive
964 # transitive transplants and rebases to yield the same results as transitive
965 # grafts.
965 # grafts.
966 for r in subset:
966 for r in subset:
967 src = _getrevsource(repo, r)
967 src = _getrevsource(repo, r)
968 lineage = None
968 lineage = None
969
969
970 while src is not None:
970 while src is not None:
971 if lineage is None:
971 if lineage is None:
972 lineage = list()
972 lineage = list()
973
973
974 lineage.append(r)
974 lineage.append(r)
975
975
976 # The visited lineage is a match if the current source is in the arg
976 # The visited lineage is a match if the current source is in the arg
977 # set. Since every candidate dest is visited by way of iterating
977 # set. Since every candidate dest is visited by way of iterating
978 # subset, any dests further back in the lineage will be tested by a
978 # subset, any dests further back in the lineage will be tested by a
979 # different iteration over subset. Likewise, if the src was already
979 # different iteration over subset. Likewise, if the src was already
980 # selected, the current lineage can be selected without going back
980 # selected, the current lineage can be selected without going back
981 # further.
981 # further.
982 if src in sources or src in dests:
982 if src in sources or src in dests:
983 dests.update(lineage)
983 dests.update(lineage)
984 break
984 break
985
985
986 r = src
986 r = src
987 src = _getrevsource(repo, r)
987 src = _getrevsource(repo, r)
988
988
989 return subset.filter(
989 return subset.filter(
990 dests.__contains__,
990 dests.__contains__,
991 condrepr=lambda: b'<destination %r>' % _sortedb(dests),
991 condrepr=lambda: b'<destination %r>' % _sortedb(dests),
992 )
992 )
993
993
994
994
995 @predicate(b'diffcontains(pattern)', weight=110)
995 @predicate(b'diffcontains(pattern)', weight=110)
996 def diffcontains(repo, subset, x):
996 def diffcontains(repo, subset, x):
997 """Search revision differences for when the pattern was added or removed.
997 """Search revision differences for when the pattern was added or removed.
998
998
999 The pattern may be a substring literal or a regular expression. See
999 The pattern may be a substring literal or a regular expression. See
1000 :hg:`help revisions.patterns`.
1000 :hg:`help revisions.patterns`.
1001 """
1001 """
1002 args = getargsdict(x, b'diffcontains', b'pattern')
1002 args = getargsdict(x, b'diffcontains', b'pattern')
1003 if b'pattern' not in args:
1003 if b'pattern' not in args:
1004 # i18n: "diffcontains" is a keyword
1004 # i18n: "diffcontains" is a keyword
1005 raise error.ParseError(_(b'diffcontains takes at least 1 argument'))
1005 raise error.ParseError(_(b'diffcontains takes at least 1 argument'))
1006
1006
1007 pattern = getstring(
1007 pattern = getstring(
1008 args[b'pattern'], _(b'diffcontains requires a string pattern')
1008 args[b'pattern'], _(b'diffcontains requires a string pattern')
1009 )
1009 )
1010 regexp = stringutil.substringregexp(pattern, re.M)
1010 regexp = stringutil.substringregexp(pattern, re.M)
1011
1011
1012 # TODO: add support for file pattern and --follow. For example,
1012 # TODO: add support for file pattern and --follow. For example,
1013 # diffcontains(pattern[, set]) where set may be file(pattern) or
1013 # diffcontains(pattern[, set]) where set may be file(pattern) or
1014 # follow(pattern), and we'll eventually add a support for narrowing
1014 # follow(pattern), and we'll eventually add a support for narrowing
1015 # files by revset?
1015 # files by revset?
1016 fmatch = matchmod.always()
1016 fmatch = matchmod.always()
1017
1017
1018 def makefilematcher(ctx):
1018 def makefilematcher(ctx):
1019 return fmatch
1019 return fmatch
1020
1020
1021 # TODO: search in a windowed way
1021 # TODO: search in a windowed way
1022 searcher = grepmod.grepsearcher(repo.ui, repo, regexp, diff=True)
1022 searcher = grepmod.grepsearcher(repo.ui, repo, regexp, diff=True)
1023
1023
1024 def testdiff(rev):
1024 def testdiff(rev):
1025 # consume the generator to discard revfiles/matches cache
1025 # consume the generator to discard revfiles/matches cache
1026 found = False
1026 found = False
1027 for fn, ctx, pstates, states in searcher.searchfiles(
1027 for fn, ctx, pstates, states in searcher.searchfiles(
1028 baseset([rev]), makefilematcher
1028 baseset([rev]), makefilematcher
1029 ):
1029 ):
1030 if next(grepmod.difflinestates(pstates, states), None):
1030 if next(grepmod.difflinestates(pstates, states), None):
1031 found = True
1031 found = True
1032 return found
1032 return found
1033
1033
1034 return subset.filter(testdiff, condrepr=(b'<diffcontains %r>', pattern))
1034 return subset.filter(testdiff, condrepr=(b'<diffcontains %r>', pattern))
1035
1035
1036
1036
1037 @predicate(b'contentdivergent()', safe=True)
1037 @predicate(b'contentdivergent()', safe=True)
1038 def contentdivergent(repo, subset, x):
1038 def contentdivergent(repo, subset, x):
1039 """
1039 """
1040 Final successors of changesets with an alternative set of final
1040 Final successors of changesets with an alternative set of final
1041 successors. (EXPERIMENTAL)
1041 successors. (EXPERIMENTAL)
1042 """
1042 """
1043 # i18n: "contentdivergent" is a keyword
1043 # i18n: "contentdivergent" is a keyword
1044 getargs(x, 0, 0, _(b"contentdivergent takes no arguments"))
1044 getargs(x, 0, 0, _(b"contentdivergent takes no arguments"))
1045 contentdivergent = obsmod.getrevs(repo, b'contentdivergent')
1045 contentdivergent = obsmod.getrevs(repo, b'contentdivergent')
1046 return subset & contentdivergent
1046 return subset & contentdivergent
1047
1047
1048
1048
1049 @predicate(b'expectsize(set[, size])', safe=True, takeorder=True)
1049 @predicate(b'expectsize(set[, size])', safe=True, takeorder=True)
1050 def expectsize(repo, subset, x, order):
1050 def expectsize(repo, subset, x, order):
1051 """Return the given revset if size matches the revset size.
1051 """Return the given revset if size matches the revset size.
1052 Abort if the revset doesn't expect given size.
1052 Abort if the revset doesn't expect given size.
1053 size can either be an integer range or an integer.
1053 size can either be an integer range or an integer.
1054
1054
1055 For example, ``expectsize(0:1, 3:5)`` will abort as revset size is 2 and
1055 For example, ``expectsize(0:1, 3:5)`` will abort as revset size is 2 and
1056 2 is not between 3 and 5 inclusive."""
1056 2 is not between 3 and 5 inclusive."""
1057
1057
1058 args = getargsdict(x, b'expectsize', b'set size')
1058 args = getargsdict(x, b'expectsize', b'set size')
1059 minsize = 0
1059 minsize = 0
1060 maxsize = len(repo) + 1
1060 maxsize = len(repo) + 1
1061 err = b''
1061 err = b''
1062 if b'size' not in args or b'set' not in args:
1062 if b'size' not in args or b'set' not in args:
1063 raise error.ParseError(_(b'invalid set of arguments'))
1063 raise error.ParseError(_(b'invalid set of arguments'))
1064 minsize, maxsize = getintrange(
1064 minsize, maxsize = getintrange(
1065 args[b'size'],
1065 args[b'size'],
1066 _(b'expectsize requires a size range or a positive integer'),
1066 _(b'expectsize requires a size range or a positive integer'),
1067 _(b'size range bounds must be integers'),
1067 _(b'size range bounds must be integers'),
1068 minsize,
1068 minsize,
1069 maxsize,
1069 maxsize,
1070 )
1070 )
1071 if minsize < 0 or maxsize < 0:
1071 if minsize < 0 or maxsize < 0:
1072 raise error.ParseError(_(b'negative size'))
1072 raise error.ParseError(_(b'negative size'))
1073 rev = getset(repo, fullreposet(repo), args[b'set'], order=order)
1073 rev = getset(repo, fullreposet(repo), args[b'set'], order=order)
1074 if minsize != maxsize and (len(rev) < minsize or len(rev) > maxsize):
1074 if minsize != maxsize and (len(rev) < minsize or len(rev) > maxsize):
1075 err = _(b'revset size mismatch. expected between %d and %d, got %d') % (
1075 err = _(b'revset size mismatch. expected between %d and %d, got %d') % (
1076 minsize,
1076 minsize,
1077 maxsize,
1077 maxsize,
1078 len(rev),
1078 len(rev),
1079 )
1079 )
1080 elif minsize == maxsize and len(rev) != minsize:
1080 elif minsize == maxsize and len(rev) != minsize:
1081 err = _(b'revset size mismatch. expected %d, got %d') % (
1081 err = _(b'revset size mismatch. expected %d, got %d') % (
1082 minsize,
1082 minsize,
1083 len(rev),
1083 len(rev),
1084 )
1084 )
1085 if err:
1085 if err:
1086 raise error.RepoLookupError(err)
1086 raise error.RepoLookupError(err)
1087 if order == followorder:
1087 if order == followorder:
1088 return subset & rev
1088 return subset & rev
1089 else:
1089 else:
1090 return rev & subset
1090 return rev & subset
1091
1091
1092
1092
1093 @predicate(b'extdata(source)', safe=False, weight=100)
1093 @predicate(b'extdata(source)', safe=False, weight=100)
1094 def extdata(repo, subset, x):
1094 def extdata(repo, subset, x):
1095 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
1095 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
1096 # i18n: "extdata" is a keyword
1096 # i18n: "extdata" is a keyword
1097 args = getargsdict(x, b'extdata', b'source')
1097 args = getargsdict(x, b'extdata', b'source')
1098 source = getstring(
1098 source = getstring(
1099 args.get(b'source'),
1099 args.get(b'source'),
1100 # i18n: "extdata" is a keyword
1100 # i18n: "extdata" is a keyword
1101 _(b'extdata takes at least 1 string argument'),
1101 _(b'extdata takes at least 1 string argument'),
1102 )
1102 )
1103 data = scmutil.extdatasource(repo, source)
1103 data = scmutil.extdatasource(repo, source)
1104 return subset & baseset(data)
1104 return subset & baseset(data)
1105
1105
1106
1106
1107 @predicate(b'extinct()', safe=True)
1107 @predicate(b'extinct()', safe=True)
1108 def extinct(repo, subset, x):
1108 def extinct(repo, subset, x):
1109 """Obsolete changesets with obsolete descendants only. (EXPERIMENTAL)"""
1109 """Obsolete changesets with obsolete descendants only. (EXPERIMENTAL)"""
1110 # i18n: "extinct" is a keyword
1110 # i18n: "extinct" is a keyword
1111 getargs(x, 0, 0, _(b"extinct takes no arguments"))
1111 getargs(x, 0, 0, _(b"extinct takes no arguments"))
1112 extincts = obsmod.getrevs(repo, b'extinct')
1112 extincts = obsmod.getrevs(repo, b'extinct')
1113 return subset & extincts
1113 return subset & extincts
1114
1114
1115
1115
1116 @predicate(b'extra(label, [value])', safe=True)
1116 @predicate(b'extra(label, [value])', safe=True)
1117 def extra(repo, subset, x):
1117 def extra(repo, subset, x):
1118 """Changesets with the given label in the extra metadata, with the given
1118 """Changesets with the given label in the extra metadata, with the given
1119 optional value.
1119 optional value.
1120
1120
1121 Pattern matching is supported for `value`. See
1121 Pattern matching is supported for `value`. See
1122 :hg:`help revisions.patterns`.
1122 :hg:`help revisions.patterns`.
1123 """
1123 """
1124 args = getargsdict(x, b'extra', b'label value')
1124 args = getargsdict(x, b'extra', b'label value')
1125 if b'label' not in args:
1125 if b'label' not in args:
1126 # i18n: "extra" is a keyword
1126 # i18n: "extra" is a keyword
1127 raise error.ParseError(_(b'extra takes at least 1 argument'))
1127 raise error.ParseError(_(b'extra takes at least 1 argument'))
1128 # i18n: "extra" is a keyword
1128 # i18n: "extra" is a keyword
1129 label = getstring(
1129 label = getstring(
1130 args[b'label'], _(b'first argument to extra must be a string')
1130 args[b'label'], _(b'first argument to extra must be a string')
1131 )
1131 )
1132 value = None
1132 value = None
1133
1133
1134 if b'value' in args:
1134 if b'value' in args:
1135 # i18n: "extra" is a keyword
1135 # i18n: "extra" is a keyword
1136 value = getstring(
1136 value = getstring(
1137 args[b'value'], _(b'second argument to extra must be a string')
1137 args[b'value'], _(b'second argument to extra must be a string')
1138 )
1138 )
1139 kind, value, matcher = stringutil.stringmatcher(value)
1139 kind, value, matcher = stringutil.stringmatcher(value)
1140
1140
1141 def _matchvalue(r):
1141 def _matchvalue(r):
1142 extra = repo[r].extra()
1142 extra = repo[r].extra()
1143 return label in extra and (value is None or matcher(extra[label]))
1143 return label in extra and (value is None or matcher(extra[label]))
1144
1144
1145 return subset.filter(
1145 return subset.filter(
1146 lambda r: _matchvalue(r), condrepr=(b'<extra[%r] %r>', label, value)
1146 lambda r: _matchvalue(r), condrepr=(b'<extra[%r] %r>', label, value)
1147 )
1147 )
1148
1148
1149
1149
1150 @predicate(b'filelog(pattern)', safe=True)
1150 @predicate(b'filelog(pattern)', safe=True)
1151 def filelog(repo, subset, x):
1151 def filelog(repo, subset, x):
1152 """Changesets connected to the specified filelog.
1152 """Changesets connected to the specified filelog.
1153
1153
1154 For performance reasons, visits only revisions mentioned in the file-level
1154 For performance reasons, visits only revisions mentioned in the file-level
1155 filelog, rather than filtering through all changesets (much faster, but
1155 filelog, rather than filtering through all changesets (much faster, but
1156 doesn't include deletes or duplicate changes). For a slower, more accurate
1156 doesn't include deletes or duplicate changes). For a slower, more accurate
1157 result, use ``file()``.
1157 result, use ``file()``.
1158
1158
1159 The pattern without explicit kind like ``glob:`` is expected to be
1159 The pattern without explicit kind like ``glob:`` is expected to be
1160 relative to the current directory and match against a file exactly
1160 relative to the current directory and match against a file exactly
1161 for efficiency.
1161 for efficiency.
1162 """
1162 """
1163
1163
1164 # i18n: "filelog" is a keyword
1164 # i18n: "filelog" is a keyword
1165 pat = getstring(x, _(b"filelog requires a pattern"))
1165 pat = getstring(x, _(b"filelog requires a pattern"))
1166 s = set()
1166 s = set()
1167 cl = repo.changelog
1167 cl = repo.changelog
1168
1168
1169 if not matchmod.patkind(pat):
1169 if not matchmod.patkind(pat):
1170 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1170 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1171 files = [f]
1171 files = [f]
1172 else:
1172 else:
1173 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1173 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1174 files = (f for f in repo[None] if m(f))
1174 files = (f for f in repo[None] if m(f))
1175
1175
1176 for f in files:
1176 for f in files:
1177 fl = repo.file(f)
1177 fl = repo.file(f)
1178 known = {}
1178 known = {}
1179 scanpos = 0
1179 scanpos = 0
1180 for fr in list(fl):
1180 for fr in list(fl):
1181 fn = fl.node(fr)
1181 fn = fl.node(fr)
1182 if fn in known:
1182 if fn in known:
1183 s.add(known[fn])
1183 s.add(known[fn])
1184 continue
1184 continue
1185
1185
1186 lr = fl.linkrev(fr)
1186 lr = fl.linkrev(fr)
1187 if lr in cl:
1187 if lr in cl:
1188 s.add(lr)
1188 s.add(lr)
1189 elif scanpos is not None:
1189 elif scanpos is not None:
1190 # lowest matching changeset is filtered, scan further
1190 # lowest matching changeset is filtered, scan further
1191 # ahead in changelog
1191 # ahead in changelog
1192 start = max(lr, scanpos) + 1
1192 start = max(lr, scanpos) + 1
1193 scanpos = None
1193 scanpos = None
1194 for r in cl.revs(start):
1194 for r in cl.revs(start):
1195 # minimize parsing of non-matching entries
1195 # minimize parsing of non-matching entries
1196 if f in cl.revision(r) and f in cl.readfiles(r):
1196 if f in cl.revision(r) and f in cl.readfiles(r):
1197 try:
1197 try:
1198 # try to use manifest delta fastpath
1198 # try to use manifest delta fastpath
1199 n = repo[r].filenode(f)
1199 n = repo[r].filenode(f)
1200 if n not in known:
1200 if n not in known:
1201 if n == fn:
1201 if n == fn:
1202 s.add(r)
1202 s.add(r)
1203 scanpos = r
1203 scanpos = r
1204 break
1204 break
1205 else:
1205 else:
1206 known[n] = r
1206 known[n] = r
1207 except error.ManifestLookupError:
1207 except error.ManifestLookupError:
1208 # deletion in changelog
1208 # deletion in changelog
1209 continue
1209 continue
1210
1210
1211 return subset & s
1211 return subset & s
1212
1212
1213
1213
1214 @predicate(b'first(set, [n])', safe=True, takeorder=True, weight=0)
1214 @predicate(b'first(set, [n])', safe=True, takeorder=True, weight=0)
1215 def first(repo, subset, x, order):
1215 def first(repo, subset, x, order):
1216 """An alias for limit()."""
1216 """An alias for limit()."""
1217 return limit(repo, subset, x, order)
1217 return limit(repo, subset, x, order)
1218
1218
1219
1219
1220 def _follow(repo, subset, x, name, followfirst=False):
1220 def _follow(repo, subset, x, name, followfirst=False):
1221 args = getargsdict(x, name, b'file startrev')
1221 args = getargsdict(x, name, b'file startrev')
1222 revs = None
1222 revs = None
1223 if b'startrev' in args:
1223 if b'startrev' in args:
1224 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1224 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1225 if b'file' in args:
1225 if b'file' in args:
1226 x = getstring(args[b'file'], _(b"%s expected a pattern") % name)
1226 x = getstring(args[b'file'], _(b"%s expected a pattern") % name)
1227 if revs is None:
1227 if revs is None:
1228 revs = [None]
1228 revs = [None]
1229 fctxs = []
1229 fctxs = []
1230 for r in revs:
1230 for r in revs:
1231 ctx = mctx = repo[r]
1231 ctx = mctx = repo[r]
1232 if r is None:
1232 if r is None:
1233 ctx = repo[b'.']
1233 ctx = repo[b'.']
1234 m = matchmod.match(
1234 m = matchmod.match(
1235 repo.root, repo.getcwd(), [x], ctx=mctx, default=b'path'
1235 repo.root, repo.getcwd(), [x], ctx=mctx, default=b'path'
1236 )
1236 )
1237 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
1237 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
1238 s = dagop.filerevancestors(fctxs, followfirst)
1238 s = dagop.filerevancestors(fctxs, followfirst)
1239 else:
1239 else:
1240 if revs is None:
1240 if revs is None:
1241 revs = baseset([repo[b'.'].rev()])
1241 revs = baseset([repo[b'.'].rev()])
1242 s = dagop.revancestors(repo, revs, followfirst)
1242 s = dagop.revancestors(repo, revs, followfirst)
1243
1243
1244 return subset & s
1244 return subset & s
1245
1245
1246
1246
1247 @predicate(b'follow([file[, startrev]])', safe=True)
1247 @predicate(b'follow([file[, startrev]])', safe=True)
1248 def follow(repo, subset, x):
1248 def follow(repo, subset, x):
1249 """
1249 """
1250 An alias for ``::.`` (ancestors of the working directory's first parent).
1250 An alias for ``::.`` (ancestors of the working directory's first parent).
1251 If file pattern is specified, the histories of files matching given
1251 If file pattern is specified, the histories of files matching given
1252 pattern in the revision given by startrev are followed, including copies.
1252 pattern in the revision given by startrev are followed, including copies.
1253 """
1253 """
1254 return _follow(repo, subset, x, b'follow')
1254 return _follow(repo, subset, x, b'follow')
1255
1255
1256
1256
1257 @predicate(b'_followfirst', safe=True)
1257 @predicate(b'_followfirst', safe=True)
1258 def _followfirst(repo, subset, x):
1258 def _followfirst(repo, subset, x):
1259 # ``followfirst([file[, startrev]])``
1259 # ``followfirst([file[, startrev]])``
1260 # Like ``follow([file[, startrev]])`` but follows only the first parent
1260 # Like ``follow([file[, startrev]])`` but follows only the first parent
1261 # of every revisions or files revisions.
1261 # of every revisions or files revisions.
1262 return _follow(repo, subset, x, b'_followfirst', followfirst=True)
1262 return _follow(repo, subset, x, b'_followfirst', followfirst=True)
1263
1263
1264
1264
1265 @predicate(
1265 @predicate(
1266 b'followlines(file, fromline:toline[, startrev=., descend=False])',
1266 b'followlines(file, fromline:toline[, startrev=., descend=False])',
1267 safe=True,
1267 safe=True,
1268 )
1268 )
1269 def followlines(repo, subset, x):
1269 def followlines(repo, subset, x):
1270 """Changesets modifying `file` in line range ('fromline', 'toline').
1270 """Changesets modifying `file` in line range ('fromline', 'toline').
1271
1271
1272 Line range corresponds to 'file' content at 'startrev' and should hence be
1272 Line range corresponds to 'file' content at 'startrev' and should hence be
1273 consistent with file size. If startrev is not specified, working directory's
1273 consistent with file size. If startrev is not specified, working directory's
1274 parent is used.
1274 parent is used.
1275
1275
1276 By default, ancestors of 'startrev' are returned. If 'descend' is True,
1276 By default, ancestors of 'startrev' are returned. If 'descend' is True,
1277 descendants of 'startrev' are returned though renames are (currently) not
1277 descendants of 'startrev' are returned though renames are (currently) not
1278 followed in this direction.
1278 followed in this direction.
1279 """
1279 """
1280 args = getargsdict(x, b'followlines', b'file *lines startrev descend')
1280 args = getargsdict(x, b'followlines', b'file *lines startrev descend')
1281 if len(args[b'lines']) != 1:
1281 if len(args[b'lines']) != 1:
1282 raise error.ParseError(_(b"followlines requires a line range"))
1282 raise error.ParseError(_(b"followlines requires a line range"))
1283
1283
1284 rev = b'.'
1284 rev = b'.'
1285 if b'startrev' in args:
1285 if b'startrev' in args:
1286 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1286 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1287 if len(revs) != 1:
1287 if len(revs) != 1:
1288 raise error.ParseError(
1288 raise error.ParseError(
1289 # i18n: "followlines" is a keyword
1289 # i18n: "followlines" is a keyword
1290 _(b"followlines expects exactly one revision")
1290 _(b"followlines expects exactly one revision")
1291 )
1291 )
1292 rev = revs.last()
1292 rev = revs.last()
1293
1293
1294 pat = getstring(args[b'file'], _(b"followlines requires a pattern"))
1294 pat = getstring(args[b'file'], _(b"followlines requires a pattern"))
1295 # i18n: "followlines" is a keyword
1295 # i18n: "followlines" is a keyword
1296 msg = _(b"followlines expects exactly one file")
1296 msg = _(b"followlines expects exactly one file")
1297 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
1297 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
1298 fromline, toline = util.processlinerange(
1298 fromline, toline = util.processlinerange(
1299 *getintrange(
1299 *getintrange(
1300 args[b'lines'][0],
1300 args[b'lines'][0],
1301 # i18n: "followlines" is a keyword
1301 # i18n: "followlines" is a keyword
1302 _(b"followlines expects a line number or a range"),
1302 _(b"followlines expects a line number or a range"),
1303 _(b"line range bounds must be integers"),
1303 _(b"line range bounds must be integers"),
1304 )
1304 )
1305 )
1305 )
1306
1306
1307 fctx = repo[rev].filectx(fname)
1307 fctx = repo[rev].filectx(fname)
1308 descend = False
1308 descend = False
1309 if b'descend' in args:
1309 if b'descend' in args:
1310 descend = getboolean(
1310 descend = getboolean(
1311 args[b'descend'],
1311 args[b'descend'],
1312 # i18n: "descend" is a keyword
1312 # i18n: "descend" is a keyword
1313 _(b"descend argument must be a boolean"),
1313 _(b"descend argument must be a boolean"),
1314 )
1314 )
1315 if descend:
1315 if descend:
1316 rs = generatorset(
1316 rs = generatorset(
1317 (
1317 (
1318 c.rev()
1318 c.rev()
1319 for c, _linerange in dagop.blockdescendants(
1319 for c, _linerange in dagop.blockdescendants(
1320 fctx, fromline, toline
1320 fctx, fromline, toline
1321 )
1321 )
1322 ),
1322 ),
1323 iterasc=True,
1323 iterasc=True,
1324 )
1324 )
1325 else:
1325 else:
1326 rs = generatorset(
1326 rs = generatorset(
1327 (
1327 (
1328 c.rev()
1328 c.rev()
1329 for c, _linerange in dagop.blockancestors(
1329 for c, _linerange in dagop.blockancestors(
1330 fctx, fromline, toline
1330 fctx, fromline, toline
1331 )
1331 )
1332 ),
1332 ),
1333 iterasc=False,
1333 iterasc=False,
1334 )
1334 )
1335 return subset & rs
1335 return subset & rs
1336
1336
1337
1337
1338 @predicate(b'all()', safe=True)
1338 @predicate(b'all()', safe=True)
1339 def getall(repo, subset, x):
1339 def getall(repo, subset, x):
1340 """All changesets, the same as ``0:tip``."""
1340 """All changesets, the same as ``0:tip``."""
1341 # i18n: "all" is a keyword
1341 # i18n: "all" is a keyword
1342 getargs(x, 0, 0, _(b"all takes no arguments"))
1342 getargs(x, 0, 0, _(b"all takes no arguments"))
1343 return subset & spanset(repo) # drop "null" if any
1343 return subset & spanset(repo) # drop "null" if any
1344
1344
1345
1345
1346 @predicate(b'grep(regex)', weight=10)
1346 @predicate(b'grep(regex)', weight=10)
1347 def grep(repo, subset, x):
1347 def grep(repo, subset, x):
1348 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1348 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1349 to ensure special escape characters are handled correctly. Unlike
1349 to ensure special escape characters are handled correctly. Unlike
1350 ``keyword(string)``, the match is case-sensitive.
1350 ``keyword(string)``, the match is case-sensitive.
1351 """
1351 """
1352 try:
1352 try:
1353 # i18n: "grep" is a keyword
1353 # i18n: "grep" is a keyword
1354 gr = re.compile(getstring(x, _(b"grep requires a string")))
1354 gr = re.compile(getstring(x, _(b"grep requires a string")))
1355 except re.error as e:
1355 except re.error as e:
1356 raise error.ParseError(
1356 raise error.ParseError(
1357 _(b'invalid match pattern: %s') % stringutil.forcebytestr(e)
1357 _(b'invalid match pattern: %s') % stringutil.forcebytestr(e)
1358 )
1358 )
1359
1359
1360 def matches(x):
1360 def matches(x):
1361 c = repo[x]
1361 c = repo[x]
1362 for e in c.files() + [c.user(), c.description()]:
1362 for e in c.files() + [c.user(), c.description()]:
1363 if gr.search(e):
1363 if gr.search(e):
1364 return True
1364 return True
1365 return False
1365 return False
1366
1366
1367 return subset.filter(matches, condrepr=(b'<grep %r>', gr.pattern))
1367 return subset.filter(matches, condrepr=(b'<grep %r>', gr.pattern))
1368
1368
1369
1369
1370 @predicate(b'_matchfiles', safe=True)
1370 @predicate(b'_matchfiles', safe=True)
1371 def _matchfiles(repo, subset, x):
1371 def _matchfiles(repo, subset, x):
1372 # _matchfiles takes a revset list of prefixed arguments:
1372 # _matchfiles takes a revset list of prefixed arguments:
1373 #
1373 #
1374 # [p:foo, i:bar, x:baz]
1374 # [p:foo, i:bar, x:baz]
1375 #
1375 #
1376 # builds a match object from them and filters subset. Allowed
1376 # builds a match object from them and filters subset. Allowed
1377 # prefixes are 'p:' for regular patterns, 'i:' for include
1377 # prefixes are 'p:' for regular patterns, 'i:' for include
1378 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1378 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1379 # a revision identifier, or the empty string to reference the
1379 # a revision identifier, or the empty string to reference the
1380 # working directory, from which the match object is
1380 # working directory, from which the match object is
1381 # initialized. Use 'd:' to set the default matching mode, default
1381 # initialized. Use 'd:' to set the default matching mode, default
1382 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1382 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1383
1383
1384 l = getargs(x, 1, -1, b"_matchfiles requires at least one argument")
1384 l = getargs(x, 1, -1, b"_matchfiles requires at least one argument")
1385 pats, inc, exc = [], [], []
1385 pats, inc, exc = [], [], []
1386 rev, default = None, None
1386 rev, default = None, None
1387 for arg in l:
1387 for arg in l:
1388 s = getstring(arg, b"_matchfiles requires string arguments")
1388 s = getstring(arg, b"_matchfiles requires string arguments")
1389 prefix, value = s[:2], s[2:]
1389 prefix, value = s[:2], s[2:]
1390 if prefix == b'p:':
1390 if prefix == b'p:':
1391 pats.append(value)
1391 pats.append(value)
1392 elif prefix == b'i:':
1392 elif prefix == b'i:':
1393 inc.append(value)
1393 inc.append(value)
1394 elif prefix == b'x:':
1394 elif prefix == b'x:':
1395 exc.append(value)
1395 exc.append(value)
1396 elif prefix == b'r:':
1396 elif prefix == b'r:':
1397 if rev is not None:
1397 if rev is not None:
1398 raise error.ParseError(
1398 raise error.ParseError(
1399 b'_matchfiles expected at most one revision'
1399 b'_matchfiles expected at most one revision'
1400 )
1400 )
1401 if value == b'': # empty means working directory
1401 if value == b'': # empty means working directory
1402 rev = wdirrev
1402 rev = wdirrev
1403 else:
1403 else:
1404 rev = value
1404 rev = value
1405 elif prefix == b'd:':
1405 elif prefix == b'd:':
1406 if default is not None:
1406 if default is not None:
1407 raise error.ParseError(
1407 raise error.ParseError(
1408 b'_matchfiles expected at most one default mode'
1408 b'_matchfiles expected at most one default mode'
1409 )
1409 )
1410 default = value
1410 default = value
1411 else:
1411 else:
1412 raise error.ParseError(b'invalid _matchfiles prefix: %s' % prefix)
1412 raise error.ParseError(b'invalid _matchfiles prefix: %s' % prefix)
1413 if not default:
1413 if not default:
1414 default = b'glob'
1414 default = b'glob'
1415 hasset = any(matchmod.patkind(p) == b'set' for p in pats + inc + exc)
1415 hasset = any(matchmod.patkind(p) == b'set' for p in pats + inc + exc)
1416
1416
1417 mcache = [None]
1417 mcache = [None]
1418
1418
1419 # This directly read the changelog data as creating changectx for all
1419 # This directly read the changelog data as creating changectx for all
1420 # revisions is quite expensive.
1420 # revisions is quite expensive.
1421 getfiles = repo.changelog.readfiles
1421 getfiles = repo.changelog.readfiles
1422
1422
1423 def matches(x):
1423 def matches(x):
1424 if x == wdirrev:
1424 if x == wdirrev:
1425 files = repo[x].files()
1425 files = repo[x].files()
1426 else:
1426 else:
1427 files = getfiles(x)
1427 files = getfiles(x)
1428
1428
1429 if not mcache[0] or (hasset and rev is None):
1429 if not mcache[0] or (hasset and rev is None):
1430 r = x if rev is None else rev
1430 r = x if rev is None else rev
1431 mcache[0] = matchmod.match(
1431 mcache[0] = matchmod.match(
1432 repo.root,
1432 repo.root,
1433 repo.getcwd(),
1433 repo.getcwd(),
1434 pats,
1434 pats,
1435 include=inc,
1435 include=inc,
1436 exclude=exc,
1436 exclude=exc,
1437 ctx=repo[r],
1437 ctx=repo[r],
1438 default=default,
1438 default=default,
1439 )
1439 )
1440 m = mcache[0]
1440 m = mcache[0]
1441
1441
1442 for f in files:
1442 for f in files:
1443 if m(f):
1443 if m(f):
1444 return True
1444 return True
1445 return False
1445 return False
1446
1446
1447 return subset.filter(
1447 return subset.filter(
1448 matches,
1448 matches,
1449 condrepr=(
1449 condrepr=(
1450 b'<matchfiles patterns=%r, include=%r '
1450 b'<matchfiles patterns=%r, include=%r '
1451 b'exclude=%r, default=%r, rev=%r>',
1451 b'exclude=%r, default=%r, rev=%r>',
1452 pats,
1452 pats,
1453 inc,
1453 inc,
1454 exc,
1454 exc,
1455 default,
1455 default,
1456 rev,
1456 rev,
1457 ),
1457 ),
1458 )
1458 )
1459
1459
1460
1460
1461 @predicate(b'file(pattern)', safe=True, weight=10)
1461 @predicate(b'file(pattern)', safe=True, weight=10)
1462 def hasfile(repo, subset, x):
1462 def hasfile(repo, subset, x):
1463 """Changesets affecting files matched by pattern.
1463 """Changesets affecting files matched by pattern.
1464
1464
1465 For a faster but less accurate result, consider using ``filelog()``
1465 For a faster but less accurate result, consider using ``filelog()``
1466 instead.
1466 instead.
1467
1467
1468 This predicate uses ``glob:`` as the default kind of pattern.
1468 This predicate uses ``glob:`` as the default kind of pattern.
1469 """
1469 """
1470 # i18n: "file" is a keyword
1470 # i18n: "file" is a keyword
1471 pat = getstring(x, _(b"file requires a pattern"))
1471 pat = getstring(x, _(b"file requires a pattern"))
1472 return _matchfiles(repo, subset, (b'string', b'p:' + pat))
1472 return _matchfiles(repo, subset, (b'string', b'p:' + pat))
1473
1473
1474
1474
1475 @predicate(b'head()', safe=True)
1475 @predicate(b'head()', safe=True)
1476 def head(repo, subset, x):
1476 def head(repo, subset, x):
1477 """Changeset is a named branch head."""
1477 """Changeset is a named branch head."""
1478 # i18n: "head" is a keyword
1478 # i18n: "head" is a keyword
1479 getargs(x, 0, 0, _(b"head takes no arguments"))
1479 getargs(x, 0, 0, _(b"head takes no arguments"))
1480 hs = set()
1480 hs = set()
1481 cl = repo.changelog
1481 cl = repo.changelog
1482 for ls in repo.branchmap().iterheads():
1482 for ls in repo.branchmap().iterheads():
1483 hs.update(cl.rev(h) for h in ls)
1483 hs.update(cl.rev(h) for h in ls)
1484 return subset & baseset(hs)
1484 return subset & baseset(hs)
1485
1485
1486
1486
1487 @predicate(b'heads(set)', safe=True, takeorder=True)
1487 @predicate(b'heads(set)', safe=True, takeorder=True)
1488 def heads(repo, subset, x, order):
1488 def heads(repo, subset, x, order):
1489 """Members of set with no children in set."""
1489 """Members of set with no children in set."""
1490 # argument set should never define order
1490 # argument set should never define order
1491 if order == defineorder:
1491 if order == defineorder:
1492 order = followorder
1492 order = followorder
1493 inputset = getset(repo, fullreposet(repo), x, order=order)
1493 inputset = getset(repo, fullreposet(repo), x, order=order)
1494 wdirparents = None
1494 wdirparents = None
1495 if wdirrev in inputset:
1495 if wdirrev in inputset:
1496 # a bit slower, but not common so good enough for now
1496 # a bit slower, but not common so good enough for now
1497 wdirparents = [p.rev() for p in repo[None].parents()]
1497 wdirparents = [p.rev() for p in repo[None].parents()]
1498 inputset = set(inputset)
1498 inputset = set(inputset)
1499 inputset.discard(wdirrev)
1499 inputset.discard(wdirrev)
1500 heads = repo.changelog.headrevs(inputset)
1500 heads = repo.changelog.headrevs(inputset)
1501 if wdirparents is not None:
1501 if wdirparents is not None:
1502 heads.difference_update(wdirparents)
1502 heads.difference_update(wdirparents)
1503 heads.add(wdirrev)
1503 heads.add(wdirrev)
1504 heads = baseset(heads)
1504 heads = baseset(heads)
1505 return subset & heads
1505 return subset & heads
1506
1506
1507
1507
1508 @predicate(b'hidden()', safe=True)
1508 @predicate(b'hidden()', safe=True)
1509 def hidden(repo, subset, x):
1509 def hidden(repo, subset, x):
1510 """Hidden changesets."""
1510 """Hidden changesets."""
1511 # i18n: "hidden" is a keyword
1511 # i18n: "hidden" is a keyword
1512 getargs(x, 0, 0, _(b"hidden takes no arguments"))
1512 getargs(x, 0, 0, _(b"hidden takes no arguments"))
1513 hiddenrevs = repoview.filterrevs(repo, b'visible')
1513 hiddenrevs = repoview.filterrevs(repo, b'visible')
1514 return subset & hiddenrevs
1514 return subset & hiddenrevs
1515
1515
1516
1516
1517 @predicate(b'keyword(string)', safe=True, weight=10)
1517 @predicate(b'keyword(string)', safe=True, weight=10)
1518 def keyword(repo, subset, x):
1518 def keyword(repo, subset, x):
1519 """Search commit message, user name, and names of changed files for
1519 """Search commit message, user name, and names of changed files for
1520 string. The match is case-insensitive.
1520 string. The match is case-insensitive.
1521
1521
1522 For a regular expression or case sensitive search of these fields, use
1522 For a regular expression or case sensitive search of these fields, use
1523 ``grep(regex)``.
1523 ``grep(regex)``.
1524 """
1524 """
1525 # i18n: "keyword" is a keyword
1525 # i18n: "keyword" is a keyword
1526 kw = encoding.lower(getstring(x, _(b"keyword requires a string")))
1526 kw = encoding.lower(getstring(x, _(b"keyword requires a string")))
1527
1527
1528 def matches(r):
1528 def matches(r):
1529 c = repo[r]
1529 c = repo[r]
1530 return any(
1530 return any(
1531 kw in encoding.lower(t)
1531 kw in encoding.lower(t)
1532 for t in c.files() + [c.user(), c.description()]
1532 for t in c.files() + [c.user(), c.description()]
1533 )
1533 )
1534
1534
1535 return subset.filter(matches, condrepr=(b'<keyword %r>', kw))
1535 return subset.filter(matches, condrepr=(b'<keyword %r>', kw))
1536
1536
1537
1537
1538 @predicate(b'limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1538 @predicate(b'limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1539 def limit(repo, subset, x, order):
1539 def limit(repo, subset, x, order):
1540 """First n members of set, defaulting to 1, starting from offset."""
1540 """First n members of set, defaulting to 1, starting from offset."""
1541 args = getargsdict(x, b'limit', b'set n offset')
1541 args = getargsdict(x, b'limit', b'set n offset')
1542 if b'set' not in args:
1542 if b'set' not in args:
1543 # i18n: "limit" is a keyword
1543 # i18n: "limit" is a keyword
1544 raise error.ParseError(_(b"limit requires one to three arguments"))
1544 raise error.ParseError(_(b"limit requires one to three arguments"))
1545 # i18n: "limit" is a keyword
1545 # i18n: "limit" is a keyword
1546 lim = getinteger(args.get(b'n'), _(b"limit expects a number"), default=1)
1546 lim = getinteger(args.get(b'n'), _(b"limit expects a number"), default=1)
1547 if lim < 0:
1547 if lim < 0:
1548 raise error.ParseError(_(b"negative number to select"))
1548 raise error.ParseError(_(b"negative number to select"))
1549 # i18n: "limit" is a keyword
1549 # i18n: "limit" is a keyword
1550 ofs = getinteger(
1550 ofs = getinteger(
1551 args.get(b'offset'), _(b"limit expects a number"), default=0
1551 args.get(b'offset'), _(b"limit expects a number"), default=0
1552 )
1552 )
1553 if ofs < 0:
1553 if ofs < 0:
1554 raise error.ParseError(_(b"negative offset"))
1554 raise error.ParseError(_(b"negative offset"))
1555 os = getset(repo, fullreposet(repo), args[b'set'])
1555 os = getset(repo, fullreposet(repo), args[b'set'])
1556 ls = os.slice(ofs, ofs + lim)
1556 ls = os.slice(ofs, ofs + lim)
1557 if order == followorder and lim > 1:
1557 if order == followorder and lim > 1:
1558 return subset & ls
1558 return subset & ls
1559 return ls & subset
1559 return ls & subset
1560
1560
1561
1561
1562 @predicate(b'last(set, [n])', safe=True, takeorder=True)
1562 @predicate(b'last(set, [n])', safe=True, takeorder=True)
1563 def last(repo, subset, x, order):
1563 def last(repo, subset, x, order):
1564 """Last n members of set, defaulting to 1."""
1564 """Last n members of set, defaulting to 1."""
1565 # i18n: "last" is a keyword
1565 # i18n: "last" is a keyword
1566 l = getargs(x, 1, 2, _(b"last requires one or two arguments"))
1566 l = getargs(x, 1, 2, _(b"last requires one or two arguments"))
1567 lim = 1
1567 lim = 1
1568 if len(l) == 2:
1568 if len(l) == 2:
1569 # i18n: "last" is a keyword
1569 # i18n: "last" is a keyword
1570 lim = getinteger(l[1], _(b"last expects a number"))
1570 lim = getinteger(l[1], _(b"last expects a number"))
1571 if lim < 0:
1571 if lim < 0:
1572 raise error.ParseError(_(b"negative number to select"))
1572 raise error.ParseError(_(b"negative number to select"))
1573 os = getset(repo, fullreposet(repo), l[0])
1573 os = getset(repo, fullreposet(repo), l[0])
1574 os.reverse()
1574 os.reverse()
1575 ls = os.slice(0, lim)
1575 ls = os.slice(0, lim)
1576 if order == followorder and lim > 1:
1576 if order == followorder and lim > 1:
1577 return subset & ls
1577 return subset & ls
1578 ls.reverse()
1578 ls.reverse()
1579 return ls & subset
1579 return ls & subset
1580
1580
1581
1581
1582 @predicate(b'max(set)', safe=True)
1582 @predicate(b'max(set)', safe=True)
1583 def maxrev(repo, subset, x):
1583 def maxrev(repo, subset, x):
1584 """Changeset with highest revision number in set."""
1584 """Changeset with highest revision number in set."""
1585 os = getset(repo, fullreposet(repo), x)
1585 os = getset(repo, fullreposet(repo), x)
1586 try:
1586 try:
1587 m = os.max()
1587 m = os.max()
1588 if m in subset:
1588 if m in subset:
1589 return baseset([m], datarepr=(b'<max %r, %r>', subset, os))
1589 return baseset([m], datarepr=(b'<max %r, %r>', subset, os))
1590 except ValueError:
1590 except ValueError:
1591 # os.max() throws a ValueError when the collection is empty.
1591 # os.max() throws a ValueError when the collection is empty.
1592 # Same as python's max().
1592 # Same as python's max().
1593 pass
1593 pass
1594 return baseset(datarepr=(b'<max %r, %r>', subset, os))
1594 return baseset(datarepr=(b'<max %r, %r>', subset, os))
1595
1595
1596
1596
1597 @predicate(b'merge()', safe=True)
1597 @predicate(b'merge()', safe=True)
1598 def merge(repo, subset, x):
1598 def merge(repo, subset, x):
1599 """Changeset is a merge changeset."""
1599 """Changeset is a merge changeset."""
1600 # i18n: "merge" is a keyword
1600 # i18n: "merge" is a keyword
1601 getargs(x, 0, 0, _(b"merge takes no arguments"))
1601 getargs(x, 0, 0, _(b"merge takes no arguments"))
1602 cl = repo.changelog
1602 cl = repo.changelog
1603
1603
1604 def ismerge(r):
1604 def ismerge(r):
1605 try:
1605 try:
1606 return cl.parentrevs(r)[1] != nullrev
1606 return cl.parentrevs(r)[1] != nullrev
1607 except error.WdirUnsupported:
1607 except error.WdirUnsupported:
1608 return bool(repo[r].p2())
1608 return bool(repo[r].p2())
1609
1609
1610 return subset.filter(ismerge, condrepr=b'<merge>')
1610 return subset.filter(ismerge, condrepr=b'<merge>')
1611
1611
1612
1612
1613 @predicate(b'branchpoint()', safe=True)
1613 @predicate(b'branchpoint()', safe=True)
1614 def branchpoint(repo, subset, x):
1614 def branchpoint(repo, subset, x):
1615 """Changesets with more than one child."""
1615 """Changesets with more than one child."""
1616 # i18n: "branchpoint" is a keyword
1616 # i18n: "branchpoint" is a keyword
1617 getargs(x, 0, 0, _(b"branchpoint takes no arguments"))
1617 getargs(x, 0, 0, _(b"branchpoint takes no arguments"))
1618 cl = repo.changelog
1618 cl = repo.changelog
1619 if not subset:
1619 if not subset:
1620 return baseset()
1620 return baseset()
1621 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1621 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1622 # (and if it is not, it should.)
1622 # (and if it is not, it should.)
1623 baserev = min(subset)
1623 baserev = min(subset)
1624 parentscount = [0] * (len(repo) - baserev)
1624 parentscount = [0] * (len(repo) - baserev)
1625 for r in cl.revs(start=baserev + 1):
1625 for r in cl.revs(start=baserev + 1):
1626 for p in cl.parentrevs(r):
1626 for p in cl.parentrevs(r):
1627 if p >= baserev:
1627 if p >= baserev:
1628 parentscount[p - baserev] += 1
1628 parentscount[p - baserev] += 1
1629 return subset.filter(
1629 return subset.filter(
1630 lambda r: parentscount[r - baserev] > 1, condrepr=b'<branchpoint>'
1630 lambda r: parentscount[r - baserev] > 1, condrepr=b'<branchpoint>'
1631 )
1631 )
1632
1632
1633
1633
1634 @predicate(b'min(set)', safe=True)
1634 @predicate(b'min(set)', safe=True)
1635 def minrev(repo, subset, x):
1635 def minrev(repo, subset, x):
1636 """Changeset with lowest revision number in set."""
1636 """Changeset with lowest revision number in set."""
1637 os = getset(repo, fullreposet(repo), x)
1637 os = getset(repo, fullreposet(repo), x)
1638 try:
1638 try:
1639 m = os.min()
1639 m = os.min()
1640 if m in subset:
1640 if m in subset:
1641 return baseset([m], datarepr=(b'<min %r, %r>', subset, os))
1641 return baseset([m], datarepr=(b'<min %r, %r>', subset, os))
1642 except ValueError:
1642 except ValueError:
1643 # os.min() throws a ValueError when the collection is empty.
1643 # os.min() throws a ValueError when the collection is empty.
1644 # Same as python's min().
1644 # Same as python's min().
1645 pass
1645 pass
1646 return baseset(datarepr=(b'<min %r, %r>', subset, os))
1646 return baseset(datarepr=(b'<min %r, %r>', subset, os))
1647
1647
1648
1648
1649 @predicate(b'modifies(pattern)', safe=True, weight=30)
1649 @predicate(b'modifies(pattern)', safe=True, weight=30)
1650 def modifies(repo, subset, x):
1650 def modifies(repo, subset, x):
1651 """Changesets modifying files matched by pattern.
1651 """Changesets modifying files matched by pattern.
1652
1652
1653 The pattern without explicit kind like ``glob:`` is expected to be
1653 The pattern without explicit kind like ``glob:`` is expected to be
1654 relative to the current directory and match against a file or a
1654 relative to the current directory and match against a file or a
1655 directory.
1655 directory.
1656 """
1656 """
1657 # i18n: "modifies" is a keyword
1657 # i18n: "modifies" is a keyword
1658 pat = getstring(x, _(b"modifies requires a pattern"))
1658 pat = getstring(x, _(b"modifies requires a pattern"))
1659 return checkstatus(repo, subset, pat, 'modified')
1659 return checkstatus(repo, subset, pat, 'modified')
1660
1660
1661
1661
1662 @predicate(b'named(namespace)')
1662 @predicate(b'named(namespace)')
1663 def named(repo, subset, x):
1663 def named(repo, subset, x):
1664 """The changesets in a given namespace.
1664 """The changesets in a given namespace.
1665
1665
1666 Pattern matching is supported for `namespace`. See
1666 Pattern matching is supported for `namespace`. See
1667 :hg:`help revisions.patterns`.
1667 :hg:`help revisions.patterns`.
1668 """
1668 """
1669 # i18n: "named" is a keyword
1669 # i18n: "named" is a keyword
1670 args = getargs(x, 1, 1, _(b'named requires a namespace argument'))
1670 args = getargs(x, 1, 1, _(b'named requires a namespace argument'))
1671
1671
1672 ns = getstring(
1672 ns = getstring(
1673 args[0],
1673 args[0],
1674 # i18n: "named" is a keyword
1674 # i18n: "named" is a keyword
1675 _(b'the argument to named must be a string'),
1675 _(b'the argument to named must be a string'),
1676 )
1676 )
1677 kind, pattern, matcher = stringutil.stringmatcher(ns)
1677 kind, pattern, matcher = stringutil.stringmatcher(ns)
1678 namespaces = set()
1678 namespaces = set()
1679 if kind == b'literal':
1679 if kind == b'literal':
1680 if pattern not in repo.names:
1680 if pattern not in repo.names:
1681 raise error.RepoLookupError(
1681 raise error.RepoLookupError(
1682 _(b"namespace '%s' does not exist") % ns
1682 _(b"namespace '%s' does not exist") % ns
1683 )
1683 )
1684 namespaces.add(repo.names[pattern])
1684 namespaces.add(repo.names[pattern])
1685 else:
1685 else:
1686 for name, ns in pycompat.iteritems(repo.names):
1686 for name, ns in pycompat.iteritems(repo.names):
1687 if matcher(name):
1687 if matcher(name):
1688 namespaces.add(ns)
1688 namespaces.add(ns)
1689
1689
1690 names = set()
1690 names = set()
1691 for ns in namespaces:
1691 for ns in namespaces:
1692 for name in ns.listnames(repo):
1692 for name in ns.listnames(repo):
1693 if name not in ns.deprecated:
1693 if name not in ns.deprecated:
1694 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1694 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1695
1695
1696 names -= {nullrev}
1696 names -= {nullrev}
1697 return subset & names
1697 return subset & names
1698
1698
1699
1699
1700 @predicate(b'id(string)', safe=True)
1700 @predicate(b'id(string)', safe=True)
1701 def node_(repo, subset, x):
1701 def node_(repo, subset, x):
1702 """Revision non-ambiguously specified by the given hex string prefix."""
1702 """Revision non-ambiguously specified by the given hex string prefix."""
1703 # i18n: "id" is a keyword
1703 # i18n: "id" is a keyword
1704 l = getargs(x, 1, 1, _(b"id requires one argument"))
1704 l = getargs(x, 1, 1, _(b"id requires one argument"))
1705 # i18n: "id" is a keyword
1705 # i18n: "id" is a keyword
1706 n = getstring(l[0], _(b"id requires a string"))
1706 n = getstring(l[0], _(b"id requires a string"))
1707 if len(n) == 40:
1707 if len(n) == 40:
1708 try:
1708 try:
1709 rn = repo.changelog.rev(bin(n))
1709 rn = repo.changelog.rev(bin(n))
1710 except error.WdirUnsupported:
1710 except error.WdirUnsupported:
1711 rn = wdirrev
1711 rn = wdirrev
1712 except (LookupError, TypeError):
1712 except (LookupError, TypeError):
1713 rn = None
1713 rn = None
1714 else:
1714 else:
1715 rn = None
1715 rn = None
1716 try:
1716 try:
1717 pm = scmutil.resolvehexnodeidprefix(repo, n)
1717 pm = scmutil.resolvehexnodeidprefix(repo, n)
1718 if pm is not None:
1718 if pm is not None:
1719 rn = repo.changelog.rev(pm)
1719 rn = repo.changelog.rev(pm)
1720 except LookupError:
1720 except LookupError:
1721 pass
1721 pass
1722 except error.WdirUnsupported:
1722 except error.WdirUnsupported:
1723 rn = wdirrev
1723 rn = wdirrev
1724
1724
1725 if rn is None:
1725 if rn is None:
1726 return baseset()
1726 return baseset()
1727 result = baseset([rn])
1727 result = baseset([rn])
1728 return result & subset
1728 return result & subset
1729
1729
1730
1730
1731 @predicate(b'none()', safe=True)
1731 @predicate(b'none()', safe=True)
1732 def none(repo, subset, x):
1732 def none(repo, subset, x):
1733 """No changesets."""
1733 """No changesets."""
1734 # i18n: "none" is a keyword
1734 # i18n: "none" is a keyword
1735 getargs(x, 0, 0, _(b"none takes no arguments"))
1735 getargs(x, 0, 0, _(b"none takes no arguments"))
1736 return baseset()
1736 return baseset()
1737
1737
1738
1738
1739 @predicate(b'obsolete()', safe=True)
1739 @predicate(b'obsolete()', safe=True)
1740 def obsolete(repo, subset, x):
1740 def obsolete(repo, subset, x):
1741 """Mutable changeset with a newer version. (EXPERIMENTAL)"""
1741 """Mutable changeset with a newer version. (EXPERIMENTAL)"""
1742 # i18n: "obsolete" is a keyword
1742 # i18n: "obsolete" is a keyword
1743 getargs(x, 0, 0, _(b"obsolete takes no arguments"))
1743 getargs(x, 0, 0, _(b"obsolete takes no arguments"))
1744 obsoletes = obsmod.getrevs(repo, b'obsolete')
1744 obsoletes = obsmod.getrevs(repo, b'obsolete')
1745 return subset & obsoletes
1745 return subset & obsoletes
1746
1746
1747
1747
1748 @predicate(b'only(set, [set])', safe=True)
1748 @predicate(b'only(set, [set])', safe=True)
1749 def only(repo, subset, x):
1749 def only(repo, subset, x):
1750 """Changesets that are ancestors of the first set that are not ancestors
1750 """Changesets that are ancestors of the first set that are not ancestors
1751 of any other head in the repo. If a second set is specified, the result
1751 of any other head in the repo. If a second set is specified, the result
1752 is ancestors of the first set that are not ancestors of the second set
1752 is ancestors of the first set that are not ancestors of the second set
1753 (i.e. ::<set1> - ::<set2>).
1753 (i.e. ::<set1> - ::<set2>).
1754 """
1754 """
1755 cl = repo.changelog
1755 cl = repo.changelog
1756 # i18n: "only" is a keyword
1756 # i18n: "only" is a keyword
1757 args = getargs(x, 1, 2, _(b'only takes one or two arguments'))
1757 args = getargs(x, 1, 2, _(b'only takes one or two arguments'))
1758 include = getset(repo, fullreposet(repo), args[0])
1758 include = getset(repo, fullreposet(repo), args[0])
1759 if len(args) == 1:
1759 if len(args) == 1:
1760 if not include:
1760 if not include:
1761 return baseset()
1761 return baseset()
1762
1762
1763 descendants = set(dagop.revdescendants(repo, include, False))
1763 descendants = set(dagop.revdescendants(repo, include, False))
1764 exclude = [
1764 exclude = [
1765 rev
1765 rev
1766 for rev in cl.headrevs()
1766 for rev in cl.headrevs()
1767 if not rev in descendants and not rev in include
1767 if not rev in descendants and not rev in include
1768 ]
1768 ]
1769 else:
1769 else:
1770 exclude = getset(repo, fullreposet(repo), args[1])
1770 exclude = getset(repo, fullreposet(repo), args[1])
1771
1771
1772 results = set(cl.findmissingrevs(common=exclude, heads=include))
1772 results = set(cl.findmissingrevs(common=exclude, heads=include))
1773 # XXX we should turn this into a baseset instead of a set, smartset may do
1773 # XXX we should turn this into a baseset instead of a set, smartset may do
1774 # some optimizations from the fact this is a baseset.
1774 # some optimizations from the fact this is a baseset.
1775 return subset & results
1775 return subset & results
1776
1776
1777
1777
1778 @predicate(b'origin([set])', safe=True)
1778 @predicate(b'origin([set])', safe=True)
1779 def origin(repo, subset, x):
1779 def origin(repo, subset, x):
1780 """
1780 """
1781 Changesets that were specified as a source for the grafts, transplants or
1781 Changesets that were specified as a source for the grafts, transplants or
1782 rebases that created the given revisions. Omitting the optional set is the
1782 rebases that created the given revisions. Omitting the optional set is the
1783 same as passing all(). If a changeset created by these operations is itself
1783 same as passing all(). If a changeset created by these operations is itself
1784 specified as a source for one of these operations, only the source changeset
1784 specified as a source for one of these operations, only the source changeset
1785 for the first operation is selected.
1785 for the first operation is selected.
1786 """
1786 """
1787 if x is not None:
1787 if x is not None:
1788 dests = getset(repo, fullreposet(repo), x)
1788 dests = getset(repo, fullreposet(repo), x)
1789 else:
1789 else:
1790 dests = fullreposet(repo)
1790 dests = fullreposet(repo)
1791
1791
1792 def _firstsrc(rev):
1792 def _firstsrc(rev):
1793 src = _getrevsource(repo, rev)
1793 src = _getrevsource(repo, rev)
1794 if src is None:
1794 if src is None:
1795 return None
1795 return None
1796
1796
1797 while True:
1797 while True:
1798 prev = _getrevsource(repo, src)
1798 prev = _getrevsource(repo, src)
1799
1799
1800 if prev is None:
1800 if prev is None:
1801 return src
1801 return src
1802 src = prev
1802 src = prev
1803
1803
1804 o = {_firstsrc(r) for r in dests}
1804 o = {_firstsrc(r) for r in dests}
1805 o -= {None}
1805 o -= {None}
1806 # XXX we should turn this into a baseset instead of a set, smartset may do
1806 # XXX we should turn this into a baseset instead of a set, smartset may do
1807 # some optimizations from the fact this is a baseset.
1807 # some optimizations from the fact this is a baseset.
1808 return subset & o
1808 return subset & o
1809
1809
1810
1810
1811 @predicate(b'outgoing([path])', safe=False, weight=10)
1811 @predicate(b'outgoing([path])', safe=False, weight=10)
1812 def outgoing(repo, subset, x):
1812 def outgoing(repo, subset, x):
1813 """Changesets not found in the specified destination repository, or the
1813 """Changesets not found in the specified destination repository, or the
1814 default push location.
1814 default push location.
1815 """
1815 """
1816 # Avoid cycles.
1816 # Avoid cycles.
1817 from . import (
1817 from . import (
1818 discovery,
1818 discovery,
1819 hg,
1819 hg,
1820 )
1820 )
1821
1821
1822 # i18n: "outgoing" is a keyword
1822 # i18n: "outgoing" is a keyword
1823 l = getargs(x, 0, 1, _(b"outgoing takes one or no arguments"))
1823 l = getargs(x, 0, 1, _(b"outgoing takes one or no arguments"))
1824 # i18n: "outgoing" is a keyword
1824 # i18n: "outgoing" is a keyword
1825 dest = (
1825 dest = (
1826 l and getstring(l[0], _(b"outgoing requires a repository path")) or b''
1826 l and getstring(l[0], _(b"outgoing requires a repository path")) or b''
1827 )
1827 )
1828 if not dest:
1828 if not dest:
1829 # ui.paths.getpath() explicitly tests for None, not just a boolean
1829 # ui.paths.getpath() explicitly tests for None, not just a boolean
1830 dest = None
1830 dest = None
1831 path = repo.ui.paths.getpath(dest, default=(b'default-push', b'default'))
1831 path = repo.ui.paths.getpath(dest, default=(b'default-push', b'default'))
1832 if not path:
1832 if not path:
1833 raise error.Abort(
1833 raise error.Abort(
1834 _(b'default repository not configured!'),
1834 _(b'default repository not configured!'),
1835 hint=_(b"see 'hg help config.paths'"),
1835 hint=_(b"see 'hg help config.paths'"),
1836 )
1836 )
1837 dest = path.pushloc or path.loc
1837 dest = path.pushloc or path.loc
1838 branches = path.branch, []
1838 branches = path.branch, []
1839
1839
1840 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1840 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1841 if revs:
1841 if revs:
1842 revs = [repo.lookup(rev) for rev in revs]
1842 revs = [repo.lookup(rev) for rev in revs]
1843 other = hg.peer(repo, {}, dest)
1843 other = hg.peer(repo, {}, dest)
1844 repo.ui.pushbuffer()
1844 try:
1845 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1845 repo.ui.pushbuffer()
1846 repo.ui.popbuffer()
1846 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1847 repo.ui.popbuffer()
1848 finally:
1849 other.close()
1847 cl = repo.changelog
1850 cl = repo.changelog
1848 o = {cl.rev(r) for r in outgoing.missing}
1851 o = {cl.rev(r) for r in outgoing.missing}
1849 return subset & o
1852 return subset & o
1850
1853
1851
1854
1852 @predicate(b'p1([set])', safe=True)
1855 @predicate(b'p1([set])', safe=True)
1853 def p1(repo, subset, x):
1856 def p1(repo, subset, x):
1854 """First parent of changesets in set, or the working directory."""
1857 """First parent of changesets in set, or the working directory."""
1855 if x is None:
1858 if x is None:
1856 p = repo[x].p1().rev()
1859 p = repo[x].p1().rev()
1857 if p >= 0:
1860 if p >= 0:
1858 return subset & baseset([p])
1861 return subset & baseset([p])
1859 return baseset()
1862 return baseset()
1860
1863
1861 ps = set()
1864 ps = set()
1862 cl = repo.changelog
1865 cl = repo.changelog
1863 for r in getset(repo, fullreposet(repo), x):
1866 for r in getset(repo, fullreposet(repo), x):
1864 try:
1867 try:
1865 ps.add(cl.parentrevs(r)[0])
1868 ps.add(cl.parentrevs(r)[0])
1866 except error.WdirUnsupported:
1869 except error.WdirUnsupported:
1867 ps.add(repo[r].p1().rev())
1870 ps.add(repo[r].p1().rev())
1868 ps -= {nullrev}
1871 ps -= {nullrev}
1869 # XXX we should turn this into a baseset instead of a set, smartset may do
1872 # XXX we should turn this into a baseset instead of a set, smartset may do
1870 # some optimizations from the fact this is a baseset.
1873 # some optimizations from the fact this is a baseset.
1871 return subset & ps
1874 return subset & ps
1872
1875
1873
1876
1874 @predicate(b'p2([set])', safe=True)
1877 @predicate(b'p2([set])', safe=True)
1875 def p2(repo, subset, x):
1878 def p2(repo, subset, x):
1876 """Second parent of changesets in set, or the working directory."""
1879 """Second parent of changesets in set, or the working directory."""
1877 if x is None:
1880 if x is None:
1878 ps = repo[x].parents()
1881 ps = repo[x].parents()
1879 try:
1882 try:
1880 p = ps[1].rev()
1883 p = ps[1].rev()
1881 if p >= 0:
1884 if p >= 0:
1882 return subset & baseset([p])
1885 return subset & baseset([p])
1883 return baseset()
1886 return baseset()
1884 except IndexError:
1887 except IndexError:
1885 return baseset()
1888 return baseset()
1886
1889
1887 ps = set()
1890 ps = set()
1888 cl = repo.changelog
1891 cl = repo.changelog
1889 for r in getset(repo, fullreposet(repo), x):
1892 for r in getset(repo, fullreposet(repo), x):
1890 try:
1893 try:
1891 ps.add(cl.parentrevs(r)[1])
1894 ps.add(cl.parentrevs(r)[1])
1892 except error.WdirUnsupported:
1895 except error.WdirUnsupported:
1893 parents = repo[r].parents()
1896 parents = repo[r].parents()
1894 if len(parents) == 2:
1897 if len(parents) == 2:
1895 ps.add(parents[1])
1898 ps.add(parents[1])
1896 ps -= {nullrev}
1899 ps -= {nullrev}
1897 # XXX we should turn this into a baseset instead of a set, smartset may do
1900 # XXX we should turn this into a baseset instead of a set, smartset may do
1898 # some optimizations from the fact this is a baseset.
1901 # some optimizations from the fact this is a baseset.
1899 return subset & ps
1902 return subset & ps
1900
1903
1901
1904
1902 def parentpost(repo, subset, x, order):
1905 def parentpost(repo, subset, x, order):
1903 return p1(repo, subset, x)
1906 return p1(repo, subset, x)
1904
1907
1905
1908
1906 @predicate(b'parents([set])', safe=True)
1909 @predicate(b'parents([set])', safe=True)
1907 def parents(repo, subset, x):
1910 def parents(repo, subset, x):
1908 """
1911 """
1909 The set of all parents for all changesets in set, or the working directory.
1912 The set of all parents for all changesets in set, or the working directory.
1910 """
1913 """
1911 if x is None:
1914 if x is None:
1912 ps = {p.rev() for p in repo[x].parents()}
1915 ps = {p.rev() for p in repo[x].parents()}
1913 else:
1916 else:
1914 ps = set()
1917 ps = set()
1915 cl = repo.changelog
1918 cl = repo.changelog
1916 up = ps.update
1919 up = ps.update
1917 parentrevs = cl.parentrevs
1920 parentrevs = cl.parentrevs
1918 for r in getset(repo, fullreposet(repo), x):
1921 for r in getset(repo, fullreposet(repo), x):
1919 try:
1922 try:
1920 up(parentrevs(r))
1923 up(parentrevs(r))
1921 except error.WdirUnsupported:
1924 except error.WdirUnsupported:
1922 up(p.rev() for p in repo[r].parents())
1925 up(p.rev() for p in repo[r].parents())
1923 ps -= {nullrev}
1926 ps -= {nullrev}
1924 return subset & ps
1927 return subset & ps
1925
1928
1926
1929
1927 def _phase(repo, subset, *targets):
1930 def _phase(repo, subset, *targets):
1928 """helper to select all rev in <targets> phases"""
1931 """helper to select all rev in <targets> phases"""
1929 return repo._phasecache.getrevset(repo, targets, subset)
1932 return repo._phasecache.getrevset(repo, targets, subset)
1930
1933
1931
1934
1932 @predicate(b'_phase(idx)', safe=True)
1935 @predicate(b'_phase(idx)', safe=True)
1933 def phase(repo, subset, x):
1936 def phase(repo, subset, x):
1934 l = getargs(x, 1, 1, b"_phase requires one argument")
1937 l = getargs(x, 1, 1, b"_phase requires one argument")
1935 target = getinteger(l[0], b"_phase expects a number")
1938 target = getinteger(l[0], b"_phase expects a number")
1936 return _phase(repo, subset, target)
1939 return _phase(repo, subset, target)
1937
1940
1938
1941
1939 @predicate(b'draft()', safe=True)
1942 @predicate(b'draft()', safe=True)
1940 def draft(repo, subset, x):
1943 def draft(repo, subset, x):
1941 """Changeset in draft phase."""
1944 """Changeset in draft phase."""
1942 # i18n: "draft" is a keyword
1945 # i18n: "draft" is a keyword
1943 getargs(x, 0, 0, _(b"draft takes no arguments"))
1946 getargs(x, 0, 0, _(b"draft takes no arguments"))
1944 target = phases.draft
1947 target = phases.draft
1945 return _phase(repo, subset, target)
1948 return _phase(repo, subset, target)
1946
1949
1947
1950
1948 @predicate(b'secret()', safe=True)
1951 @predicate(b'secret()', safe=True)
1949 def secret(repo, subset, x):
1952 def secret(repo, subset, x):
1950 """Changeset in secret phase."""
1953 """Changeset in secret phase."""
1951 # i18n: "secret" is a keyword
1954 # i18n: "secret" is a keyword
1952 getargs(x, 0, 0, _(b"secret takes no arguments"))
1955 getargs(x, 0, 0, _(b"secret takes no arguments"))
1953 target = phases.secret
1956 target = phases.secret
1954 return _phase(repo, subset, target)
1957 return _phase(repo, subset, target)
1955
1958
1956
1959
1957 @predicate(b'stack([revs])', safe=True)
1960 @predicate(b'stack([revs])', safe=True)
1958 def stack(repo, subset, x):
1961 def stack(repo, subset, x):
1959 """Experimental revset for the stack of changesets or working directory
1962 """Experimental revset for the stack of changesets or working directory
1960 parent. (EXPERIMENTAL)
1963 parent. (EXPERIMENTAL)
1961 """
1964 """
1962 if x is None:
1965 if x is None:
1963 stacks = stackmod.getstack(repo)
1966 stacks = stackmod.getstack(repo)
1964 else:
1967 else:
1965 stacks = smartset.baseset([])
1968 stacks = smartset.baseset([])
1966 for revision in getset(repo, fullreposet(repo), x):
1969 for revision in getset(repo, fullreposet(repo), x):
1967 currentstack = stackmod.getstack(repo, revision)
1970 currentstack = stackmod.getstack(repo, revision)
1968 stacks = stacks + currentstack
1971 stacks = stacks + currentstack
1969
1972
1970 return subset & stacks
1973 return subset & stacks
1971
1974
1972
1975
1973 def parentspec(repo, subset, x, n, order):
1976 def parentspec(repo, subset, x, n, order):
1974 """``set^0``
1977 """``set^0``
1975 The set.
1978 The set.
1976 ``set^1`` (or ``set^``), ``set^2``
1979 ``set^1`` (or ``set^``), ``set^2``
1977 First or second parent, respectively, of all changesets in set.
1980 First or second parent, respectively, of all changesets in set.
1978 """
1981 """
1979 try:
1982 try:
1980 n = int(n[1])
1983 n = int(n[1])
1981 if n not in (0, 1, 2):
1984 if n not in (0, 1, 2):
1982 raise ValueError
1985 raise ValueError
1983 except (TypeError, ValueError):
1986 except (TypeError, ValueError):
1984 raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
1987 raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
1985 ps = set()
1988 ps = set()
1986 cl = repo.changelog
1989 cl = repo.changelog
1987 for r in getset(repo, fullreposet(repo), x):
1990 for r in getset(repo, fullreposet(repo), x):
1988 if n == 0:
1991 if n == 0:
1989 ps.add(r)
1992 ps.add(r)
1990 elif n == 1:
1993 elif n == 1:
1991 try:
1994 try:
1992 ps.add(cl.parentrevs(r)[0])
1995 ps.add(cl.parentrevs(r)[0])
1993 except error.WdirUnsupported:
1996 except error.WdirUnsupported:
1994 ps.add(repo[r].p1().rev())
1997 ps.add(repo[r].p1().rev())
1995 else:
1998 else:
1996 try:
1999 try:
1997 parents = cl.parentrevs(r)
2000 parents = cl.parentrevs(r)
1998 if parents[1] != nullrev:
2001 if parents[1] != nullrev:
1999 ps.add(parents[1])
2002 ps.add(parents[1])
2000 except error.WdirUnsupported:
2003 except error.WdirUnsupported:
2001 parents = repo[r].parents()
2004 parents = repo[r].parents()
2002 if len(parents) == 2:
2005 if len(parents) == 2:
2003 ps.add(parents[1].rev())
2006 ps.add(parents[1].rev())
2004 return subset & ps
2007 return subset & ps
2005
2008
2006
2009
2007 @predicate(b'present(set)', safe=True, takeorder=True)
2010 @predicate(b'present(set)', safe=True, takeorder=True)
2008 def present(repo, subset, x, order):
2011 def present(repo, subset, x, order):
2009 """An empty set, if any revision in set isn't found; otherwise,
2012 """An empty set, if any revision in set isn't found; otherwise,
2010 all revisions in set.
2013 all revisions in set.
2011
2014
2012 If any of specified revisions is not present in the local repository,
2015 If any of specified revisions is not present in the local repository,
2013 the query is normally aborted. But this predicate allows the query
2016 the query is normally aborted. But this predicate allows the query
2014 to continue even in such cases.
2017 to continue even in such cases.
2015 """
2018 """
2016 try:
2019 try:
2017 return getset(repo, subset, x, order)
2020 return getset(repo, subset, x, order)
2018 except error.RepoLookupError:
2021 except error.RepoLookupError:
2019 return baseset()
2022 return baseset()
2020
2023
2021
2024
2022 # for internal use
2025 # for internal use
2023 @predicate(b'_notpublic', safe=True)
2026 @predicate(b'_notpublic', safe=True)
2024 def _notpublic(repo, subset, x):
2027 def _notpublic(repo, subset, x):
2025 getargs(x, 0, 0, b"_notpublic takes no arguments")
2028 getargs(x, 0, 0, b"_notpublic takes no arguments")
2026 return _phase(repo, subset, phases.draft, phases.secret)
2029 return _phase(repo, subset, phases.draft, phases.secret)
2027
2030
2028
2031
2029 # for internal use
2032 # for internal use
2030 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
2033 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
2031 def _phaseandancestors(repo, subset, x):
2034 def _phaseandancestors(repo, subset, x):
2032 # equivalent to (phasename() & ancestors(set)) but more efficient
2035 # equivalent to (phasename() & ancestors(set)) but more efficient
2033 # phasename could be one of 'draft', 'secret', or '_notpublic'
2036 # phasename could be one of 'draft', 'secret', or '_notpublic'
2034 args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
2037 args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
2035 phasename = getsymbol(args[0])
2038 phasename = getsymbol(args[0])
2036 s = getset(repo, fullreposet(repo), args[1])
2039 s = getset(repo, fullreposet(repo), args[1])
2037
2040
2038 draft = phases.draft
2041 draft = phases.draft
2039 secret = phases.secret
2042 secret = phases.secret
2040 phasenamemap = {
2043 phasenamemap = {
2041 b'_notpublic': draft,
2044 b'_notpublic': draft,
2042 b'draft': draft, # follow secret's ancestors
2045 b'draft': draft, # follow secret's ancestors
2043 b'secret': secret,
2046 b'secret': secret,
2044 }
2047 }
2045 if phasename not in phasenamemap:
2048 if phasename not in phasenamemap:
2046 raise error.ParseError(b'%r is not a valid phasename' % phasename)
2049 raise error.ParseError(b'%r is not a valid phasename' % phasename)
2047
2050
2048 minimalphase = phasenamemap[phasename]
2051 minimalphase = phasenamemap[phasename]
2049 getphase = repo._phasecache.phase
2052 getphase = repo._phasecache.phase
2050
2053
2051 def cutfunc(rev):
2054 def cutfunc(rev):
2052 return getphase(repo, rev) < minimalphase
2055 return getphase(repo, rev) < minimalphase
2053
2056
2054 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
2057 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
2055
2058
2056 if phasename == b'draft': # need to remove secret changesets
2059 if phasename == b'draft': # need to remove secret changesets
2057 revs = revs.filter(lambda r: getphase(repo, r) == draft)
2060 revs = revs.filter(lambda r: getphase(repo, r) == draft)
2058 return subset & revs
2061 return subset & revs
2059
2062
2060
2063
2061 @predicate(b'public()', safe=True)
2064 @predicate(b'public()', safe=True)
2062 def public(repo, subset, x):
2065 def public(repo, subset, x):
2063 """Changeset in public phase."""
2066 """Changeset in public phase."""
2064 # i18n: "public" is a keyword
2067 # i18n: "public" is a keyword
2065 getargs(x, 0, 0, _(b"public takes no arguments"))
2068 getargs(x, 0, 0, _(b"public takes no arguments"))
2066 return _phase(repo, subset, phases.public)
2069 return _phase(repo, subset, phases.public)
2067
2070
2068
2071
2069 @predicate(b'remote([id [,path]])', safe=False)
2072 @predicate(b'remote([id [,path]])', safe=False)
2070 def remote(repo, subset, x):
2073 def remote(repo, subset, x):
2071 """Local revision that corresponds to the given identifier in a
2074 """Local revision that corresponds to the given identifier in a
2072 remote repository, if present. Here, the '.' identifier is a
2075 remote repository, if present. Here, the '.' identifier is a
2073 synonym for the current local branch.
2076 synonym for the current local branch.
2074 """
2077 """
2075
2078
2076 from . import hg # avoid start-up nasties
2079 from . import hg # avoid start-up nasties
2077
2080
2078 # i18n: "remote" is a keyword
2081 # i18n: "remote" is a keyword
2079 l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
2082 l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
2080
2083
2081 q = b'.'
2084 q = b'.'
2082 if len(l) > 0:
2085 if len(l) > 0:
2083 # i18n: "remote" is a keyword
2086 # i18n: "remote" is a keyword
2084 q = getstring(l[0], _(b"remote requires a string id"))
2087 q = getstring(l[0], _(b"remote requires a string id"))
2085 if q == b'.':
2088 if q == b'.':
2086 q = repo[b'.'].branch()
2089 q = repo[b'.'].branch()
2087
2090
2088 dest = b''
2091 dest = b''
2089 if len(l) > 1:
2092 if len(l) > 1:
2090 # i18n: "remote" is a keyword
2093 # i18n: "remote" is a keyword
2091 dest = getstring(l[1], _(b"remote requires a repository path"))
2094 dest = getstring(l[1], _(b"remote requires a repository path"))
2092 dest = repo.ui.expandpath(dest or b'default')
2095 dest = repo.ui.expandpath(dest or b'default')
2093 dest, branches = hg.parseurl(dest)
2096 dest, branches = hg.parseurl(dest)
2094
2097
2095 other = hg.peer(repo, {}, dest)
2098 other = hg.peer(repo, {}, dest)
2096 n = other.lookup(q)
2099 n = other.lookup(q)
2097 if n in repo:
2100 if n in repo:
2098 r = repo[n].rev()
2101 r = repo[n].rev()
2099 if r in subset:
2102 if r in subset:
2100 return baseset([r])
2103 return baseset([r])
2101 return baseset()
2104 return baseset()
2102
2105
2103
2106
2104 @predicate(b'removes(pattern)', safe=True, weight=30)
2107 @predicate(b'removes(pattern)', safe=True, weight=30)
2105 def removes(repo, subset, x):
2108 def removes(repo, subset, x):
2106 """Changesets which remove files matching pattern.
2109 """Changesets which remove files matching pattern.
2107
2110
2108 The pattern without explicit kind like ``glob:`` is expected to be
2111 The pattern without explicit kind like ``glob:`` is expected to be
2109 relative to the current directory and match against a file or a
2112 relative to the current directory and match against a file or a
2110 directory.
2113 directory.
2111 """
2114 """
2112 # i18n: "removes" is a keyword
2115 # i18n: "removes" is a keyword
2113 pat = getstring(x, _(b"removes requires a pattern"))
2116 pat = getstring(x, _(b"removes requires a pattern"))
2114 return checkstatus(repo, subset, pat, 'removed')
2117 return checkstatus(repo, subset, pat, 'removed')
2115
2118
2116
2119
2117 @predicate(b'rev(number)', safe=True)
2120 @predicate(b'rev(number)', safe=True)
2118 def rev(repo, subset, x):
2121 def rev(repo, subset, x):
2119 """Revision with the given numeric identifier."""
2122 """Revision with the given numeric identifier."""
2120 try:
2123 try:
2121 return _rev(repo, subset, x)
2124 return _rev(repo, subset, x)
2122 except error.RepoLookupError:
2125 except error.RepoLookupError:
2123 return baseset()
2126 return baseset()
2124
2127
2125
2128
2126 @predicate(b'_rev(number)', safe=True)
2129 @predicate(b'_rev(number)', safe=True)
2127 def _rev(repo, subset, x):
2130 def _rev(repo, subset, x):
2128 # internal version of "rev(x)" that raise error if "x" is invalid
2131 # internal version of "rev(x)" that raise error if "x" is invalid
2129 # i18n: "rev" is a keyword
2132 # i18n: "rev" is a keyword
2130 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2133 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2131 try:
2134 try:
2132 # i18n: "rev" is a keyword
2135 # i18n: "rev" is a keyword
2133 l = int(getstring(l[0], _(b"rev requires a number")))
2136 l = int(getstring(l[0], _(b"rev requires a number")))
2134 except (TypeError, ValueError):
2137 except (TypeError, ValueError):
2135 # i18n: "rev" is a keyword
2138 # i18n: "rev" is a keyword
2136 raise error.ParseError(_(b"rev expects a number"))
2139 raise error.ParseError(_(b"rev expects a number"))
2137 if l not in _virtualrevs:
2140 if l not in _virtualrevs:
2138 try:
2141 try:
2139 repo.changelog.node(l) # check that the rev exists
2142 repo.changelog.node(l) # check that the rev exists
2140 except IndexError:
2143 except IndexError:
2141 raise error.RepoLookupError(_(b"unknown revision '%d'") % l)
2144 raise error.RepoLookupError(_(b"unknown revision '%d'") % l)
2142 return subset & baseset([l])
2145 return subset & baseset([l])
2143
2146
2144
2147
2145 @predicate(b'revset(set)', safe=True, takeorder=True)
2148 @predicate(b'revset(set)', safe=True, takeorder=True)
2146 def revsetpredicate(repo, subset, x, order):
2149 def revsetpredicate(repo, subset, x, order):
2147 """Strictly interpret the content as a revset.
2150 """Strictly interpret the content as a revset.
2148
2151
2149 The content of this special predicate will be strictly interpreted as a
2152 The content of this special predicate will be strictly interpreted as a
2150 revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
2153 revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
2151 without possible ambiguity with a "id(0)" bookmark or tag.
2154 without possible ambiguity with a "id(0)" bookmark or tag.
2152 """
2155 """
2153 return getset(repo, subset, x, order)
2156 return getset(repo, subset, x, order)
2154
2157
2155
2158
2156 @predicate(b'matching(revision [, field])', safe=True)
2159 @predicate(b'matching(revision [, field])', safe=True)
2157 def matching(repo, subset, x):
2160 def matching(repo, subset, x):
2158 """Changesets in which a given set of fields match the set of fields in the
2161 """Changesets in which a given set of fields match the set of fields in the
2159 selected revision or set.
2162 selected revision or set.
2160
2163
2161 To match more than one field pass the list of fields to match separated
2164 To match more than one field pass the list of fields to match separated
2162 by spaces (e.g. ``author description``).
2165 by spaces (e.g. ``author description``).
2163
2166
2164 Valid fields are most regular revision fields and some special fields.
2167 Valid fields are most regular revision fields and some special fields.
2165
2168
2166 Regular revision fields are ``description``, ``author``, ``branch``,
2169 Regular revision fields are ``description``, ``author``, ``branch``,
2167 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
2170 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
2168 and ``diff``.
2171 and ``diff``.
2169 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
2172 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
2170 contents of the revision. Two revisions matching their ``diff`` will
2173 contents of the revision. Two revisions matching their ``diff`` will
2171 also match their ``files``.
2174 also match their ``files``.
2172
2175
2173 Special fields are ``summary`` and ``metadata``:
2176 Special fields are ``summary`` and ``metadata``:
2174 ``summary`` matches the first line of the description.
2177 ``summary`` matches the first line of the description.
2175 ``metadata`` is equivalent to matching ``description user date``
2178 ``metadata`` is equivalent to matching ``description user date``
2176 (i.e. it matches the main metadata fields).
2179 (i.e. it matches the main metadata fields).
2177
2180
2178 ``metadata`` is the default field which is used when no fields are
2181 ``metadata`` is the default field which is used when no fields are
2179 specified. You can match more than one field at a time.
2182 specified. You can match more than one field at a time.
2180 """
2183 """
2181 # i18n: "matching" is a keyword
2184 # i18n: "matching" is a keyword
2182 l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
2185 l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
2183
2186
2184 revs = getset(repo, fullreposet(repo), l[0])
2187 revs = getset(repo, fullreposet(repo), l[0])
2185
2188
2186 fieldlist = [b'metadata']
2189 fieldlist = [b'metadata']
2187 if len(l) > 1:
2190 if len(l) > 1:
2188 fieldlist = getstring(
2191 fieldlist = getstring(
2189 l[1],
2192 l[1],
2190 # i18n: "matching" is a keyword
2193 # i18n: "matching" is a keyword
2191 _(b"matching requires a string as its second argument"),
2194 _(b"matching requires a string as its second argument"),
2192 ).split()
2195 ).split()
2193
2196
2194 # Make sure that there are no repeated fields,
2197 # Make sure that there are no repeated fields,
2195 # expand the 'special' 'metadata' field type
2198 # expand the 'special' 'metadata' field type
2196 # and check the 'files' whenever we check the 'diff'
2199 # and check the 'files' whenever we check the 'diff'
2197 fields = []
2200 fields = []
2198 for field in fieldlist:
2201 for field in fieldlist:
2199 if field == b'metadata':
2202 if field == b'metadata':
2200 fields += [b'user', b'description', b'date']
2203 fields += [b'user', b'description', b'date']
2201 elif field == b'diff':
2204 elif field == b'diff':
2202 # a revision matching the diff must also match the files
2205 # a revision matching the diff must also match the files
2203 # since matching the diff is very costly, make sure to
2206 # since matching the diff is very costly, make sure to
2204 # also match the files first
2207 # also match the files first
2205 fields += [b'files', b'diff']
2208 fields += [b'files', b'diff']
2206 else:
2209 else:
2207 if field == b'author':
2210 if field == b'author':
2208 field = b'user'
2211 field = b'user'
2209 fields.append(field)
2212 fields.append(field)
2210 fields = set(fields)
2213 fields = set(fields)
2211 if b'summary' in fields and b'description' in fields:
2214 if b'summary' in fields and b'description' in fields:
2212 # If a revision matches its description it also matches its summary
2215 # If a revision matches its description it also matches its summary
2213 fields.discard(b'summary')
2216 fields.discard(b'summary')
2214
2217
2215 # We may want to match more than one field
2218 # We may want to match more than one field
2216 # Not all fields take the same amount of time to be matched
2219 # Not all fields take the same amount of time to be matched
2217 # Sort the selected fields in order of increasing matching cost
2220 # Sort the selected fields in order of increasing matching cost
2218 fieldorder = [
2221 fieldorder = [
2219 b'phase',
2222 b'phase',
2220 b'parents',
2223 b'parents',
2221 b'user',
2224 b'user',
2222 b'date',
2225 b'date',
2223 b'branch',
2226 b'branch',
2224 b'summary',
2227 b'summary',
2225 b'files',
2228 b'files',
2226 b'description',
2229 b'description',
2227 b'substate',
2230 b'substate',
2228 b'diff',
2231 b'diff',
2229 ]
2232 ]
2230
2233
2231 def fieldkeyfunc(f):
2234 def fieldkeyfunc(f):
2232 try:
2235 try:
2233 return fieldorder.index(f)
2236 return fieldorder.index(f)
2234 except ValueError:
2237 except ValueError:
2235 # assume an unknown field is very costly
2238 # assume an unknown field is very costly
2236 return len(fieldorder)
2239 return len(fieldorder)
2237
2240
2238 fields = list(fields)
2241 fields = list(fields)
2239 fields.sort(key=fieldkeyfunc)
2242 fields.sort(key=fieldkeyfunc)
2240
2243
2241 # Each field will be matched with its own "getfield" function
2244 # Each field will be matched with its own "getfield" function
2242 # which will be added to the getfieldfuncs array of functions
2245 # which will be added to the getfieldfuncs array of functions
2243 getfieldfuncs = []
2246 getfieldfuncs = []
2244 _funcs = {
2247 _funcs = {
2245 b'user': lambda r: repo[r].user(),
2248 b'user': lambda r: repo[r].user(),
2246 b'branch': lambda r: repo[r].branch(),
2249 b'branch': lambda r: repo[r].branch(),
2247 b'date': lambda r: repo[r].date(),
2250 b'date': lambda r: repo[r].date(),
2248 b'description': lambda r: repo[r].description(),
2251 b'description': lambda r: repo[r].description(),
2249 b'files': lambda r: repo[r].files(),
2252 b'files': lambda r: repo[r].files(),
2250 b'parents': lambda r: repo[r].parents(),
2253 b'parents': lambda r: repo[r].parents(),
2251 b'phase': lambda r: repo[r].phase(),
2254 b'phase': lambda r: repo[r].phase(),
2252 b'substate': lambda r: repo[r].substate,
2255 b'substate': lambda r: repo[r].substate,
2253 b'summary': lambda r: repo[r].description().splitlines()[0],
2256 b'summary': lambda r: repo[r].description().splitlines()[0],
2254 b'diff': lambda r: list(
2257 b'diff': lambda r: list(
2255 repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
2258 repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
2256 ),
2259 ),
2257 }
2260 }
2258 for info in fields:
2261 for info in fields:
2259 getfield = _funcs.get(info, None)
2262 getfield = _funcs.get(info, None)
2260 if getfield is None:
2263 if getfield is None:
2261 raise error.ParseError(
2264 raise error.ParseError(
2262 # i18n: "matching" is a keyword
2265 # i18n: "matching" is a keyword
2263 _(b"unexpected field name passed to matching: %s")
2266 _(b"unexpected field name passed to matching: %s")
2264 % info
2267 % info
2265 )
2268 )
2266 getfieldfuncs.append(getfield)
2269 getfieldfuncs.append(getfield)
2267 # convert the getfield array of functions into a "getinfo" function
2270 # convert the getfield array of functions into a "getinfo" function
2268 # which returns an array of field values (or a single value if there
2271 # which returns an array of field values (or a single value if there
2269 # is only one field to match)
2272 # is only one field to match)
2270 getinfo = lambda r: [f(r) for f in getfieldfuncs]
2273 getinfo = lambda r: [f(r) for f in getfieldfuncs]
2271
2274
2272 def matches(x):
2275 def matches(x):
2273 for rev in revs:
2276 for rev in revs:
2274 target = getinfo(rev)
2277 target = getinfo(rev)
2275 match = True
2278 match = True
2276 for n, f in enumerate(getfieldfuncs):
2279 for n, f in enumerate(getfieldfuncs):
2277 if target[n] != f(x):
2280 if target[n] != f(x):
2278 match = False
2281 match = False
2279 if match:
2282 if match:
2280 return True
2283 return True
2281 return False
2284 return False
2282
2285
2283 return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
2286 return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
2284
2287
2285
2288
2286 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
2289 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
2287 def reverse(repo, subset, x, order):
2290 def reverse(repo, subset, x, order):
2288 """Reverse order of set."""
2291 """Reverse order of set."""
2289 l = getset(repo, subset, x, order)
2292 l = getset(repo, subset, x, order)
2290 if order == defineorder:
2293 if order == defineorder:
2291 l.reverse()
2294 l.reverse()
2292 return l
2295 return l
2293
2296
2294
2297
2295 @predicate(b'roots(set)', safe=True)
2298 @predicate(b'roots(set)', safe=True)
2296 def roots(repo, subset, x):
2299 def roots(repo, subset, x):
2297 """Changesets in set with no parent changeset in set."""
2300 """Changesets in set with no parent changeset in set."""
2298 s = getset(repo, fullreposet(repo), x)
2301 s = getset(repo, fullreposet(repo), x)
2299 parents = repo.changelog.parentrevs
2302 parents = repo.changelog.parentrevs
2300
2303
2301 def filter(r):
2304 def filter(r):
2302 for p in parents(r):
2305 for p in parents(r):
2303 if 0 <= p and p in s:
2306 if 0 <= p and p in s:
2304 return False
2307 return False
2305 return True
2308 return True
2306
2309
2307 return subset & s.filter(filter, condrepr=b'<roots>')
2310 return subset & s.filter(filter, condrepr=b'<roots>')
2308
2311
2309
2312
2310 _sortkeyfuncs = {
2313 _sortkeyfuncs = {
2311 b'rev': scmutil.intrev,
2314 b'rev': scmutil.intrev,
2312 b'branch': lambda c: c.branch(),
2315 b'branch': lambda c: c.branch(),
2313 b'desc': lambda c: c.description(),
2316 b'desc': lambda c: c.description(),
2314 b'user': lambda c: c.user(),
2317 b'user': lambda c: c.user(),
2315 b'author': lambda c: c.user(),
2318 b'author': lambda c: c.user(),
2316 b'date': lambda c: c.date()[0],
2319 b'date': lambda c: c.date()[0],
2317 b'node': scmutil.binnode,
2320 b'node': scmutil.binnode,
2318 }
2321 }
2319
2322
2320
2323
2321 def _getsortargs(x):
2324 def _getsortargs(x):
2322 """Parse sort options into (set, [(key, reverse)], opts)"""
2325 """Parse sort options into (set, [(key, reverse)], opts)"""
2323 args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
2326 args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
2324 if b'set' not in args:
2327 if b'set' not in args:
2325 # i18n: "sort" is a keyword
2328 # i18n: "sort" is a keyword
2326 raise error.ParseError(_(b'sort requires one or two arguments'))
2329 raise error.ParseError(_(b'sort requires one or two arguments'))
2327 keys = b"rev"
2330 keys = b"rev"
2328 if b'keys' in args:
2331 if b'keys' in args:
2329 # i18n: "sort" is a keyword
2332 # i18n: "sort" is a keyword
2330 keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
2333 keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
2331
2334
2332 keyflags = []
2335 keyflags = []
2333 for k in keys.split():
2336 for k in keys.split():
2334 fk = k
2337 fk = k
2335 reverse = k.startswith(b'-')
2338 reverse = k.startswith(b'-')
2336 if reverse:
2339 if reverse:
2337 k = k[1:]
2340 k = k[1:]
2338 if k not in _sortkeyfuncs and k != b'topo':
2341 if k not in _sortkeyfuncs and k != b'topo':
2339 raise error.ParseError(
2342 raise error.ParseError(
2340 _(b"unknown sort key %r") % pycompat.bytestr(fk)
2343 _(b"unknown sort key %r") % pycompat.bytestr(fk)
2341 )
2344 )
2342 keyflags.append((k, reverse))
2345 keyflags.append((k, reverse))
2343
2346
2344 if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
2347 if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
2345 # i18n: "topo" is a keyword
2348 # i18n: "topo" is a keyword
2346 raise error.ParseError(
2349 raise error.ParseError(
2347 _(b'topo sort order cannot be combined with other sort keys')
2350 _(b'topo sort order cannot be combined with other sort keys')
2348 )
2351 )
2349
2352
2350 opts = {}
2353 opts = {}
2351 if b'topo.firstbranch' in args:
2354 if b'topo.firstbranch' in args:
2352 if any(k == b'topo' for k, reverse in keyflags):
2355 if any(k == b'topo' for k, reverse in keyflags):
2353 opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
2356 opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
2354 else:
2357 else:
2355 # i18n: "topo" and "topo.firstbranch" are keywords
2358 # i18n: "topo" and "topo.firstbranch" are keywords
2356 raise error.ParseError(
2359 raise error.ParseError(
2357 _(
2360 _(
2358 b'topo.firstbranch can only be used '
2361 b'topo.firstbranch can only be used '
2359 b'when using the topo sort key'
2362 b'when using the topo sort key'
2360 )
2363 )
2361 )
2364 )
2362
2365
2363 return args[b'set'], keyflags, opts
2366 return args[b'set'], keyflags, opts
2364
2367
2365
2368
2366 @predicate(
2369 @predicate(
2367 b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
2370 b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
2368 )
2371 )
2369 def sort(repo, subset, x, order):
2372 def sort(repo, subset, x, order):
2370 """Sort set by keys. The default sort order is ascending, specify a key
2373 """Sort set by keys. The default sort order is ascending, specify a key
2371 as ``-key`` to sort in descending order.
2374 as ``-key`` to sort in descending order.
2372
2375
2373 The keys can be:
2376 The keys can be:
2374
2377
2375 - ``rev`` for the revision number,
2378 - ``rev`` for the revision number,
2376 - ``branch`` for the branch name,
2379 - ``branch`` for the branch name,
2377 - ``desc`` for the commit message (description),
2380 - ``desc`` for the commit message (description),
2378 - ``user`` for user name (``author`` can be used as an alias),
2381 - ``user`` for user name (``author`` can be used as an alias),
2379 - ``date`` for the commit date
2382 - ``date`` for the commit date
2380 - ``topo`` for a reverse topographical sort
2383 - ``topo`` for a reverse topographical sort
2381 - ``node`` the nodeid of the revision
2384 - ``node`` the nodeid of the revision
2382
2385
2383 The ``topo`` sort order cannot be combined with other sort keys. This sort
2386 The ``topo`` sort order cannot be combined with other sort keys. This sort
2384 takes one optional argument, ``topo.firstbranch``, which takes a revset that
2387 takes one optional argument, ``topo.firstbranch``, which takes a revset that
2385 specifies what topographical branches to prioritize in the sort.
2388 specifies what topographical branches to prioritize in the sort.
2386
2389
2387 """
2390 """
2388 s, keyflags, opts = _getsortargs(x)
2391 s, keyflags, opts = _getsortargs(x)
2389 revs = getset(repo, subset, s, order)
2392 revs = getset(repo, subset, s, order)
2390
2393
2391 if not keyflags or order != defineorder:
2394 if not keyflags or order != defineorder:
2392 return revs
2395 return revs
2393 if len(keyflags) == 1 and keyflags[0][0] == b"rev":
2396 if len(keyflags) == 1 and keyflags[0][0] == b"rev":
2394 revs.sort(reverse=keyflags[0][1])
2397 revs.sort(reverse=keyflags[0][1])
2395 return revs
2398 return revs
2396 elif keyflags[0][0] == b"topo":
2399 elif keyflags[0][0] == b"topo":
2397 firstbranch = ()
2400 firstbranch = ()
2398 if b'topo.firstbranch' in opts:
2401 if b'topo.firstbranch' in opts:
2399 firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
2402 firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
2400 revs = baseset(
2403 revs = baseset(
2401 dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
2404 dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
2402 istopo=True,
2405 istopo=True,
2403 )
2406 )
2404 if keyflags[0][1]:
2407 if keyflags[0][1]:
2405 revs.reverse()
2408 revs.reverse()
2406 return revs
2409 return revs
2407
2410
2408 # sort() is guaranteed to be stable
2411 # sort() is guaranteed to be stable
2409 ctxs = [repo[r] for r in revs]
2412 ctxs = [repo[r] for r in revs]
2410 for k, reverse in reversed(keyflags):
2413 for k, reverse in reversed(keyflags):
2411 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
2414 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
2412 return baseset([c.rev() for c in ctxs])
2415 return baseset([c.rev() for c in ctxs])
2413
2416
2414
2417
2415 @predicate(b'subrepo([pattern])')
2418 @predicate(b'subrepo([pattern])')
2416 def subrepo(repo, subset, x):
2419 def subrepo(repo, subset, x):
2417 """Changesets that add, modify or remove the given subrepo. If no subrepo
2420 """Changesets that add, modify or remove the given subrepo. If no subrepo
2418 pattern is named, any subrepo changes are returned.
2421 pattern is named, any subrepo changes are returned.
2419 """
2422 """
2420 # i18n: "subrepo" is a keyword
2423 # i18n: "subrepo" is a keyword
2421 args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
2424 args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
2422 pat = None
2425 pat = None
2423 if len(args) != 0:
2426 if len(args) != 0:
2424 pat = getstring(args[0], _(b"subrepo requires a pattern"))
2427 pat = getstring(args[0], _(b"subrepo requires a pattern"))
2425
2428
2426 m = matchmod.exact([b'.hgsubstate'])
2429 m = matchmod.exact([b'.hgsubstate'])
2427
2430
2428 def submatches(names):
2431 def submatches(names):
2429 k, p, m = stringutil.stringmatcher(pat)
2432 k, p, m = stringutil.stringmatcher(pat)
2430 for name in names:
2433 for name in names:
2431 if m(name):
2434 if m(name):
2432 yield name
2435 yield name
2433
2436
2434 def matches(x):
2437 def matches(x):
2435 c = repo[x]
2438 c = repo[x]
2436 s = repo.status(c.p1().node(), c.node(), match=m)
2439 s = repo.status(c.p1().node(), c.node(), match=m)
2437
2440
2438 if pat is None:
2441 if pat is None:
2439 return s.added or s.modified or s.removed
2442 return s.added or s.modified or s.removed
2440
2443
2441 if s.added:
2444 if s.added:
2442 return any(submatches(c.substate.keys()))
2445 return any(submatches(c.substate.keys()))
2443
2446
2444 if s.modified:
2447 if s.modified:
2445 subs = set(c.p1().substate.keys())
2448 subs = set(c.p1().substate.keys())
2446 subs.update(c.substate.keys())
2449 subs.update(c.substate.keys())
2447
2450
2448 for path in submatches(subs):
2451 for path in submatches(subs):
2449 if c.p1().substate.get(path) != c.substate.get(path):
2452 if c.p1().substate.get(path) != c.substate.get(path):
2450 return True
2453 return True
2451
2454
2452 if s.removed:
2455 if s.removed:
2453 return any(submatches(c.p1().substate.keys()))
2456 return any(submatches(c.p1().substate.keys()))
2454
2457
2455 return False
2458 return False
2456
2459
2457 return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
2460 return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
2458
2461
2459
2462
2460 def _mapbynodefunc(repo, s, f):
2463 def _mapbynodefunc(repo, s, f):
2461 """(repo, smartset, [node] -> [node]) -> smartset
2464 """(repo, smartset, [node] -> [node]) -> smartset
2462
2465
2463 Helper method to map a smartset to another smartset given a function only
2466 Helper method to map a smartset to another smartset given a function only
2464 talking about nodes. Handles converting between rev numbers and nodes, and
2467 talking about nodes. Handles converting between rev numbers and nodes, and
2465 filtering.
2468 filtering.
2466 """
2469 """
2467 cl = repo.unfiltered().changelog
2470 cl = repo.unfiltered().changelog
2468 torev = cl.index.get_rev
2471 torev = cl.index.get_rev
2469 tonode = cl.node
2472 tonode = cl.node
2470 result = {torev(n) for n in f(tonode(r) for r in s)}
2473 result = {torev(n) for n in f(tonode(r) for r in s)}
2471 result.discard(None)
2474 result.discard(None)
2472 return smartset.baseset(result - repo.changelog.filteredrevs)
2475 return smartset.baseset(result - repo.changelog.filteredrevs)
2473
2476
2474
2477
2475 @predicate(b'successors(set)', safe=True)
2478 @predicate(b'successors(set)', safe=True)
2476 def successors(repo, subset, x):
2479 def successors(repo, subset, x):
2477 """All successors for set, including the given set themselves.
2480 """All successors for set, including the given set themselves.
2478 (EXPERIMENTAL)"""
2481 (EXPERIMENTAL)"""
2479 s = getset(repo, fullreposet(repo), x)
2482 s = getset(repo, fullreposet(repo), x)
2480 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2483 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2481 d = _mapbynodefunc(repo, s, f)
2484 d = _mapbynodefunc(repo, s, f)
2482 return subset & d
2485 return subset & d
2483
2486
2484
2487
2485 def _substringmatcher(pattern, casesensitive=True):
2488 def _substringmatcher(pattern, casesensitive=True):
2486 kind, pattern, matcher = stringutil.stringmatcher(
2489 kind, pattern, matcher = stringutil.stringmatcher(
2487 pattern, casesensitive=casesensitive
2490 pattern, casesensitive=casesensitive
2488 )
2491 )
2489 if kind == b'literal':
2492 if kind == b'literal':
2490 if not casesensitive:
2493 if not casesensitive:
2491 pattern = encoding.lower(pattern)
2494 pattern = encoding.lower(pattern)
2492 matcher = lambda s: pattern in encoding.lower(s)
2495 matcher = lambda s: pattern in encoding.lower(s)
2493 else:
2496 else:
2494 matcher = lambda s: pattern in s
2497 matcher = lambda s: pattern in s
2495 return kind, pattern, matcher
2498 return kind, pattern, matcher
2496
2499
2497
2500
2498 @predicate(b'tag([name])', safe=True)
2501 @predicate(b'tag([name])', safe=True)
2499 def tag(repo, subset, x):
2502 def tag(repo, subset, x):
2500 """The specified tag by name, or all tagged revisions if no name is given.
2503 """The specified tag by name, or all tagged revisions if no name is given.
2501
2504
2502 Pattern matching is supported for `name`. See
2505 Pattern matching is supported for `name`. See
2503 :hg:`help revisions.patterns`.
2506 :hg:`help revisions.patterns`.
2504 """
2507 """
2505 # i18n: "tag" is a keyword
2508 # i18n: "tag" is a keyword
2506 args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
2509 args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
2507 cl = repo.changelog
2510 cl = repo.changelog
2508 if args:
2511 if args:
2509 pattern = getstring(
2512 pattern = getstring(
2510 args[0],
2513 args[0],
2511 # i18n: "tag" is a keyword
2514 # i18n: "tag" is a keyword
2512 _(b'the argument to tag must be a string'),
2515 _(b'the argument to tag must be a string'),
2513 )
2516 )
2514 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2517 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2515 if kind == b'literal':
2518 if kind == b'literal':
2516 # avoid resolving all tags
2519 # avoid resolving all tags
2517 tn = repo._tagscache.tags.get(pattern, None)
2520 tn = repo._tagscache.tags.get(pattern, None)
2518 if tn is None:
2521 if tn is None:
2519 raise error.RepoLookupError(
2522 raise error.RepoLookupError(
2520 _(b"tag '%s' does not exist") % pattern
2523 _(b"tag '%s' does not exist") % pattern
2521 )
2524 )
2522 s = {repo[tn].rev()}
2525 s = {repo[tn].rev()}
2523 else:
2526 else:
2524 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2527 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2525 else:
2528 else:
2526 s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
2529 s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
2527 return subset & s
2530 return subset & s
2528
2531
2529
2532
2530 @predicate(b'tagged', safe=True)
2533 @predicate(b'tagged', safe=True)
2531 def tagged(repo, subset, x):
2534 def tagged(repo, subset, x):
2532 return tag(repo, subset, x)
2535 return tag(repo, subset, x)
2533
2536
2534
2537
2535 @predicate(b'orphan()', safe=True)
2538 @predicate(b'orphan()', safe=True)
2536 def orphan(repo, subset, x):
2539 def orphan(repo, subset, x):
2537 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)"""
2540 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)"""
2538 # i18n: "orphan" is a keyword
2541 # i18n: "orphan" is a keyword
2539 getargs(x, 0, 0, _(b"orphan takes no arguments"))
2542 getargs(x, 0, 0, _(b"orphan takes no arguments"))
2540 orphan = obsmod.getrevs(repo, b'orphan')
2543 orphan = obsmod.getrevs(repo, b'orphan')
2541 return subset & orphan
2544 return subset & orphan
2542
2545
2543
2546
2544 @predicate(b'unstable()', safe=True)
2547 @predicate(b'unstable()', safe=True)
2545 def unstable(repo, subset, x):
2548 def unstable(repo, subset, x):
2546 """Changesets with instabilities. (EXPERIMENTAL)"""
2549 """Changesets with instabilities. (EXPERIMENTAL)"""
2547 # i18n: "unstable" is a keyword
2550 # i18n: "unstable" is a keyword
2548 getargs(x, 0, 0, b'unstable takes no arguments')
2551 getargs(x, 0, 0, b'unstable takes no arguments')
2549 _unstable = set()
2552 _unstable = set()
2550 _unstable.update(obsmod.getrevs(repo, b'orphan'))
2553 _unstable.update(obsmod.getrevs(repo, b'orphan'))
2551 _unstable.update(obsmod.getrevs(repo, b'phasedivergent'))
2554 _unstable.update(obsmod.getrevs(repo, b'phasedivergent'))
2552 _unstable.update(obsmod.getrevs(repo, b'contentdivergent'))
2555 _unstable.update(obsmod.getrevs(repo, b'contentdivergent'))
2553 return subset & baseset(_unstable)
2556 return subset & baseset(_unstable)
2554
2557
2555
2558
2556 @predicate(b'user(string)', safe=True, weight=10)
2559 @predicate(b'user(string)', safe=True, weight=10)
2557 def user(repo, subset, x):
2560 def user(repo, subset, x):
2558 """User name contains string. The match is case-insensitive.
2561 """User name contains string. The match is case-insensitive.
2559
2562
2560 Pattern matching is supported for `string`. See
2563 Pattern matching is supported for `string`. See
2561 :hg:`help revisions.patterns`.
2564 :hg:`help revisions.patterns`.
2562 """
2565 """
2563 return author(repo, subset, x)
2566 return author(repo, subset, x)
2564
2567
2565
2568
2566 @predicate(b'wdir()', safe=True, weight=0)
2569 @predicate(b'wdir()', safe=True, weight=0)
2567 def wdir(repo, subset, x):
2570 def wdir(repo, subset, x):
2568 """Working directory. (EXPERIMENTAL)"""
2571 """Working directory. (EXPERIMENTAL)"""
2569 # i18n: "wdir" is a keyword
2572 # i18n: "wdir" is a keyword
2570 getargs(x, 0, 0, _(b"wdir takes no arguments"))
2573 getargs(x, 0, 0, _(b"wdir takes no arguments"))
2571 if wdirrev in subset or isinstance(subset, fullreposet):
2574 if wdirrev in subset or isinstance(subset, fullreposet):
2572 return baseset([wdirrev])
2575 return baseset([wdirrev])
2573 return baseset()
2576 return baseset()
2574
2577
2575
2578
2576 def _orderedlist(repo, subset, x):
2579 def _orderedlist(repo, subset, x):
2577 s = getstring(x, b"internal error")
2580 s = getstring(x, b"internal error")
2578 if not s:
2581 if not s:
2579 return baseset()
2582 return baseset()
2580 # remove duplicates here. it's difficult for caller to deduplicate sets
2583 # remove duplicates here. it's difficult for caller to deduplicate sets
2581 # because different symbols can point to the same rev.
2584 # because different symbols can point to the same rev.
2582 cl = repo.changelog
2585 cl = repo.changelog
2583 ls = []
2586 ls = []
2584 seen = set()
2587 seen = set()
2585 for t in s.split(b'\0'):
2588 for t in s.split(b'\0'):
2586 try:
2589 try:
2587 # fast path for integer revision
2590 # fast path for integer revision
2588 r = int(t)
2591 r = int(t)
2589 if (b'%d' % r) != t or r not in cl:
2592 if (b'%d' % r) != t or r not in cl:
2590 raise ValueError
2593 raise ValueError
2591 revs = [r]
2594 revs = [r]
2592 except ValueError:
2595 except ValueError:
2593 revs = stringset(repo, subset, t, defineorder)
2596 revs = stringset(repo, subset, t, defineorder)
2594
2597
2595 for r in revs:
2598 for r in revs:
2596 if r in seen:
2599 if r in seen:
2597 continue
2600 continue
2598 if (
2601 if (
2599 r in subset
2602 r in subset
2600 or r in _virtualrevs
2603 or r in _virtualrevs
2601 and isinstance(subset, fullreposet)
2604 and isinstance(subset, fullreposet)
2602 ):
2605 ):
2603 ls.append(r)
2606 ls.append(r)
2604 seen.add(r)
2607 seen.add(r)
2605 return baseset(ls)
2608 return baseset(ls)
2606
2609
2607
2610
2608 # for internal use
2611 # for internal use
2609 @predicate(b'_list', safe=True, takeorder=True)
2612 @predicate(b'_list', safe=True, takeorder=True)
2610 def _list(repo, subset, x, order):
2613 def _list(repo, subset, x, order):
2611 if order == followorder:
2614 if order == followorder:
2612 # slow path to take the subset order
2615 # slow path to take the subset order
2613 return subset & _orderedlist(repo, fullreposet(repo), x)
2616 return subset & _orderedlist(repo, fullreposet(repo), x)
2614 else:
2617 else:
2615 return _orderedlist(repo, subset, x)
2618 return _orderedlist(repo, subset, x)
2616
2619
2617
2620
2618 def _orderedintlist(repo, subset, x):
2621 def _orderedintlist(repo, subset, x):
2619 s = getstring(x, b"internal error")
2622 s = getstring(x, b"internal error")
2620 if not s:
2623 if not s:
2621 return baseset()
2624 return baseset()
2622 ls = [int(r) for r in s.split(b'\0')]
2625 ls = [int(r) for r in s.split(b'\0')]
2623 s = subset
2626 s = subset
2624 return baseset([r for r in ls if r in s])
2627 return baseset([r for r in ls if r in s])
2625
2628
2626
2629
2627 # for internal use
2630 # for internal use
2628 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
2631 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
2629 def _intlist(repo, subset, x, order):
2632 def _intlist(repo, subset, x, order):
2630 if order == followorder:
2633 if order == followorder:
2631 # slow path to take the subset order
2634 # slow path to take the subset order
2632 return subset & _orderedintlist(repo, fullreposet(repo), x)
2635 return subset & _orderedintlist(repo, fullreposet(repo), x)
2633 else:
2636 else:
2634 return _orderedintlist(repo, subset, x)
2637 return _orderedintlist(repo, subset, x)
2635
2638
2636
2639
2637 def _orderedhexlist(repo, subset, x):
2640 def _orderedhexlist(repo, subset, x):
2638 s = getstring(x, b"internal error")
2641 s = getstring(x, b"internal error")
2639 if not s:
2642 if not s:
2640 return baseset()
2643 return baseset()
2641 cl = repo.changelog
2644 cl = repo.changelog
2642 ls = [cl.rev(bin(r)) for r in s.split(b'\0')]
2645 ls = [cl.rev(bin(r)) for r in s.split(b'\0')]
2643 s = subset
2646 s = subset
2644 return baseset([r for r in ls if r in s])
2647 return baseset([r for r in ls if r in s])
2645
2648
2646
2649
2647 # for internal use
2650 # for internal use
2648 @predicate(b'_hexlist', safe=True, takeorder=True)
2651 @predicate(b'_hexlist', safe=True, takeorder=True)
2649 def _hexlist(repo, subset, x, order):
2652 def _hexlist(repo, subset, x, order):
2650 if order == followorder:
2653 if order == followorder:
2651 # slow path to take the subset order
2654 # slow path to take the subset order
2652 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2655 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2653 else:
2656 else:
2654 return _orderedhexlist(repo, subset, x)
2657 return _orderedhexlist(repo, subset, x)
2655
2658
2656
2659
2657 methods = {
2660 methods = {
2658 b"range": rangeset,
2661 b"range": rangeset,
2659 b"rangeall": rangeall,
2662 b"rangeall": rangeall,
2660 b"rangepre": rangepre,
2663 b"rangepre": rangepre,
2661 b"rangepost": rangepost,
2664 b"rangepost": rangepost,
2662 b"dagrange": dagrange,
2665 b"dagrange": dagrange,
2663 b"string": stringset,
2666 b"string": stringset,
2664 b"symbol": stringset,
2667 b"symbol": stringset,
2665 b"and": andset,
2668 b"and": andset,
2666 b"andsmally": andsmallyset,
2669 b"andsmally": andsmallyset,
2667 b"or": orset,
2670 b"or": orset,
2668 b"not": notset,
2671 b"not": notset,
2669 b"difference": differenceset,
2672 b"difference": differenceset,
2670 b"relation": relationset,
2673 b"relation": relationset,
2671 b"relsubscript": relsubscriptset,
2674 b"relsubscript": relsubscriptset,
2672 b"subscript": subscriptset,
2675 b"subscript": subscriptset,
2673 b"list": listset,
2676 b"list": listset,
2674 b"keyvalue": keyvaluepair,
2677 b"keyvalue": keyvaluepair,
2675 b"func": func,
2678 b"func": func,
2676 b"ancestor": ancestorspec,
2679 b"ancestor": ancestorspec,
2677 b"parent": parentspec,
2680 b"parent": parentspec,
2678 b"parentpost": parentpost,
2681 b"parentpost": parentpost,
2679 b"smartset": rawsmartset,
2682 b"smartset": rawsmartset,
2680 }
2683 }
2681
2684
2682 relations = {
2685 relations = {
2683 b"g": generationsrel,
2686 b"g": generationsrel,
2684 b"generations": generationsrel,
2687 b"generations": generationsrel,
2685 }
2688 }
2686
2689
2687 subscriptrelations = {
2690 subscriptrelations = {
2688 b"g": generationssubrel,
2691 b"g": generationssubrel,
2689 b"generations": generationssubrel,
2692 b"generations": generationssubrel,
2690 }
2693 }
2691
2694
2692
2695
2693 def lookupfn(repo):
2696 def lookupfn(repo):
2694 def fn(symbol):
2697 def fn(symbol):
2695 try:
2698 try:
2696 return scmutil.isrevsymbol(repo, symbol)
2699 return scmutil.isrevsymbol(repo, symbol)
2697 except error.AmbiguousPrefixLookupError:
2700 except error.AmbiguousPrefixLookupError:
2698 raise error.InputError(
2701 raise error.InputError(
2699 b'ambiguous revision identifier: %s' % symbol
2702 b'ambiguous revision identifier: %s' % symbol
2700 )
2703 )
2701
2704
2702 return fn
2705 return fn
2703
2706
2704
2707
2705 def match(ui, spec, lookup=None):
2708 def match(ui, spec, lookup=None):
2706 """Create a matcher for a single revision spec"""
2709 """Create a matcher for a single revision spec"""
2707 return matchany(ui, [spec], lookup=lookup)
2710 return matchany(ui, [spec], lookup=lookup)
2708
2711
2709
2712
2710 def matchany(ui, specs, lookup=None, localalias=None):
2713 def matchany(ui, specs, lookup=None, localalias=None):
2711 """Create a matcher that will include any revisions matching one of the
2714 """Create a matcher that will include any revisions matching one of the
2712 given specs
2715 given specs
2713
2716
2714 If lookup function is not None, the parser will first attempt to handle
2717 If lookup function is not None, the parser will first attempt to handle
2715 old-style ranges, which may contain operator characters.
2718 old-style ranges, which may contain operator characters.
2716
2719
2717 If localalias is not None, it is a dict {name: definitionstring}. It takes
2720 If localalias is not None, it is a dict {name: definitionstring}. It takes
2718 precedence over [revsetalias] config section.
2721 precedence over [revsetalias] config section.
2719 """
2722 """
2720 if not specs:
2723 if not specs:
2721
2724
2722 def mfunc(repo, subset=None):
2725 def mfunc(repo, subset=None):
2723 return baseset()
2726 return baseset()
2724
2727
2725 return mfunc
2728 return mfunc
2726 if not all(specs):
2729 if not all(specs):
2727 raise error.ParseError(_(b"empty query"))
2730 raise error.ParseError(_(b"empty query"))
2728 if len(specs) == 1:
2731 if len(specs) == 1:
2729 tree = revsetlang.parse(specs[0], lookup)
2732 tree = revsetlang.parse(specs[0], lookup)
2730 else:
2733 else:
2731 tree = (
2734 tree = (
2732 b'or',
2735 b'or',
2733 (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
2736 (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
2734 )
2737 )
2735
2738
2736 aliases = []
2739 aliases = []
2737 warn = None
2740 warn = None
2738 if ui:
2741 if ui:
2739 aliases.extend(ui.configitems(b'revsetalias'))
2742 aliases.extend(ui.configitems(b'revsetalias'))
2740 warn = ui.warn
2743 warn = ui.warn
2741 if localalias:
2744 if localalias:
2742 aliases.extend(localalias.items())
2745 aliases.extend(localalias.items())
2743 if aliases:
2746 if aliases:
2744 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2747 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2745 tree = revsetlang.foldconcat(tree)
2748 tree = revsetlang.foldconcat(tree)
2746 tree = revsetlang.analyze(tree)
2749 tree = revsetlang.analyze(tree)
2747 tree = revsetlang.optimize(tree)
2750 tree = revsetlang.optimize(tree)
2748 return makematcher(tree)
2751 return makematcher(tree)
2749
2752
2750
2753
2751 def makematcher(tree):
2754 def makematcher(tree):
2752 """Create a matcher from an evaluatable tree"""
2755 """Create a matcher from an evaluatable tree"""
2753
2756
2754 def mfunc(repo, subset=None, order=None):
2757 def mfunc(repo, subset=None, order=None):
2755 if order is None:
2758 if order is None:
2756 if subset is None:
2759 if subset is None:
2757 order = defineorder # 'x'
2760 order = defineorder # 'x'
2758 else:
2761 else:
2759 order = followorder # 'subset & x'
2762 order = followorder # 'subset & x'
2760 if subset is None:
2763 if subset is None:
2761 subset = fullreposet(repo)
2764 subset = fullreposet(repo)
2762 return getset(repo, subset, tree, order)
2765 return getset(repo, subset, tree, order)
2763
2766
2764 return mfunc
2767 return mfunc
2765
2768
2766
2769
2767 def loadpredicate(ui, extname, registrarobj):
2770 def loadpredicate(ui, extname, registrarobj):
2768 """Load revset predicates from specified registrarobj"""
2771 """Load revset predicates from specified registrarobj"""
2769 for name, func in pycompat.iteritems(registrarobj._table):
2772 for name, func in pycompat.iteritems(registrarobj._table):
2770 symbols[name] = func
2773 symbols[name] = func
2771 if func._safe:
2774 if func._safe:
2772 safesymbols.add(name)
2775 safesymbols.add(name)
2773
2776
2774
2777
2775 # load built-in predicates explicitly to setup safesymbols
2778 # load built-in predicates explicitly to setup safesymbols
2776 loadpredicate(None, None, predicate)
2779 loadpredicate(None, None, predicate)
2777
2780
2778 # tell hggettext to extract docstrings from these functions:
2781 # tell hggettext to extract docstrings from these functions:
2779 i18nfunctions = symbols.values()
2782 i18nfunctions = symbols.values()
@@ -1,728 +1,725 b''
1 # sshpeer.py - ssh repository proxy class for mercurial
1 # sshpeer.py - ssh repository proxy class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 re
10 import re
11 import uuid
11 import uuid
12
12
13 from .i18n import _
13 from .i18n import _
14 from .pycompat import getattr
14 from .pycompat import getattr
15 from . import (
15 from . import (
16 error,
16 error,
17 pycompat,
17 pycompat,
18 util,
18 util,
19 wireprotoserver,
19 wireprotoserver,
20 wireprototypes,
20 wireprototypes,
21 wireprotov1peer,
21 wireprotov1peer,
22 wireprotov1server,
22 wireprotov1server,
23 )
23 )
24 from .utils import (
24 from .utils import (
25 procutil,
25 procutil,
26 stringutil,
26 stringutil,
27 )
27 )
28
28
29
29
30 def _serverquote(s):
30 def _serverquote(s):
31 """quote a string for the remote shell ... which we assume is sh"""
31 """quote a string for the remote shell ... which we assume is sh"""
32 if not s:
32 if not s:
33 return s
33 return s
34 if re.match(b'[a-zA-Z0-9@%_+=:,./-]*$', s):
34 if re.match(b'[a-zA-Z0-9@%_+=:,./-]*$', s):
35 return s
35 return s
36 return b"'%s'" % s.replace(b"'", b"'\\''")
36 return b"'%s'" % s.replace(b"'", b"'\\''")
37
37
38
38
39 def _forwardoutput(ui, pipe, warn=False):
39 def _forwardoutput(ui, pipe, warn=False):
40 """display all data currently available on pipe as remote output.
40 """display all data currently available on pipe as remote output.
41
41
42 This is non blocking."""
42 This is non blocking."""
43 if pipe:
43 if pipe:
44 s = procutil.readpipe(pipe)
44 s = procutil.readpipe(pipe)
45 if s:
45 if s:
46 display = ui.warn if warn else ui.status
46 display = ui.warn if warn else ui.status
47 for l in s.splitlines():
47 for l in s.splitlines():
48 display(_(b"remote: "), l, b'\n')
48 display(_(b"remote: "), l, b'\n')
49
49
50
50
51 class doublepipe(object):
51 class doublepipe(object):
52 """Operate a side-channel pipe in addition of a main one
52 """Operate a side-channel pipe in addition of a main one
53
53
54 The side-channel pipe contains server output to be forwarded to the user
54 The side-channel pipe contains server output to be forwarded to the user
55 input. The double pipe will behave as the "main" pipe, but will ensure the
55 input. The double pipe will behave as the "main" pipe, but will ensure the
56 content of the "side" pipe is properly processed while we wait for blocking
56 content of the "side" pipe is properly processed while we wait for blocking
57 call on the "main" pipe.
57 call on the "main" pipe.
58
58
59 If large amounts of data are read from "main", the forward will cease after
59 If large amounts of data are read from "main", the forward will cease after
60 the first bytes start to appear. This simplifies the implementation
60 the first bytes start to appear. This simplifies the implementation
61 without affecting actual output of sshpeer too much as we rarely issue
61 without affecting actual output of sshpeer too much as we rarely issue
62 large read for data not yet emitted by the server.
62 large read for data not yet emitted by the server.
63
63
64 The main pipe is expected to be a 'bufferedinputpipe' from the util module
64 The main pipe is expected to be a 'bufferedinputpipe' from the util module
65 that handle all the os specific bits. This class lives in this module
65 that handle all the os specific bits. This class lives in this module
66 because it focus on behavior specific to the ssh protocol."""
66 because it focus on behavior specific to the ssh protocol."""
67
67
68 def __init__(self, ui, main, side):
68 def __init__(self, ui, main, side):
69 self._ui = ui
69 self._ui = ui
70 self._main = main
70 self._main = main
71 self._side = side
71 self._side = side
72
72
73 def _wait(self):
73 def _wait(self):
74 """wait until some data are available on main or side
74 """wait until some data are available on main or side
75
75
76 return a pair of boolean (ismainready, issideready)
76 return a pair of boolean (ismainready, issideready)
77
77
78 (This will only wait for data if the setup is supported by `util.poll`)
78 (This will only wait for data if the setup is supported by `util.poll`)
79 """
79 """
80 if (
80 if (
81 isinstance(self._main, util.bufferedinputpipe)
81 isinstance(self._main, util.bufferedinputpipe)
82 and self._main.hasbuffer
82 and self._main.hasbuffer
83 ):
83 ):
84 # Main has data. Assume side is worth poking at.
84 # Main has data. Assume side is worth poking at.
85 return True, True
85 return True, True
86
86
87 fds = [self._main.fileno(), self._side.fileno()]
87 fds = [self._main.fileno(), self._side.fileno()]
88 try:
88 try:
89 act = util.poll(fds)
89 act = util.poll(fds)
90 except NotImplementedError:
90 except NotImplementedError:
91 # non supported yet case, assume all have data.
91 # non supported yet case, assume all have data.
92 act = fds
92 act = fds
93 return (self._main.fileno() in act, self._side.fileno() in act)
93 return (self._main.fileno() in act, self._side.fileno() in act)
94
94
95 def write(self, data):
95 def write(self, data):
96 return self._call(b'write', data)
96 return self._call(b'write', data)
97
97
98 def read(self, size):
98 def read(self, size):
99 r = self._call(b'read', size)
99 r = self._call(b'read', size)
100 if size != 0 and not r:
100 if size != 0 and not r:
101 # We've observed a condition that indicates the
101 # We've observed a condition that indicates the
102 # stdout closed unexpectedly. Check stderr one
102 # stdout closed unexpectedly. Check stderr one
103 # more time and snag anything that's there before
103 # more time and snag anything that's there before
104 # letting anyone know the main part of the pipe
104 # letting anyone know the main part of the pipe
105 # closed prematurely.
105 # closed prematurely.
106 _forwardoutput(self._ui, self._side)
106 _forwardoutput(self._ui, self._side)
107 return r
107 return r
108
108
109 def unbufferedread(self, size):
109 def unbufferedread(self, size):
110 r = self._call(b'unbufferedread', size)
110 r = self._call(b'unbufferedread', size)
111 if size != 0 and not r:
111 if size != 0 and not r:
112 # We've observed a condition that indicates the
112 # We've observed a condition that indicates the
113 # stdout closed unexpectedly. Check stderr one
113 # stdout closed unexpectedly. Check stderr one
114 # more time and snag anything that's there before
114 # more time and snag anything that's there before
115 # letting anyone know the main part of the pipe
115 # letting anyone know the main part of the pipe
116 # closed prematurely.
116 # closed prematurely.
117 _forwardoutput(self._ui, self._side)
117 _forwardoutput(self._ui, self._side)
118 return r
118 return r
119
119
120 def readline(self):
120 def readline(self):
121 return self._call(b'readline')
121 return self._call(b'readline')
122
122
123 def _call(self, methname, data=None):
123 def _call(self, methname, data=None):
124 """call <methname> on "main", forward output of "side" while blocking"""
124 """call <methname> on "main", forward output of "side" while blocking"""
125 # data can be '' or 0
125 # data can be '' or 0
126 if (data is not None and not data) or self._main.closed:
126 if (data is not None and not data) or self._main.closed:
127 _forwardoutput(self._ui, self._side)
127 _forwardoutput(self._ui, self._side)
128 return b''
128 return b''
129 while True:
129 while True:
130 mainready, sideready = self._wait()
130 mainready, sideready = self._wait()
131 if sideready:
131 if sideready:
132 _forwardoutput(self._ui, self._side)
132 _forwardoutput(self._ui, self._side)
133 if mainready:
133 if mainready:
134 meth = getattr(self._main, methname)
134 meth = getattr(self._main, methname)
135 if data is None:
135 if data is None:
136 return meth()
136 return meth()
137 else:
137 else:
138 return meth(data)
138 return meth(data)
139
139
140 def close(self):
140 def close(self):
141 return self._main.close()
141 return self._main.close()
142
142
143 @property
143 @property
144 def closed(self):
144 def closed(self):
145 return self._main.closed
145 return self._main.closed
146
146
147 def flush(self):
147 def flush(self):
148 return self._main.flush()
148 return self._main.flush()
149
149
150
150
151 def _cleanuppipes(ui, pipei, pipeo, pipee, warn):
151 def _cleanuppipes(ui, pipei, pipeo, pipee, warn):
152 """Clean up pipes used by an SSH connection."""
152 """Clean up pipes used by an SSH connection."""
153 didsomething = False
153 didsomething = False
154 if pipeo and not pipeo.closed:
154 if pipeo and not pipeo.closed:
155 didsomething = True
155 didsomething = True
156 pipeo.close()
156 pipeo.close()
157 if pipei and not pipei.closed:
157 if pipei and not pipei.closed:
158 didsomething = True
158 didsomething = True
159 pipei.close()
159 pipei.close()
160
160
161 if pipee and not pipee.closed:
161 if pipee and not pipee.closed:
162 didsomething = True
162 didsomething = True
163 # Try to read from the err descriptor until EOF.
163 # Try to read from the err descriptor until EOF.
164 try:
164 try:
165 for l in pipee:
165 for l in pipee:
166 ui.status(_(b'remote: '), l)
166 ui.status(_(b'remote: '), l)
167 except (IOError, ValueError):
167 except (IOError, ValueError):
168 pass
168 pass
169
169
170 pipee.close()
170 pipee.close()
171
171
172 if didsomething and warn is not None:
172 if didsomething and warn is not None:
173 # Encourage explicit close of sshpeers. Closing via __del__ is
173 # Encourage explicit close of sshpeers. Closing via __del__ is
174 # not very predictable when exceptions are thrown, which has led
174 # not very predictable when exceptions are thrown, which has led
175 # to deadlocks due to a peer get gc'ed in a fork
175 # to deadlocks due to a peer get gc'ed in a fork
176 # We add our own stack trace, because the stacktrace when called
176 # We add our own stack trace, because the stacktrace when called
177 # from __del__ is useless.
177 # from __del__ is useless.
178 if False: # enabled in next commit
178 ui.develwarn(b'missing close on SSH connection created at:\n%s' % warn)
179 ui.develwarn(
180 b'missing close on SSH connection created at:\n%s' % warn
181 )
182
179
183
180
184 def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
181 def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
185 """Create an SSH connection to a server.
182 """Create an SSH connection to a server.
186
183
187 Returns a tuple of (process, stdin, stdout, stderr) for the
184 Returns a tuple of (process, stdin, stdout, stderr) for the
188 spawned process.
185 spawned process.
189 """
186 """
190 cmd = b'%s %s %s' % (
187 cmd = b'%s %s %s' % (
191 sshcmd,
188 sshcmd,
192 args,
189 args,
193 procutil.shellquote(
190 procutil.shellquote(
194 b'%s -R %s serve --stdio'
191 b'%s -R %s serve --stdio'
195 % (_serverquote(remotecmd), _serverquote(path))
192 % (_serverquote(remotecmd), _serverquote(path))
196 ),
193 ),
197 )
194 )
198
195
199 ui.debug(b'running %s\n' % cmd)
196 ui.debug(b'running %s\n' % cmd)
200
197
201 # no buffer allow the use of 'select'
198 # no buffer allow the use of 'select'
202 # feel free to remove buffering and select usage when we ultimately
199 # feel free to remove buffering and select usage when we ultimately
203 # move to threading.
200 # move to threading.
204 stdin, stdout, stderr, proc = procutil.popen4(cmd, bufsize=0, env=sshenv)
201 stdin, stdout, stderr, proc = procutil.popen4(cmd, bufsize=0, env=sshenv)
205
202
206 return proc, stdin, stdout, stderr
203 return proc, stdin, stdout, stderr
207
204
208
205
209 def _clientcapabilities():
206 def _clientcapabilities():
210 """Return list of capabilities of this client.
207 """Return list of capabilities of this client.
211
208
212 Returns a list of capabilities that are supported by this client.
209 Returns a list of capabilities that are supported by this client.
213 """
210 """
214 protoparams = {b'partial-pull'}
211 protoparams = {b'partial-pull'}
215 comps = [
212 comps = [
216 e.wireprotosupport().name
213 e.wireprotosupport().name
217 for e in util.compengines.supportedwireengines(util.CLIENTROLE)
214 for e in util.compengines.supportedwireengines(util.CLIENTROLE)
218 ]
215 ]
219 protoparams.add(b'comp=%s' % b','.join(comps))
216 protoparams.add(b'comp=%s' % b','.join(comps))
220 return protoparams
217 return protoparams
221
218
222
219
223 def _performhandshake(ui, stdin, stdout, stderr):
220 def _performhandshake(ui, stdin, stdout, stderr):
224 def badresponse():
221 def badresponse():
225 # Flush any output on stderr. In general, the stderr contains errors
222 # Flush any output on stderr. In general, the stderr contains errors
226 # from the remote (ssh errors, some hg errors), and status indications
223 # from the remote (ssh errors, some hg errors), and status indications
227 # (like "adding changes"), with no current way to tell them apart.
224 # (like "adding changes"), with no current way to tell them apart.
228 # Here we failed so early that it's almost certainly only errors, so
225 # Here we failed so early that it's almost certainly only errors, so
229 # use warn=True so -q doesn't hide them.
226 # use warn=True so -q doesn't hide them.
230 _forwardoutput(ui, stderr, warn=True)
227 _forwardoutput(ui, stderr, warn=True)
231
228
232 msg = _(b'no suitable response from remote hg')
229 msg = _(b'no suitable response from remote hg')
233 hint = ui.config(b'ui', b'ssherrorhint')
230 hint = ui.config(b'ui', b'ssherrorhint')
234 raise error.RepoError(msg, hint=hint)
231 raise error.RepoError(msg, hint=hint)
235
232
236 # The handshake consists of sending wire protocol commands in reverse
233 # The handshake consists of sending wire protocol commands in reverse
237 # order of protocol implementation and then sniffing for a response
234 # order of protocol implementation and then sniffing for a response
238 # to one of them.
235 # to one of them.
239 #
236 #
240 # Those commands (from oldest to newest) are:
237 # Those commands (from oldest to newest) are:
241 #
238 #
242 # ``between``
239 # ``between``
243 # Asks for the set of revisions between a pair of revisions. Command
240 # Asks for the set of revisions between a pair of revisions. Command
244 # present in all Mercurial server implementations.
241 # present in all Mercurial server implementations.
245 #
242 #
246 # ``hello``
243 # ``hello``
247 # Instructs the server to advertise its capabilities. Introduced in
244 # Instructs the server to advertise its capabilities. Introduced in
248 # Mercurial 0.9.1.
245 # Mercurial 0.9.1.
249 #
246 #
250 # ``upgrade``
247 # ``upgrade``
251 # Requests upgrade from default transport protocol version 1 to
248 # Requests upgrade from default transport protocol version 1 to
252 # a newer version. Introduced in Mercurial 4.6 as an experimental
249 # a newer version. Introduced in Mercurial 4.6 as an experimental
253 # feature.
250 # feature.
254 #
251 #
255 # The ``between`` command is issued with a request for the null
252 # The ``between`` command is issued with a request for the null
256 # range. If the remote is a Mercurial server, this request will
253 # range. If the remote is a Mercurial server, this request will
257 # generate a specific response: ``1\n\n``. This represents the
254 # generate a specific response: ``1\n\n``. This represents the
258 # wire protocol encoded value for ``\n``. We look for ``1\n\n``
255 # wire protocol encoded value for ``\n``. We look for ``1\n\n``
259 # in the output stream and know this is the response to ``between``
256 # in the output stream and know this is the response to ``between``
260 # and we're at the end of our handshake reply.
257 # and we're at the end of our handshake reply.
261 #
258 #
262 # The response to the ``hello`` command will be a line with the
259 # The response to the ``hello`` command will be a line with the
263 # length of the value returned by that command followed by that
260 # length of the value returned by that command followed by that
264 # value. If the server doesn't support ``hello`` (which should be
261 # value. If the server doesn't support ``hello`` (which should be
265 # rare), that line will be ``0\n``. Otherwise, the value will contain
262 # rare), that line will be ``0\n``. Otherwise, the value will contain
266 # RFC 822 like lines. Of these, the ``capabilities:`` line contains
263 # RFC 822 like lines. Of these, the ``capabilities:`` line contains
267 # the capabilities of the server.
264 # the capabilities of the server.
268 #
265 #
269 # The ``upgrade`` command isn't really a command in the traditional
266 # The ``upgrade`` command isn't really a command in the traditional
270 # sense of version 1 of the transport because it isn't using the
267 # sense of version 1 of the transport because it isn't using the
271 # proper mechanism for formatting insteads: instead, it just encodes
268 # proper mechanism for formatting insteads: instead, it just encodes
272 # arguments on the line, delimited by spaces.
269 # arguments on the line, delimited by spaces.
273 #
270 #
274 # The ``upgrade`` line looks like ``upgrade <token> <capabilities>``.
271 # The ``upgrade`` line looks like ``upgrade <token> <capabilities>``.
275 # If the server doesn't support protocol upgrades, it will reply to
272 # If the server doesn't support protocol upgrades, it will reply to
276 # this line with ``0\n``. Otherwise, it emits an
273 # this line with ``0\n``. Otherwise, it emits an
277 # ``upgraded <token> <protocol>`` line to both stdout and stderr.
274 # ``upgraded <token> <protocol>`` line to both stdout and stderr.
278 # Content immediately following this line describes additional
275 # Content immediately following this line describes additional
279 # protocol and server state.
276 # protocol and server state.
280 #
277 #
281 # In addition to the responses to our command requests, the server
278 # In addition to the responses to our command requests, the server
282 # may emit "banner" output on stdout. SSH servers are allowed to
279 # may emit "banner" output on stdout. SSH servers are allowed to
283 # print messages to stdout on login. Issuing commands on connection
280 # print messages to stdout on login. Issuing commands on connection
284 # allows us to flush this banner output from the server by scanning
281 # allows us to flush this banner output from the server by scanning
285 # for output to our well-known ``between`` command. Of course, if
282 # for output to our well-known ``between`` command. Of course, if
286 # the banner contains ``1\n\n``, this will throw off our detection.
283 # the banner contains ``1\n\n``, this will throw off our detection.
287
284
288 requestlog = ui.configbool(b'devel', b'debug.peer-request')
285 requestlog = ui.configbool(b'devel', b'debug.peer-request')
289
286
290 # Generate a random token to help identify responses to version 2
287 # Generate a random token to help identify responses to version 2
291 # upgrade request.
288 # upgrade request.
292 token = pycompat.sysbytes(str(uuid.uuid4()))
289 token = pycompat.sysbytes(str(uuid.uuid4()))
293 upgradecaps = [
290 upgradecaps = [
294 (b'proto', wireprotoserver.SSHV2),
291 (b'proto', wireprotoserver.SSHV2),
295 ]
292 ]
296 upgradecaps = util.urlreq.urlencode(upgradecaps)
293 upgradecaps = util.urlreq.urlencode(upgradecaps)
297
294
298 try:
295 try:
299 pairsarg = b'%s-%s' % (b'0' * 40, b'0' * 40)
296 pairsarg = b'%s-%s' % (b'0' * 40, b'0' * 40)
300 handshake = [
297 handshake = [
301 b'hello\n',
298 b'hello\n',
302 b'between\n',
299 b'between\n',
303 b'pairs %d\n' % len(pairsarg),
300 b'pairs %d\n' % len(pairsarg),
304 pairsarg,
301 pairsarg,
305 ]
302 ]
306
303
307 # Request upgrade to version 2 if configured.
304 # Request upgrade to version 2 if configured.
308 if ui.configbool(b'experimental', b'sshpeer.advertise-v2'):
305 if ui.configbool(b'experimental', b'sshpeer.advertise-v2'):
309 ui.debug(b'sending upgrade request: %s %s\n' % (token, upgradecaps))
306 ui.debug(b'sending upgrade request: %s %s\n' % (token, upgradecaps))
310 handshake.insert(0, b'upgrade %s %s\n' % (token, upgradecaps))
307 handshake.insert(0, b'upgrade %s %s\n' % (token, upgradecaps))
311
308
312 if requestlog:
309 if requestlog:
313 ui.debug(b'devel-peer-request: hello+between\n')
310 ui.debug(b'devel-peer-request: hello+between\n')
314 ui.debug(b'devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
311 ui.debug(b'devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
315 ui.debug(b'sending hello command\n')
312 ui.debug(b'sending hello command\n')
316 ui.debug(b'sending between command\n')
313 ui.debug(b'sending between command\n')
317
314
318 stdin.write(b''.join(handshake))
315 stdin.write(b''.join(handshake))
319 stdin.flush()
316 stdin.flush()
320 except IOError:
317 except IOError:
321 badresponse()
318 badresponse()
322
319
323 # Assume version 1 of wire protocol by default.
320 # Assume version 1 of wire protocol by default.
324 protoname = wireprototypes.SSHV1
321 protoname = wireprototypes.SSHV1
325 reupgraded = re.compile(b'^upgraded %s (.*)$' % stringutil.reescape(token))
322 reupgraded = re.compile(b'^upgraded %s (.*)$' % stringutil.reescape(token))
326
323
327 lines = [b'', b'dummy']
324 lines = [b'', b'dummy']
328 max_noise = 500
325 max_noise = 500
329 while lines[-1] and max_noise:
326 while lines[-1] and max_noise:
330 try:
327 try:
331 l = stdout.readline()
328 l = stdout.readline()
332 _forwardoutput(ui, stderr, warn=True)
329 _forwardoutput(ui, stderr, warn=True)
333
330
334 # Look for reply to protocol upgrade request. It has a token
331 # Look for reply to protocol upgrade request. It has a token
335 # in it, so there should be no false positives.
332 # in it, so there should be no false positives.
336 m = reupgraded.match(l)
333 m = reupgraded.match(l)
337 if m:
334 if m:
338 protoname = m.group(1)
335 protoname = m.group(1)
339 ui.debug(b'protocol upgraded to %s\n' % protoname)
336 ui.debug(b'protocol upgraded to %s\n' % protoname)
340 # If an upgrade was handled, the ``hello`` and ``between``
337 # If an upgrade was handled, the ``hello`` and ``between``
341 # requests are ignored. The next output belongs to the
338 # requests are ignored. The next output belongs to the
342 # protocol, so stop scanning lines.
339 # protocol, so stop scanning lines.
343 break
340 break
344
341
345 # Otherwise it could be a banner, ``0\n`` response if server
342 # Otherwise it could be a banner, ``0\n`` response if server
346 # doesn't support upgrade.
343 # doesn't support upgrade.
347
344
348 if lines[-1] == b'1\n' and l == b'\n':
345 if lines[-1] == b'1\n' and l == b'\n':
349 break
346 break
350 if l:
347 if l:
351 ui.debug(b'remote: ', l)
348 ui.debug(b'remote: ', l)
352 lines.append(l)
349 lines.append(l)
353 max_noise -= 1
350 max_noise -= 1
354 except IOError:
351 except IOError:
355 badresponse()
352 badresponse()
356 else:
353 else:
357 badresponse()
354 badresponse()
358
355
359 caps = set()
356 caps = set()
360
357
361 # For version 1, we should see a ``capabilities`` line in response to the
358 # For version 1, we should see a ``capabilities`` line in response to the
362 # ``hello`` command.
359 # ``hello`` command.
363 if protoname == wireprototypes.SSHV1:
360 if protoname == wireprototypes.SSHV1:
364 for l in reversed(lines):
361 for l in reversed(lines):
365 # Look for response to ``hello`` command. Scan from the back so
362 # Look for response to ``hello`` command. Scan from the back so
366 # we don't misinterpret banner output as the command reply.
363 # we don't misinterpret banner output as the command reply.
367 if l.startswith(b'capabilities:'):
364 if l.startswith(b'capabilities:'):
368 caps.update(l[:-1].split(b':')[1].split())
365 caps.update(l[:-1].split(b':')[1].split())
369 break
366 break
370 elif protoname == wireprotoserver.SSHV2:
367 elif protoname == wireprotoserver.SSHV2:
371 # We see a line with number of bytes to follow and then a value
368 # We see a line with number of bytes to follow and then a value
372 # looking like ``capabilities: *``.
369 # looking like ``capabilities: *``.
373 line = stdout.readline()
370 line = stdout.readline()
374 try:
371 try:
375 valuelen = int(line)
372 valuelen = int(line)
376 except ValueError:
373 except ValueError:
377 badresponse()
374 badresponse()
378
375
379 capsline = stdout.read(valuelen)
376 capsline = stdout.read(valuelen)
380 if not capsline.startswith(b'capabilities: '):
377 if not capsline.startswith(b'capabilities: '):
381 badresponse()
378 badresponse()
382
379
383 ui.debug(b'remote: %s\n' % capsline)
380 ui.debug(b'remote: %s\n' % capsline)
384
381
385 caps.update(capsline.split(b':')[1].split())
382 caps.update(capsline.split(b':')[1].split())
386 # Trailing newline.
383 # Trailing newline.
387 stdout.read(1)
384 stdout.read(1)
388
385
389 # Error if we couldn't find capabilities, this means:
386 # Error if we couldn't find capabilities, this means:
390 #
387 #
391 # 1. Remote isn't a Mercurial server
388 # 1. Remote isn't a Mercurial server
392 # 2. Remote is a <0.9.1 Mercurial server
389 # 2. Remote is a <0.9.1 Mercurial server
393 # 3. Remote is a future Mercurial server that dropped ``hello``
390 # 3. Remote is a future Mercurial server that dropped ``hello``
394 # and other attempted handshake mechanisms.
391 # and other attempted handshake mechanisms.
395 if not caps:
392 if not caps:
396 badresponse()
393 badresponse()
397
394
398 # Flush any output on stderr before proceeding.
395 # Flush any output on stderr before proceeding.
399 _forwardoutput(ui, stderr, warn=True)
396 _forwardoutput(ui, stderr, warn=True)
400
397
401 return protoname, caps
398 return protoname, caps
402
399
403
400
404 class sshv1peer(wireprotov1peer.wirepeer):
401 class sshv1peer(wireprotov1peer.wirepeer):
405 def __init__(
402 def __init__(
406 self, ui, url, proc, stdin, stdout, stderr, caps, autoreadstderr=True
403 self, ui, url, proc, stdin, stdout, stderr, caps, autoreadstderr=True
407 ):
404 ):
408 """Create a peer from an existing SSH connection.
405 """Create a peer from an existing SSH connection.
409
406
410 ``proc`` is a handle on the underlying SSH process.
407 ``proc`` is a handle on the underlying SSH process.
411 ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
408 ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
412 pipes for that process.
409 pipes for that process.
413 ``caps`` is a set of capabilities supported by the remote.
410 ``caps`` is a set of capabilities supported by the remote.
414 ``autoreadstderr`` denotes whether to automatically read from
411 ``autoreadstderr`` denotes whether to automatically read from
415 stderr and to forward its output.
412 stderr and to forward its output.
416 """
413 """
417 self._url = url
414 self._url = url
418 self.ui = ui
415 self.ui = ui
419 # self._subprocess is unused. Keeping a handle on the process
416 # self._subprocess is unused. Keeping a handle on the process
420 # holds a reference and prevents it from being garbage collected.
417 # holds a reference and prevents it from being garbage collected.
421 self._subprocess = proc
418 self._subprocess = proc
422
419
423 # And we hook up our "doublepipe" wrapper to allow querying
420 # And we hook up our "doublepipe" wrapper to allow querying
424 # stderr any time we perform I/O.
421 # stderr any time we perform I/O.
425 if autoreadstderr:
422 if autoreadstderr:
426 stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
423 stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
427 stdin = doublepipe(ui, stdin, stderr)
424 stdin = doublepipe(ui, stdin, stderr)
428
425
429 self._pipeo = stdin
426 self._pipeo = stdin
430 self._pipei = stdout
427 self._pipei = stdout
431 self._pipee = stderr
428 self._pipee = stderr
432 self._caps = caps
429 self._caps = caps
433 self._autoreadstderr = autoreadstderr
430 self._autoreadstderr = autoreadstderr
434 self._initstack = b''.join(util.getstackframes(1))
431 self._initstack = b''.join(util.getstackframes(1))
435
432
436 # Commands that have a "framed" response where the first line of the
433 # Commands that have a "framed" response where the first line of the
437 # response contains the length of that response.
434 # response contains the length of that response.
438 _FRAMED_COMMANDS = {
435 _FRAMED_COMMANDS = {
439 b'batch',
436 b'batch',
440 }
437 }
441
438
442 # Begin of ipeerconnection interface.
439 # Begin of ipeerconnection interface.
443
440
444 def url(self):
441 def url(self):
445 return self._url
442 return self._url
446
443
447 def local(self):
444 def local(self):
448 return None
445 return None
449
446
450 def peer(self):
447 def peer(self):
451 return self
448 return self
452
449
453 def canpush(self):
450 def canpush(self):
454 return True
451 return True
455
452
456 def close(self):
453 def close(self):
457 self._cleanup()
454 self._cleanup()
458
455
459 # End of ipeerconnection interface.
456 # End of ipeerconnection interface.
460
457
461 # Begin of ipeercommands interface.
458 # Begin of ipeercommands interface.
462
459
463 def capabilities(self):
460 def capabilities(self):
464 return self._caps
461 return self._caps
465
462
466 # End of ipeercommands interface.
463 # End of ipeercommands interface.
467
464
468 def _readerr(self):
465 def _readerr(self):
469 _forwardoutput(self.ui, self._pipee)
466 _forwardoutput(self.ui, self._pipee)
470
467
471 def _abort(self, exception):
468 def _abort(self, exception):
472 self._cleanup()
469 self._cleanup()
473 raise exception
470 raise exception
474
471
475 def _cleanup(self, warn=None):
472 def _cleanup(self, warn=None):
476 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee, warn=warn)
473 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee, warn=warn)
477
474
478 def __del__(self):
475 def __del__(self):
479 self._cleanup(warn=self._initstack)
476 self._cleanup(warn=self._initstack)
480
477
481 def _sendrequest(self, cmd, args, framed=False):
478 def _sendrequest(self, cmd, args, framed=False):
482 if self.ui.debugflag and self.ui.configbool(
479 if self.ui.debugflag and self.ui.configbool(
483 b'devel', b'debug.peer-request'
480 b'devel', b'debug.peer-request'
484 ):
481 ):
485 dbg = self.ui.debug
482 dbg = self.ui.debug
486 line = b'devel-peer-request: %s\n'
483 line = b'devel-peer-request: %s\n'
487 dbg(line % cmd)
484 dbg(line % cmd)
488 for key, value in sorted(args.items()):
485 for key, value in sorted(args.items()):
489 if not isinstance(value, dict):
486 if not isinstance(value, dict):
490 dbg(line % b' %s: %d bytes' % (key, len(value)))
487 dbg(line % b' %s: %d bytes' % (key, len(value)))
491 else:
488 else:
492 for dk, dv in sorted(value.items()):
489 for dk, dv in sorted(value.items()):
493 dbg(line % b' %s-%s: %d' % (key, dk, len(dv)))
490 dbg(line % b' %s-%s: %d' % (key, dk, len(dv)))
494 self.ui.debug(b"sending %s command\n" % cmd)
491 self.ui.debug(b"sending %s command\n" % cmd)
495 self._pipeo.write(b"%s\n" % cmd)
492 self._pipeo.write(b"%s\n" % cmd)
496 _func, names = wireprotov1server.commands[cmd]
493 _func, names = wireprotov1server.commands[cmd]
497 keys = names.split()
494 keys = names.split()
498 wireargs = {}
495 wireargs = {}
499 for k in keys:
496 for k in keys:
500 if k == b'*':
497 if k == b'*':
501 wireargs[b'*'] = args
498 wireargs[b'*'] = args
502 break
499 break
503 else:
500 else:
504 wireargs[k] = args[k]
501 wireargs[k] = args[k]
505 del args[k]
502 del args[k]
506 for k, v in sorted(pycompat.iteritems(wireargs)):
503 for k, v in sorted(pycompat.iteritems(wireargs)):
507 self._pipeo.write(b"%s %d\n" % (k, len(v)))
504 self._pipeo.write(b"%s %d\n" % (k, len(v)))
508 if isinstance(v, dict):
505 if isinstance(v, dict):
509 for dk, dv in pycompat.iteritems(v):
506 for dk, dv in pycompat.iteritems(v):
510 self._pipeo.write(b"%s %d\n" % (dk, len(dv)))
507 self._pipeo.write(b"%s %d\n" % (dk, len(dv)))
511 self._pipeo.write(dv)
508 self._pipeo.write(dv)
512 else:
509 else:
513 self._pipeo.write(v)
510 self._pipeo.write(v)
514 self._pipeo.flush()
511 self._pipeo.flush()
515
512
516 # We know exactly how many bytes are in the response. So return a proxy
513 # We know exactly how many bytes are in the response. So return a proxy
517 # around the raw output stream that allows reading exactly this many
514 # around the raw output stream that allows reading exactly this many
518 # bytes. Callers then can read() without fear of overrunning the
515 # bytes. Callers then can read() without fear of overrunning the
519 # response.
516 # response.
520 if framed:
517 if framed:
521 amount = self._getamount()
518 amount = self._getamount()
522 return util.cappedreader(self._pipei, amount)
519 return util.cappedreader(self._pipei, amount)
523
520
524 return self._pipei
521 return self._pipei
525
522
526 def _callstream(self, cmd, **args):
523 def _callstream(self, cmd, **args):
527 args = pycompat.byteskwargs(args)
524 args = pycompat.byteskwargs(args)
528 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
525 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
529
526
530 def _callcompressable(self, cmd, **args):
527 def _callcompressable(self, cmd, **args):
531 args = pycompat.byteskwargs(args)
528 args = pycompat.byteskwargs(args)
532 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
529 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
533
530
534 def _call(self, cmd, **args):
531 def _call(self, cmd, **args):
535 args = pycompat.byteskwargs(args)
532 args = pycompat.byteskwargs(args)
536 return self._sendrequest(cmd, args, framed=True).read()
533 return self._sendrequest(cmd, args, framed=True).read()
537
534
538 def _callpush(self, cmd, fp, **args):
535 def _callpush(self, cmd, fp, **args):
539 # The server responds with an empty frame if the client should
536 # The server responds with an empty frame if the client should
540 # continue submitting the payload.
537 # continue submitting the payload.
541 r = self._call(cmd, **args)
538 r = self._call(cmd, **args)
542 if r:
539 if r:
543 return b'', r
540 return b'', r
544
541
545 # The payload consists of frames with content followed by an empty
542 # The payload consists of frames with content followed by an empty
546 # frame.
543 # frame.
547 for d in iter(lambda: fp.read(4096), b''):
544 for d in iter(lambda: fp.read(4096), b''):
548 self._writeframed(d)
545 self._writeframed(d)
549 self._writeframed(b"", flush=True)
546 self._writeframed(b"", flush=True)
550
547
551 # In case of success, there is an empty frame and a frame containing
548 # In case of success, there is an empty frame and a frame containing
552 # the integer result (as a string).
549 # the integer result (as a string).
553 # In case of error, there is a non-empty frame containing the error.
550 # In case of error, there is a non-empty frame containing the error.
554 r = self._readframed()
551 r = self._readframed()
555 if r:
552 if r:
556 return b'', r
553 return b'', r
557 return self._readframed(), b''
554 return self._readframed(), b''
558
555
559 def _calltwowaystream(self, cmd, fp, **args):
556 def _calltwowaystream(self, cmd, fp, **args):
560 # The server responds with an empty frame if the client should
557 # The server responds with an empty frame if the client should
561 # continue submitting the payload.
558 # continue submitting the payload.
562 r = self._call(cmd, **args)
559 r = self._call(cmd, **args)
563 if r:
560 if r:
564 # XXX needs to be made better
561 # XXX needs to be made better
565 raise error.Abort(_(b'unexpected remote reply: %s') % r)
562 raise error.Abort(_(b'unexpected remote reply: %s') % r)
566
563
567 # The payload consists of frames with content followed by an empty
564 # The payload consists of frames with content followed by an empty
568 # frame.
565 # frame.
569 for d in iter(lambda: fp.read(4096), b''):
566 for d in iter(lambda: fp.read(4096), b''):
570 self._writeframed(d)
567 self._writeframed(d)
571 self._writeframed(b"", flush=True)
568 self._writeframed(b"", flush=True)
572
569
573 return self._pipei
570 return self._pipei
574
571
575 def _getamount(self):
572 def _getamount(self):
576 l = self._pipei.readline()
573 l = self._pipei.readline()
577 if l == b'\n':
574 if l == b'\n':
578 if self._autoreadstderr:
575 if self._autoreadstderr:
579 self._readerr()
576 self._readerr()
580 msg = _(b'check previous remote output')
577 msg = _(b'check previous remote output')
581 self._abort(error.OutOfBandError(hint=msg))
578 self._abort(error.OutOfBandError(hint=msg))
582 if self._autoreadstderr:
579 if self._autoreadstderr:
583 self._readerr()
580 self._readerr()
584 try:
581 try:
585 return int(l)
582 return int(l)
586 except ValueError:
583 except ValueError:
587 self._abort(error.ResponseError(_(b"unexpected response:"), l))
584 self._abort(error.ResponseError(_(b"unexpected response:"), l))
588
585
589 def _readframed(self):
586 def _readframed(self):
590 size = self._getamount()
587 size = self._getamount()
591 if not size:
588 if not size:
592 return b''
589 return b''
593
590
594 return self._pipei.read(size)
591 return self._pipei.read(size)
595
592
596 def _writeframed(self, data, flush=False):
593 def _writeframed(self, data, flush=False):
597 self._pipeo.write(b"%d\n" % len(data))
594 self._pipeo.write(b"%d\n" % len(data))
598 if data:
595 if data:
599 self._pipeo.write(data)
596 self._pipeo.write(data)
600 if flush:
597 if flush:
601 self._pipeo.flush()
598 self._pipeo.flush()
602 if self._autoreadstderr:
599 if self._autoreadstderr:
603 self._readerr()
600 self._readerr()
604
601
605
602
606 class sshv2peer(sshv1peer):
603 class sshv2peer(sshv1peer):
607 """A peer that speakers version 2 of the transport protocol."""
604 """A peer that speakers version 2 of the transport protocol."""
608
605
609 # Currently version 2 is identical to version 1 post handshake.
606 # Currently version 2 is identical to version 1 post handshake.
610 # And handshake is performed before the peer is instantiated. So
607 # And handshake is performed before the peer is instantiated. So
611 # we need no custom code.
608 # we need no custom code.
612
609
613
610
614 def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True):
611 def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True):
615 """Make a peer instance from existing pipes.
612 """Make a peer instance from existing pipes.
616
613
617 ``path`` and ``proc`` are stored on the eventual peer instance and may
614 ``path`` and ``proc`` are stored on the eventual peer instance and may
618 not be used for anything meaningful.
615 not be used for anything meaningful.
619
616
620 ``stdin``, ``stdout``, and ``stderr`` are the pipes connected to the
617 ``stdin``, ``stdout``, and ``stderr`` are the pipes connected to the
621 SSH server's stdio handles.
618 SSH server's stdio handles.
622
619
623 This function is factored out to allow creating peers that don't
620 This function is factored out to allow creating peers that don't
624 actually spawn a new process. It is useful for starting SSH protocol
621 actually spawn a new process. It is useful for starting SSH protocol
625 servers and clients via non-standard means, which can be useful for
622 servers and clients via non-standard means, which can be useful for
626 testing.
623 testing.
627 """
624 """
628 try:
625 try:
629 protoname, caps = _performhandshake(ui, stdin, stdout, stderr)
626 protoname, caps = _performhandshake(ui, stdin, stdout, stderr)
630 except Exception:
627 except Exception:
631 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
628 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
632 raise
629 raise
633
630
634 if protoname == wireprototypes.SSHV1:
631 if protoname == wireprototypes.SSHV1:
635 return sshv1peer(
632 return sshv1peer(
636 ui,
633 ui,
637 path,
634 path,
638 proc,
635 proc,
639 stdin,
636 stdin,
640 stdout,
637 stdout,
641 stderr,
638 stderr,
642 caps,
639 caps,
643 autoreadstderr=autoreadstderr,
640 autoreadstderr=autoreadstderr,
644 )
641 )
645 elif protoname == wireprototypes.SSHV2:
642 elif protoname == wireprototypes.SSHV2:
646 return sshv2peer(
643 return sshv2peer(
647 ui,
644 ui,
648 path,
645 path,
649 proc,
646 proc,
650 stdin,
647 stdin,
651 stdout,
648 stdout,
652 stderr,
649 stderr,
653 caps,
650 caps,
654 autoreadstderr=autoreadstderr,
651 autoreadstderr=autoreadstderr,
655 )
652 )
656 else:
653 else:
657 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
654 _cleanuppipes(ui, stdout, stdin, stderr, warn=None)
658 raise error.RepoError(
655 raise error.RepoError(
659 _(b'unknown version of SSH protocol: %s') % protoname
656 _(b'unknown version of SSH protocol: %s') % protoname
660 )
657 )
661
658
662
659
663 def instance(ui, path, create, intents=None, createopts=None):
660 def instance(ui, path, create, intents=None, createopts=None):
664 """Create an SSH peer.
661 """Create an SSH peer.
665
662
666 The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
663 The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
667 """
664 """
668 u = util.url(path, parsequery=False, parsefragment=False)
665 u = util.url(path, parsequery=False, parsefragment=False)
669 if u.scheme != b'ssh' or not u.host or u.path is None:
666 if u.scheme != b'ssh' or not u.host or u.path is None:
670 raise error.RepoError(_(b"couldn't parse location %s") % path)
667 raise error.RepoError(_(b"couldn't parse location %s") % path)
671
668
672 util.checksafessh(path)
669 util.checksafessh(path)
673
670
674 if u.passwd is not None:
671 if u.passwd is not None:
675 raise error.RepoError(_(b'password in URL not supported'))
672 raise error.RepoError(_(b'password in URL not supported'))
676
673
677 sshcmd = ui.config(b'ui', b'ssh')
674 sshcmd = ui.config(b'ui', b'ssh')
678 remotecmd = ui.config(b'ui', b'remotecmd')
675 remotecmd = ui.config(b'ui', b'remotecmd')
679 sshaddenv = dict(ui.configitems(b'sshenv'))
676 sshaddenv = dict(ui.configitems(b'sshenv'))
680 sshenv = procutil.shellenviron(sshaddenv)
677 sshenv = procutil.shellenviron(sshaddenv)
681 remotepath = u.path or b'.'
678 remotepath = u.path or b'.'
682
679
683 args = procutil.sshargs(sshcmd, u.host, u.user, u.port)
680 args = procutil.sshargs(sshcmd, u.host, u.user, u.port)
684
681
685 if create:
682 if create:
686 # We /could/ do this, but only if the remote init command knows how to
683 # We /could/ do this, but only if the remote init command knows how to
687 # handle them. We don't yet make any assumptions about that. And without
684 # handle them. We don't yet make any assumptions about that. And without
688 # querying the remote, there's no way of knowing if the remote even
685 # querying the remote, there's no way of knowing if the remote even
689 # supports said requested feature.
686 # supports said requested feature.
690 if createopts:
687 if createopts:
691 raise error.RepoError(
688 raise error.RepoError(
692 _(
689 _(
693 b'cannot create remote SSH repositories '
690 b'cannot create remote SSH repositories '
694 b'with extra options'
691 b'with extra options'
695 )
692 )
696 )
693 )
697
694
698 cmd = b'%s %s %s' % (
695 cmd = b'%s %s %s' % (
699 sshcmd,
696 sshcmd,
700 args,
697 args,
701 procutil.shellquote(
698 procutil.shellquote(
702 b'%s init %s'
699 b'%s init %s'
703 % (_serverquote(remotecmd), _serverquote(remotepath))
700 % (_serverquote(remotecmd), _serverquote(remotepath))
704 ),
701 ),
705 )
702 )
706 ui.debug(b'running %s\n' % cmd)
703 ui.debug(b'running %s\n' % cmd)
707 res = ui.system(cmd, blockedtag=b'sshpeer', environ=sshenv)
704 res = ui.system(cmd, blockedtag=b'sshpeer', environ=sshenv)
708 if res != 0:
705 if res != 0:
709 raise error.RepoError(_(b'could not create remote repo'))
706 raise error.RepoError(_(b'could not create remote repo'))
710
707
711 proc, stdin, stdout, stderr = _makeconnection(
708 proc, stdin, stdout, stderr = _makeconnection(
712 ui, sshcmd, args, remotecmd, remotepath, sshenv
709 ui, sshcmd, args, remotecmd, remotepath, sshenv
713 )
710 )
714
711
715 peer = makepeer(ui, path, proc, stdin, stdout, stderr)
712 peer = makepeer(ui, path, proc, stdin, stdout, stderr)
716
713
717 # Finally, if supported by the server, notify it about our own
714 # Finally, if supported by the server, notify it about our own
718 # capabilities.
715 # capabilities.
719 if b'protocaps' in peer.capabilities():
716 if b'protocaps' in peer.capabilities():
720 try:
717 try:
721 peer._call(
718 peer._call(
722 b"protocaps", caps=b' '.join(sorted(_clientcapabilities()))
719 b"protocaps", caps=b' '.join(sorted(_clientcapabilities()))
723 )
720 )
724 except IOError:
721 except IOError:
725 peer._cleanup()
722 peer._cleanup()
726 raise error.RepoError(_(b'capability exchange failed'))
723 raise error.RepoError(_(b'capability exchange failed'))
727
724
728 return peer
725 return peer
@@ -1,2054 +1,2069 b''
1 # subrepo.py - sub-repository classes and factory
1 # subrepo.py - sub-repository classes and factory
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 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 copy
10 import copy
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import stat
14 import stat
15 import subprocess
15 import subprocess
16 import sys
16 import sys
17 import tarfile
17 import tarfile
18 import xml.dom.minidom
18 import xml.dom.minidom
19
19
20 from .i18n import _
20 from .i18n import _
21 from .node import (
21 from .node import (
22 bin,
22 bin,
23 hex,
23 hex,
24 nullid,
24 nullid,
25 short,
25 short,
26 )
26 )
27 from . import (
27 from . import (
28 cmdutil,
28 cmdutil,
29 encoding,
29 encoding,
30 error,
30 error,
31 exchange,
31 exchange,
32 logcmdutil,
32 logcmdutil,
33 match as matchmod,
33 match as matchmod,
34 merge as merge,
34 merge as merge,
35 pathutil,
35 pathutil,
36 phases,
36 phases,
37 pycompat,
37 pycompat,
38 scmutil,
38 scmutil,
39 subrepoutil,
39 subrepoutil,
40 util,
40 util,
41 vfs as vfsmod,
41 vfs as vfsmod,
42 )
42 )
43 from .utils import (
43 from .utils import (
44 dateutil,
44 dateutil,
45 hashutil,
45 hashutil,
46 procutil,
46 procutil,
47 )
47 )
48
48
49 hg = None
49 hg = None
50 reporelpath = subrepoutil.reporelpath
50 reporelpath = subrepoutil.reporelpath
51 subrelpath = subrepoutil.subrelpath
51 subrelpath = subrepoutil.subrelpath
52 _abssource = subrepoutil._abssource
52 _abssource = subrepoutil._abssource
53 propertycache = util.propertycache
53 propertycache = util.propertycache
54
54
55
55
56 def _expandedabspath(path):
56 def _expandedabspath(path):
57 """
57 """
58 get a path or url and if it is a path expand it and return an absolute path
58 get a path or url and if it is a path expand it and return an absolute path
59 """
59 """
60 expandedpath = util.urllocalpath(util.expandpath(path))
60 expandedpath = util.urllocalpath(util.expandpath(path))
61 u = util.url(expandedpath)
61 u = util.url(expandedpath)
62 if not u.scheme:
62 if not u.scheme:
63 path = util.normpath(os.path.abspath(u.path))
63 path = util.normpath(os.path.abspath(u.path))
64 return path
64 return path
65
65
66
66
67 def _getstorehashcachename(remotepath):
67 def _getstorehashcachename(remotepath):
68 '''get a unique filename for the store hash cache of a remote repository'''
68 '''get a unique filename for the store hash cache of a remote repository'''
69 return hex(hashutil.sha1(_expandedabspath(remotepath)).digest())[0:12]
69 return hex(hashutil.sha1(_expandedabspath(remotepath)).digest())[0:12]
70
70
71
71
72 class SubrepoAbort(error.Abort):
72 class SubrepoAbort(error.Abort):
73 """Exception class used to avoid handling a subrepo error more than once"""
73 """Exception class used to avoid handling a subrepo error more than once"""
74
74
75 def __init__(self, *args, **kw):
75 def __init__(self, *args, **kw):
76 self.subrepo = kw.pop('subrepo', None)
76 self.subrepo = kw.pop('subrepo', None)
77 self.cause = kw.pop('cause', None)
77 self.cause = kw.pop('cause', None)
78 error.Abort.__init__(self, *args, **kw)
78 error.Abort.__init__(self, *args, **kw)
79
79
80
80
81 def annotatesubrepoerror(func):
81 def annotatesubrepoerror(func):
82 def decoratedmethod(self, *args, **kargs):
82 def decoratedmethod(self, *args, **kargs):
83 try:
83 try:
84 res = func(self, *args, **kargs)
84 res = func(self, *args, **kargs)
85 except SubrepoAbort as ex:
85 except SubrepoAbort as ex:
86 # This exception has already been handled
86 # This exception has already been handled
87 raise ex
87 raise ex
88 except error.Abort as ex:
88 except error.Abort as ex:
89 subrepo = subrelpath(self)
89 subrepo = subrelpath(self)
90 errormsg = (
90 errormsg = (
91 ex.message + b' ' + _(b'(in subrepository "%s")') % subrepo
91 ex.message + b' ' + _(b'(in subrepository "%s")') % subrepo
92 )
92 )
93 # avoid handling this exception by raising a SubrepoAbort exception
93 # avoid handling this exception by raising a SubrepoAbort exception
94 raise SubrepoAbort(
94 raise SubrepoAbort(
95 errormsg, hint=ex.hint, subrepo=subrepo, cause=sys.exc_info()
95 errormsg, hint=ex.hint, subrepo=subrepo, cause=sys.exc_info()
96 )
96 )
97 return res
97 return res
98
98
99 return decoratedmethod
99 return decoratedmethod
100
100
101
101
102 def _updateprompt(ui, sub, dirty, local, remote):
102 def _updateprompt(ui, sub, dirty, local, remote):
103 if dirty:
103 if dirty:
104 msg = _(
104 msg = _(
105 b' subrepository sources for %s differ\n'
105 b' subrepository sources for %s differ\n'
106 b'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
106 b'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
107 b'what do you want to do?'
107 b'what do you want to do?'
108 b'$$ &Local $$ &Remote'
108 b'$$ &Local $$ &Remote'
109 ) % (subrelpath(sub), local, remote)
109 ) % (subrelpath(sub), local, remote)
110 else:
110 else:
111 msg = _(
111 msg = _(
112 b' subrepository sources for %s differ (in checked out '
112 b' subrepository sources for %s differ (in checked out '
113 b'version)\n'
113 b'version)\n'
114 b'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
114 b'you can use (l)ocal source (%s) or (r)emote source (%s).\n'
115 b'what do you want to do?'
115 b'what do you want to do?'
116 b'$$ &Local $$ &Remote'
116 b'$$ &Local $$ &Remote'
117 ) % (subrelpath(sub), local, remote)
117 ) % (subrelpath(sub), local, remote)
118 return ui.promptchoice(msg, 0)
118 return ui.promptchoice(msg, 0)
119
119
120
120
121 def _sanitize(ui, vfs, ignore):
121 def _sanitize(ui, vfs, ignore):
122 for dirname, dirs, names in vfs.walk():
122 for dirname, dirs, names in vfs.walk():
123 for i, d in enumerate(dirs):
123 for i, d in enumerate(dirs):
124 if d.lower() == ignore:
124 if d.lower() == ignore:
125 del dirs[i]
125 del dirs[i]
126 break
126 break
127 if vfs.basename(dirname).lower() != b'.hg':
127 if vfs.basename(dirname).lower() != b'.hg':
128 continue
128 continue
129 for f in names:
129 for f in names:
130 if f.lower() == b'hgrc':
130 if f.lower() == b'hgrc':
131 ui.warn(
131 ui.warn(
132 _(
132 _(
133 b"warning: removing potentially hostile 'hgrc' "
133 b"warning: removing potentially hostile 'hgrc' "
134 b"in '%s'\n"
134 b"in '%s'\n"
135 )
135 )
136 % vfs.join(dirname)
136 % vfs.join(dirname)
137 )
137 )
138 vfs.unlink(vfs.reljoin(dirname, f))
138 vfs.unlink(vfs.reljoin(dirname, f))
139
139
140
140
141 def _auditsubrepopath(repo, path):
141 def _auditsubrepopath(repo, path):
142 # sanity check for potentially unsafe paths such as '~' and '$FOO'
142 # sanity check for potentially unsafe paths such as '~' and '$FOO'
143 if path.startswith(b'~') or b'$' in path or util.expandpath(path) != path:
143 if path.startswith(b'~') or b'$' in path or util.expandpath(path) != path:
144 raise error.Abort(
144 raise error.Abort(
145 _(b'subrepo path contains illegal component: %s') % path
145 _(b'subrepo path contains illegal component: %s') % path
146 )
146 )
147 # auditor doesn't check if the path itself is a symlink
147 # auditor doesn't check if the path itself is a symlink
148 pathutil.pathauditor(repo.root)(path)
148 pathutil.pathauditor(repo.root)(path)
149 if repo.wvfs.islink(path):
149 if repo.wvfs.islink(path):
150 raise error.Abort(_(b"subrepo '%s' traverses symbolic link") % path)
150 raise error.Abort(_(b"subrepo '%s' traverses symbolic link") % path)
151
151
152
152
153 SUBREPO_ALLOWED_DEFAULTS = {
153 SUBREPO_ALLOWED_DEFAULTS = {
154 b'hg': True,
154 b'hg': True,
155 b'git': False,
155 b'git': False,
156 b'svn': False,
156 b'svn': False,
157 }
157 }
158
158
159
159
160 def _checktype(ui, kind):
160 def _checktype(ui, kind):
161 # subrepos.allowed is a master kill switch. If disabled, subrepos are
161 # subrepos.allowed is a master kill switch. If disabled, subrepos are
162 # disabled period.
162 # disabled period.
163 if not ui.configbool(b'subrepos', b'allowed', True):
163 if not ui.configbool(b'subrepos', b'allowed', True):
164 raise error.Abort(
164 raise error.Abort(
165 _(b'subrepos not enabled'),
165 _(b'subrepos not enabled'),
166 hint=_(b"see 'hg help config.subrepos' for details"),
166 hint=_(b"see 'hg help config.subrepos' for details"),
167 )
167 )
168
168
169 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
169 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
170 if not ui.configbool(b'subrepos', b'%s:allowed' % kind, default):
170 if not ui.configbool(b'subrepos', b'%s:allowed' % kind, default):
171 raise error.Abort(
171 raise error.Abort(
172 _(b'%s subrepos not allowed') % kind,
172 _(b'%s subrepos not allowed') % kind,
173 hint=_(b"see 'hg help config.subrepos' for details"),
173 hint=_(b"see 'hg help config.subrepos' for details"),
174 )
174 )
175
175
176 if kind not in types:
176 if kind not in types:
177 raise error.Abort(_(b'unknown subrepo type %s') % kind)
177 raise error.Abort(_(b'unknown subrepo type %s') % kind)
178
178
179
179
180 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
180 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
181 """return instance of the right subrepo class for subrepo in path"""
181 """return instance of the right subrepo class for subrepo in path"""
182 # subrepo inherently violates our import layering rules
182 # subrepo inherently violates our import layering rules
183 # because it wants to make repo objects from deep inside the stack
183 # because it wants to make repo objects from deep inside the stack
184 # so we manually delay the circular imports to not break
184 # so we manually delay the circular imports to not break
185 # scripts that don't use our demand-loading
185 # scripts that don't use our demand-loading
186 global hg
186 global hg
187 from . import hg as h
187 from . import hg as h
188
188
189 hg = h
189 hg = h
190
190
191 repo = ctx.repo()
191 repo = ctx.repo()
192 _auditsubrepopath(repo, path)
192 _auditsubrepopath(repo, path)
193 state = ctx.substate[path]
193 state = ctx.substate[path]
194 _checktype(repo.ui, state[2])
194 _checktype(repo.ui, state[2])
195 if allowwdir:
195 if allowwdir:
196 state = (state[0], ctx.subrev(path), state[2])
196 state = (state[0], ctx.subrev(path), state[2])
197 return types[state[2]](ctx, path, state[:2], allowcreate)
197 return types[state[2]](ctx, path, state[:2], allowcreate)
198
198
199
199
200 def nullsubrepo(ctx, path, pctx):
200 def nullsubrepo(ctx, path, pctx):
201 """return an empty subrepo in pctx for the extant subrepo in ctx"""
201 """return an empty subrepo in pctx for the extant subrepo in ctx"""
202 # subrepo inherently violates our import layering rules
202 # subrepo inherently violates our import layering rules
203 # because it wants to make repo objects from deep inside the stack
203 # because it wants to make repo objects from deep inside the stack
204 # so we manually delay the circular imports to not break
204 # so we manually delay the circular imports to not break
205 # scripts that don't use our demand-loading
205 # scripts that don't use our demand-loading
206 global hg
206 global hg
207 from . import hg as h
207 from . import hg as h
208
208
209 hg = h
209 hg = h
210
210
211 repo = ctx.repo()
211 repo = ctx.repo()
212 _auditsubrepopath(repo, path)
212 _auditsubrepopath(repo, path)
213 state = ctx.substate[path]
213 state = ctx.substate[path]
214 _checktype(repo.ui, state[2])
214 _checktype(repo.ui, state[2])
215 subrev = b''
215 subrev = b''
216 if state[2] == b'hg':
216 if state[2] == b'hg':
217 subrev = b"0" * 40
217 subrev = b"0" * 40
218 return types[state[2]](pctx, path, (state[0], subrev), True)
218 return types[state[2]](pctx, path, (state[0], subrev), True)
219
219
220
220
221 # subrepo classes need to implement the following abstract class:
221 # subrepo classes need to implement the following abstract class:
222
222
223
223
224 class abstractsubrepo(object):
224 class abstractsubrepo(object):
225 def __init__(self, ctx, path):
225 def __init__(self, ctx, path):
226 """Initialize abstractsubrepo part
226 """Initialize abstractsubrepo part
227
227
228 ``ctx`` is the context referring this subrepository in the
228 ``ctx`` is the context referring this subrepository in the
229 parent repository.
229 parent repository.
230
230
231 ``path`` is the path to this subrepository as seen from
231 ``path`` is the path to this subrepository as seen from
232 innermost repository.
232 innermost repository.
233 """
233 """
234 self.ui = ctx.repo().ui
234 self.ui = ctx.repo().ui
235 self._ctx = ctx
235 self._ctx = ctx
236 self._path = path
236 self._path = path
237
237
238 def addwebdirpath(self, serverpath, webconf):
238 def addwebdirpath(self, serverpath, webconf):
239 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
239 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
240
240
241 ``serverpath`` is the path component of the URL for this repo.
241 ``serverpath`` is the path component of the URL for this repo.
242
242
243 ``webconf`` is the dictionary of hgwebdir entries.
243 ``webconf`` is the dictionary of hgwebdir entries.
244 """
244 """
245 pass
245 pass
246
246
247 def storeclean(self, path):
247 def storeclean(self, path):
248 """
248 """
249 returns true if the repository has not changed since it was last
249 returns true if the repository has not changed since it was last
250 cloned from or pushed to a given repository.
250 cloned from or pushed to a given repository.
251 """
251 """
252 return False
252 return False
253
253
254 def dirty(self, ignoreupdate=False, missing=False):
254 def dirty(self, ignoreupdate=False, missing=False):
255 """returns true if the dirstate of the subrepo is dirty or does not
255 """returns true if the dirstate of the subrepo is dirty or does not
256 match current stored state. If ignoreupdate is true, only check
256 match current stored state. If ignoreupdate is true, only check
257 whether the subrepo has uncommitted changes in its dirstate. If missing
257 whether the subrepo has uncommitted changes in its dirstate. If missing
258 is true, check for deleted files.
258 is true, check for deleted files.
259 """
259 """
260 raise NotImplementedError
260 raise NotImplementedError
261
261
262 def dirtyreason(self, ignoreupdate=False, missing=False):
262 def dirtyreason(self, ignoreupdate=False, missing=False):
263 """return reason string if it is ``dirty()``
263 """return reason string if it is ``dirty()``
264
264
265 Returned string should have enough information for the message
265 Returned string should have enough information for the message
266 of exception.
266 of exception.
267
267
268 This returns None, otherwise.
268 This returns None, otherwise.
269 """
269 """
270 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
270 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
271 return _(b'uncommitted changes in subrepository "%s"') % subrelpath(
271 return _(b'uncommitted changes in subrepository "%s"') % subrelpath(
272 self
272 self
273 )
273 )
274
274
275 def bailifchanged(self, ignoreupdate=False, hint=None):
275 def bailifchanged(self, ignoreupdate=False, hint=None):
276 """raise Abort if subrepository is ``dirty()``"""
276 """raise Abort if subrepository is ``dirty()``"""
277 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate, missing=True)
277 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate, missing=True)
278 if dirtyreason:
278 if dirtyreason:
279 raise error.Abort(dirtyreason, hint=hint)
279 raise error.Abort(dirtyreason, hint=hint)
280
280
281 def basestate(self):
281 def basestate(self):
282 """current working directory base state, disregarding .hgsubstate
282 """current working directory base state, disregarding .hgsubstate
283 state and working directory modifications"""
283 state and working directory modifications"""
284 raise NotImplementedError
284 raise NotImplementedError
285
285
286 def checknested(self, path):
286 def checknested(self, path):
287 """check if path is a subrepository within this repository"""
287 """check if path is a subrepository within this repository"""
288 return False
288 return False
289
289
290 def commit(self, text, user, date):
290 def commit(self, text, user, date):
291 """commit the current changes to the subrepo with the given
291 """commit the current changes to the subrepo with the given
292 log message. Use given user and date if possible. Return the
292 log message. Use given user and date if possible. Return the
293 new state of the subrepo.
293 new state of the subrepo.
294 """
294 """
295 raise NotImplementedError
295 raise NotImplementedError
296
296
297 def phase(self, state):
297 def phase(self, state):
298 """returns phase of specified state in the subrepository."""
298 """returns phase of specified state in the subrepository."""
299 return phases.public
299 return phases.public
300
300
301 def remove(self):
301 def remove(self):
302 """remove the subrepo
302 """remove the subrepo
303
303
304 (should verify the dirstate is not dirty first)
304 (should verify the dirstate is not dirty first)
305 """
305 """
306 raise NotImplementedError
306 raise NotImplementedError
307
307
308 def get(self, state, overwrite=False):
308 def get(self, state, overwrite=False):
309 """run whatever commands are needed to put the subrepo into
309 """run whatever commands are needed to put the subrepo into
310 this state
310 this state
311 """
311 """
312 raise NotImplementedError
312 raise NotImplementedError
313
313
314 def merge(self, state):
314 def merge(self, state):
315 """merge currently-saved state with the new state."""
315 """merge currently-saved state with the new state."""
316 raise NotImplementedError
316 raise NotImplementedError
317
317
318 def push(self, opts):
318 def push(self, opts):
319 """perform whatever action is analogous to 'hg push'
319 """perform whatever action is analogous to 'hg push'
320
320
321 This may be a no-op on some systems.
321 This may be a no-op on some systems.
322 """
322 """
323 raise NotImplementedError
323 raise NotImplementedError
324
324
325 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
325 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
326 return []
326 return []
327
327
328 def addremove(self, matcher, prefix, uipathfn, opts):
328 def addremove(self, matcher, prefix, uipathfn, opts):
329 self.ui.warn(b"%s: %s" % (prefix, _(b"addremove is not supported")))
329 self.ui.warn(b"%s: %s" % (prefix, _(b"addremove is not supported")))
330 return 1
330 return 1
331
331
332 def cat(self, match, fm, fntemplate, prefix, **opts):
332 def cat(self, match, fm, fntemplate, prefix, **opts):
333 return 1
333 return 1
334
334
335 def status(self, rev2, **opts):
335 def status(self, rev2, **opts):
336 return scmutil.status([], [], [], [], [], [], [])
336 return scmutil.status([], [], [], [], [], [], [])
337
337
338 def diff(self, ui, diffopts, node2, match, prefix, **opts):
338 def diff(self, ui, diffopts, node2, match, prefix, **opts):
339 pass
339 pass
340
340
341 def outgoing(self, ui, dest, opts):
341 def outgoing(self, ui, dest, opts):
342 return 1
342 return 1
343
343
344 def incoming(self, ui, source, opts):
344 def incoming(self, ui, source, opts):
345 return 1
345 return 1
346
346
347 def files(self):
347 def files(self):
348 """return filename iterator"""
348 """return filename iterator"""
349 raise NotImplementedError
349 raise NotImplementedError
350
350
351 def filedata(self, name, decode):
351 def filedata(self, name, decode):
352 """return file data, optionally passed through repo decoders"""
352 """return file data, optionally passed through repo decoders"""
353 raise NotImplementedError
353 raise NotImplementedError
354
354
355 def fileflags(self, name):
355 def fileflags(self, name):
356 """return file flags"""
356 """return file flags"""
357 return b''
357 return b''
358
358
359 def matchfileset(self, cwd, expr, badfn=None):
359 def matchfileset(self, cwd, expr, badfn=None):
360 """Resolve the fileset expression for this repo"""
360 """Resolve the fileset expression for this repo"""
361 return matchmod.never(badfn=badfn)
361 return matchmod.never(badfn=badfn)
362
362
363 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
363 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
364 """handle the files command for this subrepo"""
364 """handle the files command for this subrepo"""
365 return 1
365 return 1
366
366
367 def archive(self, archiver, prefix, match=None, decode=True):
367 def archive(self, archiver, prefix, match=None, decode=True):
368 if match is not None:
368 if match is not None:
369 files = [f for f in self.files() if match(f)]
369 files = [f for f in self.files() if match(f)]
370 else:
370 else:
371 files = self.files()
371 files = self.files()
372 total = len(files)
372 total = len(files)
373 relpath = subrelpath(self)
373 relpath = subrelpath(self)
374 progress = self.ui.makeprogress(
374 progress = self.ui.makeprogress(
375 _(b'archiving (%s)') % relpath, unit=_(b'files'), total=total
375 _(b'archiving (%s)') % relpath, unit=_(b'files'), total=total
376 )
376 )
377 progress.update(0)
377 progress.update(0)
378 for name in files:
378 for name in files:
379 flags = self.fileflags(name)
379 flags = self.fileflags(name)
380 mode = b'x' in flags and 0o755 or 0o644
380 mode = b'x' in flags and 0o755 or 0o644
381 symlink = b'l' in flags
381 symlink = b'l' in flags
382 archiver.addfile(
382 archiver.addfile(
383 prefix + name, mode, symlink, self.filedata(name, decode)
383 prefix + name, mode, symlink, self.filedata(name, decode)
384 )
384 )
385 progress.increment()
385 progress.increment()
386 progress.complete()
386 progress.complete()
387 return total
387 return total
388
388
389 def walk(self, match):
389 def walk(self, match):
390 """
390 """
391 walk recursively through the directory tree, finding all files
391 walk recursively through the directory tree, finding all files
392 matched by the match function
392 matched by the match function
393 """
393 """
394
394
395 def forget(self, match, prefix, uipathfn, dryrun, interactive):
395 def forget(self, match, prefix, uipathfn, dryrun, interactive):
396 return ([], [])
396 return ([], [])
397
397
398 def removefiles(
398 def removefiles(
399 self,
399 self,
400 matcher,
400 matcher,
401 prefix,
401 prefix,
402 uipathfn,
402 uipathfn,
403 after,
403 after,
404 force,
404 force,
405 subrepos,
405 subrepos,
406 dryrun,
406 dryrun,
407 warnings,
407 warnings,
408 ):
408 ):
409 """remove the matched files from the subrepository and the filesystem,
409 """remove the matched files from the subrepository and the filesystem,
410 possibly by force and/or after the file has been removed from the
410 possibly by force and/or after the file has been removed from the
411 filesystem. Return 0 on success, 1 on any warning.
411 filesystem. Return 0 on success, 1 on any warning.
412 """
412 """
413 warnings.append(
413 warnings.append(
414 _(b"warning: removefiles not implemented (%s)") % self._path
414 _(b"warning: removefiles not implemented (%s)") % self._path
415 )
415 )
416 return 1
416 return 1
417
417
418 def revert(self, substate, *pats, **opts):
418 def revert(self, substate, *pats, **opts):
419 self.ui.warn(
419 self.ui.warn(
420 _(b'%s: reverting %s subrepos is unsupported\n')
420 _(b'%s: reverting %s subrepos is unsupported\n')
421 % (substate[0], substate[2])
421 % (substate[0], substate[2])
422 )
422 )
423 return []
423 return []
424
424
425 def shortid(self, revid):
425 def shortid(self, revid):
426 return revid
426 return revid
427
427
428 def unshare(self):
428 def unshare(self):
429 """
429 """
430 convert this repository from shared to normal storage.
430 convert this repository from shared to normal storage.
431 """
431 """
432
432
433 def verify(self, onpush=False):
433 def verify(self, onpush=False):
434 """verify the revision of this repository that is held in `_state` is
434 """verify the revision of this repository that is held in `_state` is
435 present and not hidden. Return 0 on success or warning, 1 on any
435 present and not hidden. Return 0 on success or warning, 1 on any
436 error. In the case of ``onpush``, warnings or errors will raise an
436 error. In the case of ``onpush``, warnings or errors will raise an
437 exception if the result of pushing would be a broken remote repository.
437 exception if the result of pushing would be a broken remote repository.
438 """
438 """
439 return 0
439 return 0
440
440
441 @propertycache
441 @propertycache
442 def wvfs(self):
442 def wvfs(self):
443 """return vfs to access the working directory of this subrepository"""
443 """return vfs to access the working directory of this subrepository"""
444 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
444 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
445
445
446 @propertycache
446 @propertycache
447 def _relpath(self):
447 def _relpath(self):
448 """return path to this subrepository as seen from outermost repository"""
448 """return path to this subrepository as seen from outermost repository"""
449 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
449 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
450
450
451
451
452 class hgsubrepo(abstractsubrepo):
452 class hgsubrepo(abstractsubrepo):
453 def __init__(self, ctx, path, state, allowcreate):
453 def __init__(self, ctx, path, state, allowcreate):
454 super(hgsubrepo, self).__init__(ctx, path)
454 super(hgsubrepo, self).__init__(ctx, path)
455 self._state = state
455 self._state = state
456 r = ctx.repo()
456 r = ctx.repo()
457 root = r.wjoin(util.localpath(path))
457 root = r.wjoin(util.localpath(path))
458 create = allowcreate and not r.wvfs.exists(b'%s/.hg' % path)
458 create = allowcreate and not r.wvfs.exists(b'%s/.hg' % path)
459 # repository constructor does expand variables in path, which is
459 # repository constructor does expand variables in path, which is
460 # unsafe since subrepo path might come from untrusted source.
460 # unsafe since subrepo path might come from untrusted source.
461 if os.path.realpath(util.expandpath(root)) != root:
461 if os.path.realpath(util.expandpath(root)) != root:
462 raise error.Abort(
462 raise error.Abort(
463 _(b'subrepo path contains illegal component: %s') % path
463 _(b'subrepo path contains illegal component: %s') % path
464 )
464 )
465 self._repo = hg.repository(r.baseui, root, create=create)
465 self._repo = hg.repository(r.baseui, root, create=create)
466 if self._repo.root != root:
466 if self._repo.root != root:
467 raise error.ProgrammingError(
467 raise error.ProgrammingError(
468 b'failed to reject unsafe subrepo '
468 b'failed to reject unsafe subrepo '
469 b'path: %s (expanded to %s)' % (root, self._repo.root)
469 b'path: %s (expanded to %s)' % (root, self._repo.root)
470 )
470 )
471
471
472 # Propagate the parent's --hidden option
472 # Propagate the parent's --hidden option
473 if r is r.unfiltered():
473 if r is r.unfiltered():
474 self._repo = self._repo.unfiltered()
474 self._repo = self._repo.unfiltered()
475
475
476 self.ui = self._repo.ui
476 self.ui = self._repo.ui
477 for s, k in [(b'ui', b'commitsubrepos')]:
477 for s, k in [(b'ui', b'commitsubrepos')]:
478 v = r.ui.config(s, k)
478 v = r.ui.config(s, k)
479 if v:
479 if v:
480 self.ui.setconfig(s, k, v, b'subrepo')
480 self.ui.setconfig(s, k, v, b'subrepo')
481 # internal config: ui._usedassubrepo
481 # internal config: ui._usedassubrepo
482 self.ui.setconfig(b'ui', b'_usedassubrepo', b'True', b'subrepo')
482 self.ui.setconfig(b'ui', b'_usedassubrepo', b'True', b'subrepo')
483 self._initrepo(r, state[0], create)
483 self._initrepo(r, state[0], create)
484
484
485 @annotatesubrepoerror
485 @annotatesubrepoerror
486 def addwebdirpath(self, serverpath, webconf):
486 def addwebdirpath(self, serverpath, webconf):
487 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
487 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
488
488
489 def storeclean(self, path):
489 def storeclean(self, path):
490 with self._repo.lock():
490 with self._repo.lock():
491 return self._storeclean(path)
491 return self._storeclean(path)
492
492
493 def _storeclean(self, path):
493 def _storeclean(self, path):
494 clean = True
494 clean = True
495 itercache = self._calcstorehash(path)
495 itercache = self._calcstorehash(path)
496 for filehash in self._readstorehashcache(path):
496 for filehash in self._readstorehashcache(path):
497 if filehash != next(itercache, None):
497 if filehash != next(itercache, None):
498 clean = False
498 clean = False
499 break
499 break
500 if clean:
500 if clean:
501 # if not empty:
501 # if not empty:
502 # the cached and current pull states have a different size
502 # the cached and current pull states have a different size
503 clean = next(itercache, None) is None
503 clean = next(itercache, None) is None
504 return clean
504 return clean
505
505
506 def _calcstorehash(self, remotepath):
506 def _calcstorehash(self, remotepath):
507 """calculate a unique "store hash"
507 """calculate a unique "store hash"
508
508
509 This method is used to to detect when there are changes that may
509 This method is used to to detect when there are changes that may
510 require a push to a given remote path."""
510 require a push to a given remote path."""
511 # sort the files that will be hashed in increasing (likely) file size
511 # sort the files that will be hashed in increasing (likely) file size
512 filelist = (b'bookmarks', b'store/phaseroots', b'store/00changelog.i')
512 filelist = (b'bookmarks', b'store/phaseroots', b'store/00changelog.i')
513 yield b'# %s\n' % _expandedabspath(remotepath)
513 yield b'# %s\n' % _expandedabspath(remotepath)
514 vfs = self._repo.vfs
514 vfs = self._repo.vfs
515 for relname in filelist:
515 for relname in filelist:
516 filehash = hex(hashutil.sha1(vfs.tryread(relname)).digest())
516 filehash = hex(hashutil.sha1(vfs.tryread(relname)).digest())
517 yield b'%s = %s\n' % (relname, filehash)
517 yield b'%s = %s\n' % (relname, filehash)
518
518
519 @propertycache
519 @propertycache
520 def _cachestorehashvfs(self):
520 def _cachestorehashvfs(self):
521 return vfsmod.vfs(self._repo.vfs.join(b'cache/storehash'))
521 return vfsmod.vfs(self._repo.vfs.join(b'cache/storehash'))
522
522
523 def _readstorehashcache(self, remotepath):
523 def _readstorehashcache(self, remotepath):
524 '''read the store hash cache for a given remote repository'''
524 '''read the store hash cache for a given remote repository'''
525 cachefile = _getstorehashcachename(remotepath)
525 cachefile = _getstorehashcachename(remotepath)
526 return self._cachestorehashvfs.tryreadlines(cachefile, b'r')
526 return self._cachestorehashvfs.tryreadlines(cachefile, b'r')
527
527
528 def _cachestorehash(self, remotepath):
528 def _cachestorehash(self, remotepath):
529 """cache the current store hash
529 """cache the current store hash
530
530
531 Each remote repo requires its own store hash cache, because a subrepo
531 Each remote repo requires its own store hash cache, because a subrepo
532 store may be "clean" versus a given remote repo, but not versus another
532 store may be "clean" versus a given remote repo, but not versus another
533 """
533 """
534 cachefile = _getstorehashcachename(remotepath)
534 cachefile = _getstorehashcachename(remotepath)
535 with self._repo.lock():
535 with self._repo.lock():
536 storehash = list(self._calcstorehash(remotepath))
536 storehash = list(self._calcstorehash(remotepath))
537 vfs = self._cachestorehashvfs
537 vfs = self._cachestorehashvfs
538 vfs.writelines(cachefile, storehash, mode=b'wb', notindexed=True)
538 vfs.writelines(cachefile, storehash, mode=b'wb', notindexed=True)
539
539
540 def _getctx(self):
540 def _getctx(self):
541 """fetch the context for this subrepo revision, possibly a workingctx"""
541 """fetch the context for this subrepo revision, possibly a workingctx"""
542 if self._ctx.rev() is None:
542 if self._ctx.rev() is None:
543 return self._repo[None] # workingctx if parent is workingctx
543 return self._repo[None] # workingctx if parent is workingctx
544 else:
544 else:
545 rev = self._state[1]
545 rev = self._state[1]
546 return self._repo[rev]
546 return self._repo[rev]
547
547
548 @annotatesubrepoerror
548 @annotatesubrepoerror
549 def _initrepo(self, parentrepo, source, create):
549 def _initrepo(self, parentrepo, source, create):
550 self._repo._subparent = parentrepo
550 self._repo._subparent = parentrepo
551 self._repo._subsource = source
551 self._repo._subsource = source
552
552
553 if create:
553 if create:
554 lines = [b'[paths]\n']
554 lines = [b'[paths]\n']
555
555
556 def addpathconfig(key, value):
556 def addpathconfig(key, value):
557 if value:
557 if value:
558 lines.append(b'%s = %s\n' % (key, value))
558 lines.append(b'%s = %s\n' % (key, value))
559 self.ui.setconfig(b'paths', key, value, b'subrepo')
559 self.ui.setconfig(b'paths', key, value, b'subrepo')
560
560
561 defpath = _abssource(self._repo, abort=False)
561 defpath = _abssource(self._repo, abort=False)
562 defpushpath = _abssource(self._repo, True, abort=False)
562 defpushpath = _abssource(self._repo, True, abort=False)
563 addpathconfig(b'default', defpath)
563 addpathconfig(b'default', defpath)
564 if defpath != defpushpath:
564 if defpath != defpushpath:
565 addpathconfig(b'default-push', defpushpath)
565 addpathconfig(b'default-push', defpushpath)
566
566
567 self._repo.vfs.write(b'hgrc', util.tonativeeol(b''.join(lines)))
567 self._repo.vfs.write(b'hgrc', util.tonativeeol(b''.join(lines)))
568
568
569 @annotatesubrepoerror
569 @annotatesubrepoerror
570 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
570 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
571 return cmdutil.add(
571 return cmdutil.add(
572 ui, self._repo, match, prefix, uipathfn, explicitonly, **opts
572 ui, self._repo, match, prefix, uipathfn, explicitonly, **opts
573 )
573 )
574
574
575 @annotatesubrepoerror
575 @annotatesubrepoerror
576 def addremove(self, m, prefix, uipathfn, opts):
576 def addremove(self, m, prefix, uipathfn, opts):
577 # In the same way as sub directories are processed, once in a subrepo,
577 # In the same way as sub directories are processed, once in a subrepo,
578 # always entry any of its subrepos. Don't corrupt the options that will
578 # always entry any of its subrepos. Don't corrupt the options that will
579 # be used to process sibling subrepos however.
579 # be used to process sibling subrepos however.
580 opts = copy.copy(opts)
580 opts = copy.copy(opts)
581 opts[b'subrepos'] = True
581 opts[b'subrepos'] = True
582 return scmutil.addremove(self._repo, m, prefix, uipathfn, opts)
582 return scmutil.addremove(self._repo, m, prefix, uipathfn, opts)
583
583
584 @annotatesubrepoerror
584 @annotatesubrepoerror
585 def cat(self, match, fm, fntemplate, prefix, **opts):
585 def cat(self, match, fm, fntemplate, prefix, **opts):
586 rev = self._state[1]
586 rev = self._state[1]
587 ctx = self._repo[rev]
587 ctx = self._repo[rev]
588 return cmdutil.cat(
588 return cmdutil.cat(
589 self.ui, self._repo, ctx, match, fm, fntemplate, prefix, **opts
589 self.ui, self._repo, ctx, match, fm, fntemplate, prefix, **opts
590 )
590 )
591
591
592 @annotatesubrepoerror
592 @annotatesubrepoerror
593 def status(self, rev2, **opts):
593 def status(self, rev2, **opts):
594 try:
594 try:
595 rev1 = self._state[1]
595 rev1 = self._state[1]
596 ctx1 = self._repo[rev1]
596 ctx1 = self._repo[rev1]
597 ctx2 = self._repo[rev2]
597 ctx2 = self._repo[rev2]
598 return self._repo.status(ctx1, ctx2, **opts)
598 return self._repo.status(ctx1, ctx2, **opts)
599 except error.RepoLookupError as inst:
599 except error.RepoLookupError as inst:
600 self.ui.warn(
600 self.ui.warn(
601 _(b'warning: error "%s" in subrepository "%s"\n')
601 _(b'warning: error "%s" in subrepository "%s"\n')
602 % (inst, subrelpath(self))
602 % (inst, subrelpath(self))
603 )
603 )
604 return scmutil.status([], [], [], [], [], [], [])
604 return scmutil.status([], [], [], [], [], [], [])
605
605
606 @annotatesubrepoerror
606 @annotatesubrepoerror
607 def diff(self, ui, diffopts, node2, match, prefix, **opts):
607 def diff(self, ui, diffopts, node2, match, prefix, **opts):
608 try:
608 try:
609 node1 = bin(self._state[1])
609 node1 = bin(self._state[1])
610 # We currently expect node2 to come from substate and be
610 # We currently expect node2 to come from substate and be
611 # in hex format
611 # in hex format
612 if node2 is not None:
612 if node2 is not None:
613 node2 = bin(node2)
613 node2 = bin(node2)
614 logcmdutil.diffordiffstat(
614 logcmdutil.diffordiffstat(
615 ui,
615 ui,
616 self._repo,
616 self._repo,
617 diffopts,
617 diffopts,
618 self._repo[node1],
618 self._repo[node1],
619 self._repo[node2],
619 self._repo[node2],
620 match,
620 match,
621 prefix=prefix,
621 prefix=prefix,
622 listsubrepos=True,
622 listsubrepos=True,
623 **opts
623 **opts
624 )
624 )
625 except error.RepoLookupError as inst:
625 except error.RepoLookupError as inst:
626 self.ui.warn(
626 self.ui.warn(
627 _(b'warning: error "%s" in subrepository "%s"\n')
627 _(b'warning: error "%s" in subrepository "%s"\n')
628 % (inst, subrelpath(self))
628 % (inst, subrelpath(self))
629 )
629 )
630
630
631 @annotatesubrepoerror
631 @annotatesubrepoerror
632 def archive(self, archiver, prefix, match=None, decode=True):
632 def archive(self, archiver, prefix, match=None, decode=True):
633 self._get(self._state + (b'hg',))
633 self._get(self._state + (b'hg',))
634 files = self.files()
634 files = self.files()
635 if match:
635 if match:
636 files = [f for f in files if match(f)]
636 files = [f for f in files if match(f)]
637 rev = self._state[1]
637 rev = self._state[1]
638 ctx = self._repo[rev]
638 ctx = self._repo[rev]
639 scmutil.prefetchfiles(
639 scmutil.prefetchfiles(
640 self._repo, [(ctx.rev(), scmutil.matchfiles(self._repo, files))]
640 self._repo, [(ctx.rev(), scmutil.matchfiles(self._repo, files))]
641 )
641 )
642 total = abstractsubrepo.archive(self, archiver, prefix, match)
642 total = abstractsubrepo.archive(self, archiver, prefix, match)
643 for subpath in ctx.substate:
643 for subpath in ctx.substate:
644 s = subrepo(ctx, subpath, True)
644 s = subrepo(ctx, subpath, True)
645 submatch = matchmod.subdirmatcher(subpath, match)
645 submatch = matchmod.subdirmatcher(subpath, match)
646 subprefix = prefix + subpath + b'/'
646 subprefix = prefix + subpath + b'/'
647 total += s.archive(archiver, subprefix, submatch, decode)
647 total += s.archive(archiver, subprefix, submatch, decode)
648 return total
648 return total
649
649
650 @annotatesubrepoerror
650 @annotatesubrepoerror
651 def dirty(self, ignoreupdate=False, missing=False):
651 def dirty(self, ignoreupdate=False, missing=False):
652 r = self._state[1]
652 r = self._state[1]
653 if r == b'' and not ignoreupdate: # no state recorded
653 if r == b'' and not ignoreupdate: # no state recorded
654 return True
654 return True
655 w = self._repo[None]
655 w = self._repo[None]
656 if r != w.p1().hex() and not ignoreupdate:
656 if r != w.p1().hex() and not ignoreupdate:
657 # different version checked out
657 # different version checked out
658 return True
658 return True
659 return w.dirty(missing=missing) # working directory changed
659 return w.dirty(missing=missing) # working directory changed
660
660
661 def basestate(self):
661 def basestate(self):
662 return self._repo[b'.'].hex()
662 return self._repo[b'.'].hex()
663
663
664 def checknested(self, path):
664 def checknested(self, path):
665 return self._repo._checknested(self._repo.wjoin(path))
665 return self._repo._checknested(self._repo.wjoin(path))
666
666
667 @annotatesubrepoerror
667 @annotatesubrepoerror
668 def commit(self, text, user, date):
668 def commit(self, text, user, date):
669 # don't bother committing in the subrepo if it's only been
669 # don't bother committing in the subrepo if it's only been
670 # updated
670 # updated
671 if not self.dirty(True):
671 if not self.dirty(True):
672 return self._repo[b'.'].hex()
672 return self._repo[b'.'].hex()
673 self.ui.debug(b"committing subrepo %s\n" % subrelpath(self))
673 self.ui.debug(b"committing subrepo %s\n" % subrelpath(self))
674 n = self._repo.commit(text, user, date)
674 n = self._repo.commit(text, user, date)
675 if not n:
675 if not n:
676 return self._repo[b'.'].hex() # different version checked out
676 return self._repo[b'.'].hex() # different version checked out
677 return hex(n)
677 return hex(n)
678
678
679 @annotatesubrepoerror
679 @annotatesubrepoerror
680 def phase(self, state):
680 def phase(self, state):
681 return self._repo[state or b'.'].phase()
681 return self._repo[state or b'.'].phase()
682
682
683 @annotatesubrepoerror
683 @annotatesubrepoerror
684 def remove(self):
684 def remove(self):
685 # we can't fully delete the repository as it may contain
685 # we can't fully delete the repository as it may contain
686 # local-only history
686 # local-only history
687 self.ui.note(_(b'removing subrepo %s\n') % subrelpath(self))
687 self.ui.note(_(b'removing subrepo %s\n') % subrelpath(self))
688 hg.clean(self._repo, nullid, False)
688 hg.clean(self._repo, nullid, False)
689
689
690 def _get(self, state):
690 def _get(self, state):
691 source, revision, kind = state
691 source, revision, kind = state
692 parentrepo = self._repo._subparent
692 parentrepo = self._repo._subparent
693
693
694 if revision in self._repo.unfiltered():
694 if revision in self._repo.unfiltered():
695 # Allow shared subrepos tracked at null to setup the sharedpath
695 # Allow shared subrepos tracked at null to setup the sharedpath
696 if len(self._repo) != 0 or not parentrepo.shared():
696 if len(self._repo) != 0 or not parentrepo.shared():
697 return True
697 return True
698 self._repo._subsource = source
698 self._repo._subsource = source
699 srcurl = _abssource(self._repo)
699 srcurl = _abssource(self._repo)
700
700
701 # Defer creating the peer until after the status message is logged, in
701 # Defer creating the peer until after the status message is logged, in
702 # case there are network problems.
702 # case there are network problems.
703 getpeer = lambda: hg.peer(self._repo, {}, srcurl)
703 getpeer = lambda: hg.peer(self._repo, {}, srcurl)
704
704
705 if len(self._repo) == 0:
705 if len(self._repo) == 0:
706 # use self._repo.vfs instead of self.wvfs to remove .hg only
706 # use self._repo.vfs instead of self.wvfs to remove .hg only
707 self._repo.vfs.rmtree()
707 self._repo.vfs.rmtree()
708
708
709 # A remote subrepo could be shared if there is a local copy
709 # A remote subrepo could be shared if there is a local copy
710 # relative to the parent's share source. But clone pooling doesn't
710 # relative to the parent's share source. But clone pooling doesn't
711 # assemble the repos in a tree, so that can't be consistently done.
711 # assemble the repos in a tree, so that can't be consistently done.
712 # A simpler option is for the user to configure clone pooling, and
712 # A simpler option is for the user to configure clone pooling, and
713 # work with that.
713 # work with that.
714 if parentrepo.shared() and hg.islocal(srcurl):
714 if parentrepo.shared() and hg.islocal(srcurl):
715 self.ui.status(
715 self.ui.status(
716 _(b'sharing subrepo %s from %s\n')
716 _(b'sharing subrepo %s from %s\n')
717 % (subrelpath(self), srcurl)
717 % (subrelpath(self), srcurl)
718 )
718 )
719 shared = hg.share(
719 peer = getpeer()
720 self._repo._subparent.baseui,
720 try:
721 getpeer(),
721 shared = hg.share(
722 self._repo.root,
722 self._repo._subparent.baseui,
723 update=False,
723 peer,
724 bookmarks=False,
724 self._repo.root,
725 )
725 update=False,
726 bookmarks=False,
727 )
728 finally:
729 peer.close()
726 self._repo = shared.local()
730 self._repo = shared.local()
727 else:
731 else:
728 # TODO: find a common place for this and this code in the
732 # TODO: find a common place for this and this code in the
729 # share.py wrap of the clone command.
733 # share.py wrap of the clone command.
730 if parentrepo.shared():
734 if parentrepo.shared():
731 pool = self.ui.config(b'share', b'pool')
735 pool = self.ui.config(b'share', b'pool')
732 if pool:
736 if pool:
733 pool = util.expandpath(pool)
737 pool = util.expandpath(pool)
734
738
735 shareopts = {
739 shareopts = {
736 b'pool': pool,
740 b'pool': pool,
737 b'mode': self.ui.config(b'share', b'poolnaming'),
741 b'mode': self.ui.config(b'share', b'poolnaming'),
738 }
742 }
739 else:
743 else:
740 shareopts = {}
744 shareopts = {}
741
745
742 self.ui.status(
746 self.ui.status(
743 _(b'cloning subrepo %s from %s\n')
747 _(b'cloning subrepo %s from %s\n')
744 % (subrelpath(self), util.hidepassword(srcurl))
748 % (subrelpath(self), util.hidepassword(srcurl))
745 )
749 )
746 other, cloned = hg.clone(
750 peer = getpeer()
747 self._repo._subparent.baseui,
751 try:
748 {},
752 other, cloned = hg.clone(
749 getpeer(),
753 self._repo._subparent.baseui,
750 self._repo.root,
754 {},
751 update=False,
755 peer,
752 shareopts=shareopts,
756 self._repo.root,
753 )
757 update=False,
758 shareopts=shareopts,
759 )
760 finally:
761 peer.close()
754 self._repo = cloned.local()
762 self._repo = cloned.local()
755 self._initrepo(parentrepo, source, create=True)
763 self._initrepo(parentrepo, source, create=True)
756 self._cachestorehash(srcurl)
764 self._cachestorehash(srcurl)
757 else:
765 else:
758 self.ui.status(
766 self.ui.status(
759 _(b'pulling subrepo %s from %s\n')
767 _(b'pulling subrepo %s from %s\n')
760 % (subrelpath(self), util.hidepassword(srcurl))
768 % (subrelpath(self), util.hidepassword(srcurl))
761 )
769 )
762 cleansub = self.storeclean(srcurl)
770 cleansub = self.storeclean(srcurl)
763 exchange.pull(self._repo, getpeer())
771 peer = getpeer()
772 try:
773 exchange.pull(self._repo, peer)
774 finally:
775 peer.close()
764 if cleansub:
776 if cleansub:
765 # keep the repo clean after pull
777 # keep the repo clean after pull
766 self._cachestorehash(srcurl)
778 self._cachestorehash(srcurl)
767 return False
779 return False
768
780
769 @annotatesubrepoerror
781 @annotatesubrepoerror
770 def get(self, state, overwrite=False):
782 def get(self, state, overwrite=False):
771 inrepo = self._get(state)
783 inrepo = self._get(state)
772 source, revision, kind = state
784 source, revision, kind = state
773 repo = self._repo
785 repo = self._repo
774 repo.ui.debug(b"getting subrepo %s\n" % self._path)
786 repo.ui.debug(b"getting subrepo %s\n" % self._path)
775 if inrepo:
787 if inrepo:
776 urepo = repo.unfiltered()
788 urepo = repo.unfiltered()
777 ctx = urepo[revision]
789 ctx = urepo[revision]
778 if ctx.hidden():
790 if ctx.hidden():
779 urepo.ui.warn(
791 urepo.ui.warn(
780 _(b'revision %s in subrepository "%s" is hidden\n')
792 _(b'revision %s in subrepository "%s" is hidden\n')
781 % (revision[0:12], self._path)
793 % (revision[0:12], self._path)
782 )
794 )
783 repo = urepo
795 repo = urepo
784 if overwrite:
796 if overwrite:
785 merge.clean_update(repo[revision])
797 merge.clean_update(repo[revision])
786 else:
798 else:
787 merge.update(repo[revision])
799 merge.update(repo[revision])
788
800
789 @annotatesubrepoerror
801 @annotatesubrepoerror
790 def merge(self, state):
802 def merge(self, state):
791 self._get(state)
803 self._get(state)
792 cur = self._repo[b'.']
804 cur = self._repo[b'.']
793 dst = self._repo[state[1]]
805 dst = self._repo[state[1]]
794 anc = dst.ancestor(cur)
806 anc = dst.ancestor(cur)
795
807
796 def mergefunc():
808 def mergefunc():
797 if anc == cur and dst.branch() == cur.branch():
809 if anc == cur and dst.branch() == cur.branch():
798 self.ui.debug(
810 self.ui.debug(
799 b'updating subrepository "%s"\n' % subrelpath(self)
811 b'updating subrepository "%s"\n' % subrelpath(self)
800 )
812 )
801 hg.update(self._repo, state[1])
813 hg.update(self._repo, state[1])
802 elif anc == dst:
814 elif anc == dst:
803 self.ui.debug(
815 self.ui.debug(
804 b'skipping subrepository "%s"\n' % subrelpath(self)
816 b'skipping subrepository "%s"\n' % subrelpath(self)
805 )
817 )
806 else:
818 else:
807 self.ui.debug(
819 self.ui.debug(
808 b'merging subrepository "%s"\n' % subrelpath(self)
820 b'merging subrepository "%s"\n' % subrelpath(self)
809 )
821 )
810 hg.merge(dst, remind=False)
822 hg.merge(dst, remind=False)
811
823
812 wctx = self._repo[None]
824 wctx = self._repo[None]
813 if self.dirty():
825 if self.dirty():
814 if anc != dst:
826 if anc != dst:
815 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
827 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
816 mergefunc()
828 mergefunc()
817 else:
829 else:
818 mergefunc()
830 mergefunc()
819 else:
831 else:
820 mergefunc()
832 mergefunc()
821
833
822 @annotatesubrepoerror
834 @annotatesubrepoerror
823 def push(self, opts):
835 def push(self, opts):
824 force = opts.get(b'force')
836 force = opts.get(b'force')
825 newbranch = opts.get(b'new_branch')
837 newbranch = opts.get(b'new_branch')
826 ssh = opts.get(b'ssh')
838 ssh = opts.get(b'ssh')
827
839
828 # push subrepos depth-first for coherent ordering
840 # push subrepos depth-first for coherent ordering
829 c = self._repo[b'.']
841 c = self._repo[b'.']
830 subs = c.substate # only repos that are committed
842 subs = c.substate # only repos that are committed
831 for s in sorted(subs):
843 for s in sorted(subs):
832 if c.sub(s).push(opts) == 0:
844 if c.sub(s).push(opts) == 0:
833 return False
845 return False
834
846
835 dsturl = _abssource(self._repo, True)
847 dsturl = _abssource(self._repo, True)
836 if not force:
848 if not force:
837 if self.storeclean(dsturl):
849 if self.storeclean(dsturl):
838 self.ui.status(
850 self.ui.status(
839 _(b'no changes made to subrepo %s since last push to %s\n')
851 _(b'no changes made to subrepo %s since last push to %s\n')
840 % (subrelpath(self), util.hidepassword(dsturl))
852 % (subrelpath(self), util.hidepassword(dsturl))
841 )
853 )
842 return None
854 return None
843 self.ui.status(
855 self.ui.status(
844 _(b'pushing subrepo %s to %s\n')
856 _(b'pushing subrepo %s to %s\n')
845 % (subrelpath(self), util.hidepassword(dsturl))
857 % (subrelpath(self), util.hidepassword(dsturl))
846 )
858 )
847 other = hg.peer(self._repo, {b'ssh': ssh}, dsturl)
859 other = hg.peer(self._repo, {b'ssh': ssh}, dsturl)
848 res = exchange.push(self._repo, other, force, newbranch=newbranch)
860 try:
861 res = exchange.push(self._repo, other, force, newbranch=newbranch)
862 finally:
863 other.close()
849
864
850 # the repo is now clean
865 # the repo is now clean
851 self._cachestorehash(dsturl)
866 self._cachestorehash(dsturl)
852 return res.cgresult
867 return res.cgresult
853
868
854 @annotatesubrepoerror
869 @annotatesubrepoerror
855 def outgoing(self, ui, dest, opts):
870 def outgoing(self, ui, dest, opts):
856 if b'rev' in opts or b'branch' in opts:
871 if b'rev' in opts or b'branch' in opts:
857 opts = copy.copy(opts)
872 opts = copy.copy(opts)
858 opts.pop(b'rev', None)
873 opts.pop(b'rev', None)
859 opts.pop(b'branch', None)
874 opts.pop(b'branch', None)
860 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
875 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
861
876
862 @annotatesubrepoerror
877 @annotatesubrepoerror
863 def incoming(self, ui, source, opts):
878 def incoming(self, ui, source, opts):
864 if b'rev' in opts or b'branch' in opts:
879 if b'rev' in opts or b'branch' in opts:
865 opts = copy.copy(opts)
880 opts = copy.copy(opts)
866 opts.pop(b'rev', None)
881 opts.pop(b'rev', None)
867 opts.pop(b'branch', None)
882 opts.pop(b'branch', None)
868 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
883 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
869
884
870 @annotatesubrepoerror
885 @annotatesubrepoerror
871 def files(self):
886 def files(self):
872 rev = self._state[1]
887 rev = self._state[1]
873 ctx = self._repo[rev]
888 ctx = self._repo[rev]
874 return ctx.manifest().keys()
889 return ctx.manifest().keys()
875
890
876 def filedata(self, name, decode):
891 def filedata(self, name, decode):
877 rev = self._state[1]
892 rev = self._state[1]
878 data = self._repo[rev][name].data()
893 data = self._repo[rev][name].data()
879 if decode:
894 if decode:
880 data = self._repo.wwritedata(name, data)
895 data = self._repo.wwritedata(name, data)
881 return data
896 return data
882
897
883 def fileflags(self, name):
898 def fileflags(self, name):
884 rev = self._state[1]
899 rev = self._state[1]
885 ctx = self._repo[rev]
900 ctx = self._repo[rev]
886 return ctx.flags(name)
901 return ctx.flags(name)
887
902
888 @annotatesubrepoerror
903 @annotatesubrepoerror
889 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
904 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
890 # If the parent context is a workingctx, use the workingctx here for
905 # If the parent context is a workingctx, use the workingctx here for
891 # consistency.
906 # consistency.
892 if self._ctx.rev() is None:
907 if self._ctx.rev() is None:
893 ctx = self._repo[None]
908 ctx = self._repo[None]
894 else:
909 else:
895 rev = self._state[1]
910 rev = self._state[1]
896 ctx = self._repo[rev]
911 ctx = self._repo[rev]
897 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt, subrepos)
912 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt, subrepos)
898
913
899 @annotatesubrepoerror
914 @annotatesubrepoerror
900 def matchfileset(self, cwd, expr, badfn=None):
915 def matchfileset(self, cwd, expr, badfn=None):
901 if self._ctx.rev() is None:
916 if self._ctx.rev() is None:
902 ctx = self._repo[None]
917 ctx = self._repo[None]
903 else:
918 else:
904 rev = self._state[1]
919 rev = self._state[1]
905 ctx = self._repo[rev]
920 ctx = self._repo[rev]
906
921
907 matchers = [ctx.matchfileset(cwd, expr, badfn=badfn)]
922 matchers = [ctx.matchfileset(cwd, expr, badfn=badfn)]
908
923
909 for subpath in ctx.substate:
924 for subpath in ctx.substate:
910 sub = ctx.sub(subpath)
925 sub = ctx.sub(subpath)
911
926
912 try:
927 try:
913 sm = sub.matchfileset(cwd, expr, badfn=badfn)
928 sm = sub.matchfileset(cwd, expr, badfn=badfn)
914 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn)
929 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn)
915 matchers.append(pm)
930 matchers.append(pm)
916 except error.LookupError:
931 except error.LookupError:
917 self.ui.status(
932 self.ui.status(
918 _(b"skipping missing subrepository: %s\n")
933 _(b"skipping missing subrepository: %s\n")
919 % self.wvfs.reljoin(reporelpath(self), subpath)
934 % self.wvfs.reljoin(reporelpath(self), subpath)
920 )
935 )
921 if len(matchers) == 1:
936 if len(matchers) == 1:
922 return matchers[0]
937 return matchers[0]
923 return matchmod.unionmatcher(matchers)
938 return matchmod.unionmatcher(matchers)
924
939
925 def walk(self, match):
940 def walk(self, match):
926 ctx = self._repo[None]
941 ctx = self._repo[None]
927 return ctx.walk(match)
942 return ctx.walk(match)
928
943
929 @annotatesubrepoerror
944 @annotatesubrepoerror
930 def forget(self, match, prefix, uipathfn, dryrun, interactive):
945 def forget(self, match, prefix, uipathfn, dryrun, interactive):
931 return cmdutil.forget(
946 return cmdutil.forget(
932 self.ui,
947 self.ui,
933 self._repo,
948 self._repo,
934 match,
949 match,
935 prefix,
950 prefix,
936 uipathfn,
951 uipathfn,
937 True,
952 True,
938 dryrun=dryrun,
953 dryrun=dryrun,
939 interactive=interactive,
954 interactive=interactive,
940 )
955 )
941
956
942 @annotatesubrepoerror
957 @annotatesubrepoerror
943 def removefiles(
958 def removefiles(
944 self,
959 self,
945 matcher,
960 matcher,
946 prefix,
961 prefix,
947 uipathfn,
962 uipathfn,
948 after,
963 after,
949 force,
964 force,
950 subrepos,
965 subrepos,
951 dryrun,
966 dryrun,
952 warnings,
967 warnings,
953 ):
968 ):
954 return cmdutil.remove(
969 return cmdutil.remove(
955 self.ui,
970 self.ui,
956 self._repo,
971 self._repo,
957 matcher,
972 matcher,
958 prefix,
973 prefix,
959 uipathfn,
974 uipathfn,
960 after,
975 after,
961 force,
976 force,
962 subrepos,
977 subrepos,
963 dryrun,
978 dryrun,
964 )
979 )
965
980
966 @annotatesubrepoerror
981 @annotatesubrepoerror
967 def revert(self, substate, *pats, **opts):
982 def revert(self, substate, *pats, **opts):
968 # reverting a subrepo is a 2 step process:
983 # reverting a subrepo is a 2 step process:
969 # 1. if the no_backup is not set, revert all modified
984 # 1. if the no_backup is not set, revert all modified
970 # files inside the subrepo
985 # files inside the subrepo
971 # 2. update the subrepo to the revision specified in
986 # 2. update the subrepo to the revision specified in
972 # the corresponding substate dictionary
987 # the corresponding substate dictionary
973 self.ui.status(_(b'reverting subrepo %s\n') % substate[0])
988 self.ui.status(_(b'reverting subrepo %s\n') % substate[0])
974 if not opts.get('no_backup'):
989 if not opts.get('no_backup'):
975 # Revert all files on the subrepo, creating backups
990 # Revert all files on the subrepo, creating backups
976 # Note that this will not recursively revert subrepos
991 # Note that this will not recursively revert subrepos
977 # We could do it if there was a set:subrepos() predicate
992 # We could do it if there was a set:subrepos() predicate
978 opts = opts.copy()
993 opts = opts.copy()
979 opts['date'] = None
994 opts['date'] = None
980 opts['rev'] = substate[1]
995 opts['rev'] = substate[1]
981
996
982 self.filerevert(*pats, **opts)
997 self.filerevert(*pats, **opts)
983
998
984 # Update the repo to the revision specified in the given substate
999 # Update the repo to the revision specified in the given substate
985 if not opts.get('dry_run'):
1000 if not opts.get('dry_run'):
986 self.get(substate, overwrite=True)
1001 self.get(substate, overwrite=True)
987
1002
988 def filerevert(self, *pats, **opts):
1003 def filerevert(self, *pats, **opts):
989 ctx = self._repo[opts['rev']]
1004 ctx = self._repo[opts['rev']]
990 if opts.get('all'):
1005 if opts.get('all'):
991 pats = [b'set:modified()']
1006 pats = [b'set:modified()']
992 else:
1007 else:
993 pats = []
1008 pats = []
994 cmdutil.revert(self.ui, self._repo, ctx, *pats, **opts)
1009 cmdutil.revert(self.ui, self._repo, ctx, *pats, **opts)
995
1010
996 def shortid(self, revid):
1011 def shortid(self, revid):
997 return revid[:12]
1012 return revid[:12]
998
1013
999 @annotatesubrepoerror
1014 @annotatesubrepoerror
1000 def unshare(self):
1015 def unshare(self):
1001 # subrepo inherently violates our import layering rules
1016 # subrepo inherently violates our import layering rules
1002 # because it wants to make repo objects from deep inside the stack
1017 # because it wants to make repo objects from deep inside the stack
1003 # so we manually delay the circular imports to not break
1018 # so we manually delay the circular imports to not break
1004 # scripts that don't use our demand-loading
1019 # scripts that don't use our demand-loading
1005 global hg
1020 global hg
1006 from . import hg as h
1021 from . import hg as h
1007
1022
1008 hg = h
1023 hg = h
1009
1024
1010 # Nothing prevents a user from sharing in a repo, and then making that a
1025 # Nothing prevents a user from sharing in a repo, and then making that a
1011 # subrepo. Alternately, the previous unshare attempt may have failed
1026 # subrepo. Alternately, the previous unshare attempt may have failed
1012 # part way through. So recurse whether or not this layer is shared.
1027 # part way through. So recurse whether or not this layer is shared.
1013 if self._repo.shared():
1028 if self._repo.shared():
1014 self.ui.status(_(b"unsharing subrepo '%s'\n") % self._relpath)
1029 self.ui.status(_(b"unsharing subrepo '%s'\n") % self._relpath)
1015
1030
1016 hg.unshare(self.ui, self._repo)
1031 hg.unshare(self.ui, self._repo)
1017
1032
1018 def verify(self, onpush=False):
1033 def verify(self, onpush=False):
1019 try:
1034 try:
1020 rev = self._state[1]
1035 rev = self._state[1]
1021 ctx = self._repo.unfiltered()[rev]
1036 ctx = self._repo.unfiltered()[rev]
1022 if ctx.hidden():
1037 if ctx.hidden():
1023 # Since hidden revisions aren't pushed/pulled, it seems worth an
1038 # Since hidden revisions aren't pushed/pulled, it seems worth an
1024 # explicit warning.
1039 # explicit warning.
1025 msg = _(b"subrepo '%s' is hidden in revision %s") % (
1040 msg = _(b"subrepo '%s' is hidden in revision %s") % (
1026 self._relpath,
1041 self._relpath,
1027 short(self._ctx.node()),
1042 short(self._ctx.node()),
1028 )
1043 )
1029
1044
1030 if onpush:
1045 if onpush:
1031 raise error.Abort(msg)
1046 raise error.Abort(msg)
1032 else:
1047 else:
1033 self._repo.ui.warn(b'%s\n' % msg)
1048 self._repo.ui.warn(b'%s\n' % msg)
1034 return 0
1049 return 0
1035 except error.RepoLookupError:
1050 except error.RepoLookupError:
1036 # A missing subrepo revision may be a case of needing to pull it, so
1051 # A missing subrepo revision may be a case of needing to pull it, so
1037 # don't treat this as an error for `hg verify`.
1052 # don't treat this as an error for `hg verify`.
1038 msg = _(b"subrepo '%s' not found in revision %s") % (
1053 msg = _(b"subrepo '%s' not found in revision %s") % (
1039 self._relpath,
1054 self._relpath,
1040 short(self._ctx.node()),
1055 short(self._ctx.node()),
1041 )
1056 )
1042
1057
1043 if onpush:
1058 if onpush:
1044 raise error.Abort(msg)
1059 raise error.Abort(msg)
1045 else:
1060 else:
1046 self._repo.ui.warn(b'%s\n' % msg)
1061 self._repo.ui.warn(b'%s\n' % msg)
1047 return 0
1062 return 0
1048
1063
1049 @propertycache
1064 @propertycache
1050 def wvfs(self):
1065 def wvfs(self):
1051 """return own wvfs for efficiency and consistency"""
1066 """return own wvfs for efficiency and consistency"""
1052 return self._repo.wvfs
1067 return self._repo.wvfs
1053
1068
1054 @propertycache
1069 @propertycache
1055 def _relpath(self):
1070 def _relpath(self):
1056 """return path to this subrepository as seen from outermost repository"""
1071 """return path to this subrepository as seen from outermost repository"""
1057 # Keep consistent dir separators by avoiding vfs.join(self._path)
1072 # Keep consistent dir separators by avoiding vfs.join(self._path)
1058 return reporelpath(self._repo)
1073 return reporelpath(self._repo)
1059
1074
1060
1075
1061 class svnsubrepo(abstractsubrepo):
1076 class svnsubrepo(abstractsubrepo):
1062 def __init__(self, ctx, path, state, allowcreate):
1077 def __init__(self, ctx, path, state, allowcreate):
1063 super(svnsubrepo, self).__init__(ctx, path)
1078 super(svnsubrepo, self).__init__(ctx, path)
1064 self._state = state
1079 self._state = state
1065 self._exe = procutil.findexe(b'svn')
1080 self._exe = procutil.findexe(b'svn')
1066 if not self._exe:
1081 if not self._exe:
1067 raise error.Abort(
1082 raise error.Abort(
1068 _(b"'svn' executable not found for subrepo '%s'") % self._path
1083 _(b"'svn' executable not found for subrepo '%s'") % self._path
1069 )
1084 )
1070
1085
1071 def _svncommand(self, commands, filename=b'', failok=False):
1086 def _svncommand(self, commands, filename=b'', failok=False):
1072 cmd = [self._exe]
1087 cmd = [self._exe]
1073 extrakw = {}
1088 extrakw = {}
1074 if not self.ui.interactive():
1089 if not self.ui.interactive():
1075 # Making stdin be a pipe should prevent svn from behaving
1090 # Making stdin be a pipe should prevent svn from behaving
1076 # interactively even if we can't pass --non-interactive.
1091 # interactively even if we can't pass --non-interactive.
1077 extrakw['stdin'] = subprocess.PIPE
1092 extrakw['stdin'] = subprocess.PIPE
1078 # Starting in svn 1.5 --non-interactive is a global flag
1093 # Starting in svn 1.5 --non-interactive is a global flag
1079 # instead of being per-command, but we need to support 1.4 so
1094 # instead of being per-command, but we need to support 1.4 so
1080 # we have to be intelligent about what commands take
1095 # we have to be intelligent about what commands take
1081 # --non-interactive.
1096 # --non-interactive.
1082 if commands[0] in (b'update', b'checkout', b'commit'):
1097 if commands[0] in (b'update', b'checkout', b'commit'):
1083 cmd.append(b'--non-interactive')
1098 cmd.append(b'--non-interactive')
1084 cmd.extend(commands)
1099 cmd.extend(commands)
1085 if filename is not None:
1100 if filename is not None:
1086 path = self.wvfs.reljoin(
1101 path = self.wvfs.reljoin(
1087 self._ctx.repo().origroot, self._path, filename
1102 self._ctx.repo().origroot, self._path, filename
1088 )
1103 )
1089 cmd.append(path)
1104 cmd.append(path)
1090 env = dict(encoding.environ)
1105 env = dict(encoding.environ)
1091 # Avoid localized output, preserve current locale for everything else.
1106 # Avoid localized output, preserve current locale for everything else.
1092 lc_all = env.get(b'LC_ALL')
1107 lc_all = env.get(b'LC_ALL')
1093 if lc_all:
1108 if lc_all:
1094 env[b'LANG'] = lc_all
1109 env[b'LANG'] = lc_all
1095 del env[b'LC_ALL']
1110 del env[b'LC_ALL']
1096 env[b'LC_MESSAGES'] = b'C'
1111 env[b'LC_MESSAGES'] = b'C'
1097 p = subprocess.Popen(
1112 p = subprocess.Popen(
1098 pycompat.rapply(procutil.tonativestr, cmd),
1113 pycompat.rapply(procutil.tonativestr, cmd),
1099 bufsize=-1,
1114 bufsize=-1,
1100 close_fds=procutil.closefds,
1115 close_fds=procutil.closefds,
1101 stdout=subprocess.PIPE,
1116 stdout=subprocess.PIPE,
1102 stderr=subprocess.PIPE,
1117 stderr=subprocess.PIPE,
1103 env=procutil.tonativeenv(env),
1118 env=procutil.tonativeenv(env),
1104 **extrakw
1119 **extrakw
1105 )
1120 )
1106 stdout, stderr = map(util.fromnativeeol, p.communicate())
1121 stdout, stderr = map(util.fromnativeeol, p.communicate())
1107 stderr = stderr.strip()
1122 stderr = stderr.strip()
1108 if not failok:
1123 if not failok:
1109 if p.returncode:
1124 if p.returncode:
1110 raise error.Abort(
1125 raise error.Abort(
1111 stderr or b'exited with code %d' % p.returncode
1126 stderr or b'exited with code %d' % p.returncode
1112 )
1127 )
1113 if stderr:
1128 if stderr:
1114 self.ui.warn(stderr + b'\n')
1129 self.ui.warn(stderr + b'\n')
1115 return stdout, stderr
1130 return stdout, stderr
1116
1131
1117 @propertycache
1132 @propertycache
1118 def _svnversion(self):
1133 def _svnversion(self):
1119 output, err = self._svncommand(
1134 output, err = self._svncommand(
1120 [b'--version', b'--quiet'], filename=None
1135 [b'--version', b'--quiet'], filename=None
1121 )
1136 )
1122 m = re.search(br'^(\d+)\.(\d+)', output)
1137 m = re.search(br'^(\d+)\.(\d+)', output)
1123 if not m:
1138 if not m:
1124 raise error.Abort(_(b'cannot retrieve svn tool version'))
1139 raise error.Abort(_(b'cannot retrieve svn tool version'))
1125 return (int(m.group(1)), int(m.group(2)))
1140 return (int(m.group(1)), int(m.group(2)))
1126
1141
1127 def _svnmissing(self):
1142 def _svnmissing(self):
1128 return not self.wvfs.exists(b'.svn')
1143 return not self.wvfs.exists(b'.svn')
1129
1144
1130 def _wcrevs(self):
1145 def _wcrevs(self):
1131 # Get the working directory revision as well as the last
1146 # Get the working directory revision as well as the last
1132 # commit revision so we can compare the subrepo state with
1147 # commit revision so we can compare the subrepo state with
1133 # both. We used to store the working directory one.
1148 # both. We used to store the working directory one.
1134 output, err = self._svncommand([b'info', b'--xml'])
1149 output, err = self._svncommand([b'info', b'--xml'])
1135 doc = xml.dom.minidom.parseString(output)
1150 doc = xml.dom.minidom.parseString(output)
1136 entries = doc.getElementsByTagName('entry')
1151 entries = doc.getElementsByTagName('entry')
1137 lastrev, rev = b'0', b'0'
1152 lastrev, rev = b'0', b'0'
1138 if entries:
1153 if entries:
1139 rev = pycompat.bytestr(entries[0].getAttribute('revision')) or b'0'
1154 rev = pycompat.bytestr(entries[0].getAttribute('revision')) or b'0'
1140 commits = entries[0].getElementsByTagName('commit')
1155 commits = entries[0].getElementsByTagName('commit')
1141 if commits:
1156 if commits:
1142 lastrev = (
1157 lastrev = (
1143 pycompat.bytestr(commits[0].getAttribute('revision'))
1158 pycompat.bytestr(commits[0].getAttribute('revision'))
1144 or b'0'
1159 or b'0'
1145 )
1160 )
1146 return (lastrev, rev)
1161 return (lastrev, rev)
1147
1162
1148 def _wcrev(self):
1163 def _wcrev(self):
1149 return self._wcrevs()[0]
1164 return self._wcrevs()[0]
1150
1165
1151 def _wcchanged(self):
1166 def _wcchanged(self):
1152 """Return (changes, extchanges, missing) where changes is True
1167 """Return (changes, extchanges, missing) where changes is True
1153 if the working directory was changed, extchanges is
1168 if the working directory was changed, extchanges is
1154 True if any of these changes concern an external entry and missing
1169 True if any of these changes concern an external entry and missing
1155 is True if any change is a missing entry.
1170 is True if any change is a missing entry.
1156 """
1171 """
1157 output, err = self._svncommand([b'status', b'--xml'])
1172 output, err = self._svncommand([b'status', b'--xml'])
1158 externals, changes, missing = [], [], []
1173 externals, changes, missing = [], [], []
1159 doc = xml.dom.minidom.parseString(output)
1174 doc = xml.dom.minidom.parseString(output)
1160 for e in doc.getElementsByTagName('entry'):
1175 for e in doc.getElementsByTagName('entry'):
1161 s = e.getElementsByTagName('wc-status')
1176 s = e.getElementsByTagName('wc-status')
1162 if not s:
1177 if not s:
1163 continue
1178 continue
1164 item = s[0].getAttribute('item')
1179 item = s[0].getAttribute('item')
1165 props = s[0].getAttribute('props')
1180 props = s[0].getAttribute('props')
1166 path = e.getAttribute('path').encode('utf8')
1181 path = e.getAttribute('path').encode('utf8')
1167 if item == 'external':
1182 if item == 'external':
1168 externals.append(path)
1183 externals.append(path)
1169 elif item == 'missing':
1184 elif item == 'missing':
1170 missing.append(path)
1185 missing.append(path)
1171 if (
1186 if (
1172 item
1187 item
1173 not in (
1188 not in (
1174 '',
1189 '',
1175 'normal',
1190 'normal',
1176 'unversioned',
1191 'unversioned',
1177 'external',
1192 'external',
1178 )
1193 )
1179 or props not in ('', 'none', 'normal')
1194 or props not in ('', 'none', 'normal')
1180 ):
1195 ):
1181 changes.append(path)
1196 changes.append(path)
1182 for path in changes:
1197 for path in changes:
1183 for ext in externals:
1198 for ext in externals:
1184 if path == ext or path.startswith(ext + pycompat.ossep):
1199 if path == ext or path.startswith(ext + pycompat.ossep):
1185 return True, True, bool(missing)
1200 return True, True, bool(missing)
1186 return bool(changes), False, bool(missing)
1201 return bool(changes), False, bool(missing)
1187
1202
1188 @annotatesubrepoerror
1203 @annotatesubrepoerror
1189 def dirty(self, ignoreupdate=False, missing=False):
1204 def dirty(self, ignoreupdate=False, missing=False):
1190 if self._svnmissing():
1205 if self._svnmissing():
1191 return self._state[1] != b''
1206 return self._state[1] != b''
1192 wcchanged = self._wcchanged()
1207 wcchanged = self._wcchanged()
1193 changed = wcchanged[0] or (missing and wcchanged[2])
1208 changed = wcchanged[0] or (missing and wcchanged[2])
1194 if not changed:
1209 if not changed:
1195 if self._state[1] in self._wcrevs() or ignoreupdate:
1210 if self._state[1] in self._wcrevs() or ignoreupdate:
1196 return False
1211 return False
1197 return True
1212 return True
1198
1213
1199 def basestate(self):
1214 def basestate(self):
1200 lastrev, rev = self._wcrevs()
1215 lastrev, rev = self._wcrevs()
1201 if lastrev != rev:
1216 if lastrev != rev:
1202 # Last committed rev is not the same than rev. We would
1217 # Last committed rev is not the same than rev. We would
1203 # like to take lastrev but we do not know if the subrepo
1218 # like to take lastrev but we do not know if the subrepo
1204 # URL exists at lastrev. Test it and fallback to rev it
1219 # URL exists at lastrev. Test it and fallback to rev it
1205 # is not there.
1220 # is not there.
1206 try:
1221 try:
1207 self._svncommand(
1222 self._svncommand(
1208 [b'list', b'%s@%s' % (self._state[0], lastrev)]
1223 [b'list', b'%s@%s' % (self._state[0], lastrev)]
1209 )
1224 )
1210 return lastrev
1225 return lastrev
1211 except error.Abort:
1226 except error.Abort:
1212 pass
1227 pass
1213 return rev
1228 return rev
1214
1229
1215 @annotatesubrepoerror
1230 @annotatesubrepoerror
1216 def commit(self, text, user, date):
1231 def commit(self, text, user, date):
1217 # user and date are out of our hands since svn is centralized
1232 # user and date are out of our hands since svn is centralized
1218 changed, extchanged, missing = self._wcchanged()
1233 changed, extchanged, missing = self._wcchanged()
1219 if not changed:
1234 if not changed:
1220 return self.basestate()
1235 return self.basestate()
1221 if extchanged:
1236 if extchanged:
1222 # Do not try to commit externals
1237 # Do not try to commit externals
1223 raise error.Abort(_(b'cannot commit svn externals'))
1238 raise error.Abort(_(b'cannot commit svn externals'))
1224 if missing:
1239 if missing:
1225 # svn can commit with missing entries but aborting like hg
1240 # svn can commit with missing entries but aborting like hg
1226 # seems a better approach.
1241 # seems a better approach.
1227 raise error.Abort(_(b'cannot commit missing svn entries'))
1242 raise error.Abort(_(b'cannot commit missing svn entries'))
1228 commitinfo, err = self._svncommand([b'commit', b'-m', text])
1243 commitinfo, err = self._svncommand([b'commit', b'-m', text])
1229 self.ui.status(commitinfo)
1244 self.ui.status(commitinfo)
1230 newrev = re.search(b'Committed revision ([0-9]+).', commitinfo)
1245 newrev = re.search(b'Committed revision ([0-9]+).', commitinfo)
1231 if not newrev:
1246 if not newrev:
1232 if not commitinfo.strip():
1247 if not commitinfo.strip():
1233 # Sometimes, our definition of "changed" differs from
1248 # Sometimes, our definition of "changed" differs from
1234 # svn one. For instance, svn ignores missing files
1249 # svn one. For instance, svn ignores missing files
1235 # when committing. If there are only missing files, no
1250 # when committing. If there are only missing files, no
1236 # commit is made, no output and no error code.
1251 # commit is made, no output and no error code.
1237 raise error.Abort(_(b'failed to commit svn changes'))
1252 raise error.Abort(_(b'failed to commit svn changes'))
1238 raise error.Abort(commitinfo.splitlines()[-1])
1253 raise error.Abort(commitinfo.splitlines()[-1])
1239 newrev = newrev.groups()[0]
1254 newrev = newrev.groups()[0]
1240 self.ui.status(self._svncommand([b'update', b'-r', newrev])[0])
1255 self.ui.status(self._svncommand([b'update', b'-r', newrev])[0])
1241 return newrev
1256 return newrev
1242
1257
1243 @annotatesubrepoerror
1258 @annotatesubrepoerror
1244 def remove(self):
1259 def remove(self):
1245 if self.dirty():
1260 if self.dirty():
1246 self.ui.warn(
1261 self.ui.warn(
1247 _(b'not removing repo %s because it has changes.\n')
1262 _(b'not removing repo %s because it has changes.\n')
1248 % self._path
1263 % self._path
1249 )
1264 )
1250 return
1265 return
1251 self.ui.note(_(b'removing subrepo %s\n') % self._path)
1266 self.ui.note(_(b'removing subrepo %s\n') % self._path)
1252
1267
1253 self.wvfs.rmtree(forcibly=True)
1268 self.wvfs.rmtree(forcibly=True)
1254 try:
1269 try:
1255 pwvfs = self._ctx.repo().wvfs
1270 pwvfs = self._ctx.repo().wvfs
1256 pwvfs.removedirs(pwvfs.dirname(self._path))
1271 pwvfs.removedirs(pwvfs.dirname(self._path))
1257 except OSError:
1272 except OSError:
1258 pass
1273 pass
1259
1274
1260 @annotatesubrepoerror
1275 @annotatesubrepoerror
1261 def get(self, state, overwrite=False):
1276 def get(self, state, overwrite=False):
1262 if overwrite:
1277 if overwrite:
1263 self._svncommand([b'revert', b'--recursive'])
1278 self._svncommand([b'revert', b'--recursive'])
1264 args = [b'checkout']
1279 args = [b'checkout']
1265 if self._svnversion >= (1, 5):
1280 if self._svnversion >= (1, 5):
1266 args.append(b'--force')
1281 args.append(b'--force')
1267 # The revision must be specified at the end of the URL to properly
1282 # The revision must be specified at the end of the URL to properly
1268 # update to a directory which has since been deleted and recreated.
1283 # update to a directory which has since been deleted and recreated.
1269 args.append(b'%s@%s' % (state[0], state[1]))
1284 args.append(b'%s@%s' % (state[0], state[1]))
1270
1285
1271 # SEC: check that the ssh url is safe
1286 # SEC: check that the ssh url is safe
1272 util.checksafessh(state[0])
1287 util.checksafessh(state[0])
1273
1288
1274 status, err = self._svncommand(args, failok=True)
1289 status, err = self._svncommand(args, failok=True)
1275 _sanitize(self.ui, self.wvfs, b'.svn')
1290 _sanitize(self.ui, self.wvfs, b'.svn')
1276 if not re.search(b'Checked out revision [0-9]+.', status):
1291 if not re.search(b'Checked out revision [0-9]+.', status):
1277 if b'is already a working copy for a different URL' in err and (
1292 if b'is already a working copy for a different URL' in err and (
1278 self._wcchanged()[:2] == (False, False)
1293 self._wcchanged()[:2] == (False, False)
1279 ):
1294 ):
1280 # obstructed but clean working copy, so just blow it away.
1295 # obstructed but clean working copy, so just blow it away.
1281 self.remove()
1296 self.remove()
1282 self.get(state, overwrite=False)
1297 self.get(state, overwrite=False)
1283 return
1298 return
1284 raise error.Abort((status or err).splitlines()[-1])
1299 raise error.Abort((status or err).splitlines()[-1])
1285 self.ui.status(status)
1300 self.ui.status(status)
1286
1301
1287 @annotatesubrepoerror
1302 @annotatesubrepoerror
1288 def merge(self, state):
1303 def merge(self, state):
1289 old = self._state[1]
1304 old = self._state[1]
1290 new = state[1]
1305 new = state[1]
1291 wcrev = self._wcrev()
1306 wcrev = self._wcrev()
1292 if new != wcrev:
1307 if new != wcrev:
1293 dirty = old == wcrev or self._wcchanged()[0]
1308 dirty = old == wcrev or self._wcchanged()[0]
1294 if _updateprompt(self.ui, self, dirty, wcrev, new):
1309 if _updateprompt(self.ui, self, dirty, wcrev, new):
1295 self.get(state, False)
1310 self.get(state, False)
1296
1311
1297 def push(self, opts):
1312 def push(self, opts):
1298 # push is a no-op for SVN
1313 # push is a no-op for SVN
1299 return True
1314 return True
1300
1315
1301 @annotatesubrepoerror
1316 @annotatesubrepoerror
1302 def files(self):
1317 def files(self):
1303 output = self._svncommand([b'list', b'--recursive', b'--xml'])[0]
1318 output = self._svncommand([b'list', b'--recursive', b'--xml'])[0]
1304 doc = xml.dom.minidom.parseString(output)
1319 doc = xml.dom.minidom.parseString(output)
1305 paths = []
1320 paths = []
1306 for e in doc.getElementsByTagName('entry'):
1321 for e in doc.getElementsByTagName('entry'):
1307 kind = pycompat.bytestr(e.getAttribute('kind'))
1322 kind = pycompat.bytestr(e.getAttribute('kind'))
1308 if kind != b'file':
1323 if kind != b'file':
1309 continue
1324 continue
1310 name = ''.join(
1325 name = ''.join(
1311 c.data
1326 c.data
1312 for c in e.getElementsByTagName('name')[0].childNodes
1327 for c in e.getElementsByTagName('name')[0].childNodes
1313 if c.nodeType == c.TEXT_NODE
1328 if c.nodeType == c.TEXT_NODE
1314 )
1329 )
1315 paths.append(name.encode('utf8'))
1330 paths.append(name.encode('utf8'))
1316 return paths
1331 return paths
1317
1332
1318 def filedata(self, name, decode):
1333 def filedata(self, name, decode):
1319 return self._svncommand([b'cat'], name)[0]
1334 return self._svncommand([b'cat'], name)[0]
1320
1335
1321
1336
1322 class gitsubrepo(abstractsubrepo):
1337 class gitsubrepo(abstractsubrepo):
1323 def __init__(self, ctx, path, state, allowcreate):
1338 def __init__(self, ctx, path, state, allowcreate):
1324 super(gitsubrepo, self).__init__(ctx, path)
1339 super(gitsubrepo, self).__init__(ctx, path)
1325 self._state = state
1340 self._state = state
1326 self._abspath = ctx.repo().wjoin(path)
1341 self._abspath = ctx.repo().wjoin(path)
1327 self._subparent = ctx.repo()
1342 self._subparent = ctx.repo()
1328 self._ensuregit()
1343 self._ensuregit()
1329
1344
1330 def _ensuregit(self):
1345 def _ensuregit(self):
1331 try:
1346 try:
1332 self._gitexecutable = b'git'
1347 self._gitexecutable = b'git'
1333 out, err = self._gitnodir([b'--version'])
1348 out, err = self._gitnodir([b'--version'])
1334 except OSError as e:
1349 except OSError as e:
1335 genericerror = _(b"error executing git for subrepo '%s': %s")
1350 genericerror = _(b"error executing git for subrepo '%s': %s")
1336 notfoundhint = _(b"check git is installed and in your PATH")
1351 notfoundhint = _(b"check git is installed and in your PATH")
1337 if e.errno != errno.ENOENT:
1352 if e.errno != errno.ENOENT:
1338 raise error.Abort(
1353 raise error.Abort(
1339 genericerror % (self._path, encoding.strtolocal(e.strerror))
1354 genericerror % (self._path, encoding.strtolocal(e.strerror))
1340 )
1355 )
1341 elif pycompat.iswindows:
1356 elif pycompat.iswindows:
1342 try:
1357 try:
1343 self._gitexecutable = b'git.cmd'
1358 self._gitexecutable = b'git.cmd'
1344 out, err = self._gitnodir([b'--version'])
1359 out, err = self._gitnodir([b'--version'])
1345 except OSError as e2:
1360 except OSError as e2:
1346 if e2.errno == errno.ENOENT:
1361 if e2.errno == errno.ENOENT:
1347 raise error.Abort(
1362 raise error.Abort(
1348 _(
1363 _(
1349 b"couldn't find 'git' or 'git.cmd'"
1364 b"couldn't find 'git' or 'git.cmd'"
1350 b" for subrepo '%s'"
1365 b" for subrepo '%s'"
1351 )
1366 )
1352 % self._path,
1367 % self._path,
1353 hint=notfoundhint,
1368 hint=notfoundhint,
1354 )
1369 )
1355 else:
1370 else:
1356 raise error.Abort(
1371 raise error.Abort(
1357 genericerror
1372 genericerror
1358 % (self._path, encoding.strtolocal(e2.strerror))
1373 % (self._path, encoding.strtolocal(e2.strerror))
1359 )
1374 )
1360 else:
1375 else:
1361 raise error.Abort(
1376 raise error.Abort(
1362 _(b"couldn't find git for subrepo '%s'") % self._path,
1377 _(b"couldn't find git for subrepo '%s'") % self._path,
1363 hint=notfoundhint,
1378 hint=notfoundhint,
1364 )
1379 )
1365 versionstatus = self._checkversion(out)
1380 versionstatus = self._checkversion(out)
1366 if versionstatus == b'unknown':
1381 if versionstatus == b'unknown':
1367 self.ui.warn(_(b'cannot retrieve git version\n'))
1382 self.ui.warn(_(b'cannot retrieve git version\n'))
1368 elif versionstatus == b'abort':
1383 elif versionstatus == b'abort':
1369 raise error.Abort(
1384 raise error.Abort(
1370 _(b'git subrepo requires at least 1.6.0 or later')
1385 _(b'git subrepo requires at least 1.6.0 or later')
1371 )
1386 )
1372 elif versionstatus == b'warning':
1387 elif versionstatus == b'warning':
1373 self.ui.warn(_(b'git subrepo requires at least 1.6.0 or later\n'))
1388 self.ui.warn(_(b'git subrepo requires at least 1.6.0 or later\n'))
1374
1389
1375 @staticmethod
1390 @staticmethod
1376 def _gitversion(out):
1391 def _gitversion(out):
1377 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1392 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1378 if m:
1393 if m:
1379 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1394 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1380
1395
1381 m = re.search(br'^git version (\d+)\.(\d+)', out)
1396 m = re.search(br'^git version (\d+)\.(\d+)', out)
1382 if m:
1397 if m:
1383 return (int(m.group(1)), int(m.group(2)), 0)
1398 return (int(m.group(1)), int(m.group(2)), 0)
1384
1399
1385 return -1
1400 return -1
1386
1401
1387 @staticmethod
1402 @staticmethod
1388 def _checkversion(out):
1403 def _checkversion(out):
1389 """ensure git version is new enough
1404 """ensure git version is new enough
1390
1405
1391 >>> _checkversion = gitsubrepo._checkversion
1406 >>> _checkversion = gitsubrepo._checkversion
1392 >>> _checkversion(b'git version 1.6.0')
1407 >>> _checkversion(b'git version 1.6.0')
1393 'ok'
1408 'ok'
1394 >>> _checkversion(b'git version 1.8.5')
1409 >>> _checkversion(b'git version 1.8.5')
1395 'ok'
1410 'ok'
1396 >>> _checkversion(b'git version 1.4.0')
1411 >>> _checkversion(b'git version 1.4.0')
1397 'abort'
1412 'abort'
1398 >>> _checkversion(b'git version 1.5.0')
1413 >>> _checkversion(b'git version 1.5.0')
1399 'warning'
1414 'warning'
1400 >>> _checkversion(b'git version 1.9-rc0')
1415 >>> _checkversion(b'git version 1.9-rc0')
1401 'ok'
1416 'ok'
1402 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1417 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1403 'ok'
1418 'ok'
1404 >>> _checkversion(b'git version 1.9.0.GIT')
1419 >>> _checkversion(b'git version 1.9.0.GIT')
1405 'ok'
1420 'ok'
1406 >>> _checkversion(b'git version 12345')
1421 >>> _checkversion(b'git version 12345')
1407 'unknown'
1422 'unknown'
1408 >>> _checkversion(b'no')
1423 >>> _checkversion(b'no')
1409 'unknown'
1424 'unknown'
1410 """
1425 """
1411 version = gitsubrepo._gitversion(out)
1426 version = gitsubrepo._gitversion(out)
1412 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1427 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1413 # despite the docstring comment. For now, error on 1.4.0, warn on
1428 # despite the docstring comment. For now, error on 1.4.0, warn on
1414 # 1.5.0 but attempt to continue.
1429 # 1.5.0 but attempt to continue.
1415 if version == -1:
1430 if version == -1:
1416 return b'unknown'
1431 return b'unknown'
1417 if version < (1, 5, 0):
1432 if version < (1, 5, 0):
1418 return b'abort'
1433 return b'abort'
1419 elif version < (1, 6, 0):
1434 elif version < (1, 6, 0):
1420 return b'warning'
1435 return b'warning'
1421 return b'ok'
1436 return b'ok'
1422
1437
1423 def _gitcommand(self, commands, env=None, stream=False):
1438 def _gitcommand(self, commands, env=None, stream=False):
1424 return self._gitdir(commands, env=env, stream=stream)[0]
1439 return self._gitdir(commands, env=env, stream=stream)[0]
1425
1440
1426 def _gitdir(self, commands, env=None, stream=False):
1441 def _gitdir(self, commands, env=None, stream=False):
1427 return self._gitnodir(
1442 return self._gitnodir(
1428 commands, env=env, stream=stream, cwd=self._abspath
1443 commands, env=env, stream=stream, cwd=self._abspath
1429 )
1444 )
1430
1445
1431 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1446 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1432 """Calls the git command
1447 """Calls the git command
1433
1448
1434 The methods tries to call the git command. versions prior to 1.6.0
1449 The methods tries to call the git command. versions prior to 1.6.0
1435 are not supported and very probably fail.
1450 are not supported and very probably fail.
1436 """
1451 """
1437 self.ui.debug(b'%s: git %s\n' % (self._relpath, b' '.join(commands)))
1452 self.ui.debug(b'%s: git %s\n' % (self._relpath, b' '.join(commands)))
1438 if env is None:
1453 if env is None:
1439 env = encoding.environ.copy()
1454 env = encoding.environ.copy()
1440 # disable localization for Git output (issue5176)
1455 # disable localization for Git output (issue5176)
1441 env[b'LC_ALL'] = b'C'
1456 env[b'LC_ALL'] = b'C'
1442 # fix for Git CVE-2015-7545
1457 # fix for Git CVE-2015-7545
1443 if b'GIT_ALLOW_PROTOCOL' not in env:
1458 if b'GIT_ALLOW_PROTOCOL' not in env:
1444 env[b'GIT_ALLOW_PROTOCOL'] = b'file:git:http:https:ssh'
1459 env[b'GIT_ALLOW_PROTOCOL'] = b'file:git:http:https:ssh'
1445 # unless ui.quiet is set, print git's stderr,
1460 # unless ui.quiet is set, print git's stderr,
1446 # which is mostly progress and useful info
1461 # which is mostly progress and useful info
1447 errpipe = None
1462 errpipe = None
1448 if self.ui.quiet:
1463 if self.ui.quiet:
1449 errpipe = pycompat.open(os.devnull, b'w')
1464 errpipe = pycompat.open(os.devnull, b'w')
1450 if self.ui._colormode and len(commands) and commands[0] == b"diff":
1465 if self.ui._colormode and len(commands) and commands[0] == b"diff":
1451 # insert the argument in the front,
1466 # insert the argument in the front,
1452 # the end of git diff arguments is used for paths
1467 # the end of git diff arguments is used for paths
1453 commands.insert(1, b'--color')
1468 commands.insert(1, b'--color')
1454 p = subprocess.Popen(
1469 p = subprocess.Popen(
1455 pycompat.rapply(
1470 pycompat.rapply(
1456 procutil.tonativestr, [self._gitexecutable] + commands
1471 procutil.tonativestr, [self._gitexecutable] + commands
1457 ),
1472 ),
1458 bufsize=-1,
1473 bufsize=-1,
1459 cwd=pycompat.rapply(procutil.tonativestr, cwd),
1474 cwd=pycompat.rapply(procutil.tonativestr, cwd),
1460 env=procutil.tonativeenv(env),
1475 env=procutil.tonativeenv(env),
1461 close_fds=procutil.closefds,
1476 close_fds=procutil.closefds,
1462 stdout=subprocess.PIPE,
1477 stdout=subprocess.PIPE,
1463 stderr=errpipe,
1478 stderr=errpipe,
1464 )
1479 )
1465 if stream:
1480 if stream:
1466 return p.stdout, None
1481 return p.stdout, None
1467
1482
1468 retdata = p.stdout.read().strip()
1483 retdata = p.stdout.read().strip()
1469 # wait for the child to exit to avoid race condition.
1484 # wait for the child to exit to avoid race condition.
1470 p.wait()
1485 p.wait()
1471
1486
1472 if p.returncode != 0 and p.returncode != 1:
1487 if p.returncode != 0 and p.returncode != 1:
1473 # there are certain error codes that are ok
1488 # there are certain error codes that are ok
1474 command = commands[0]
1489 command = commands[0]
1475 if command in (b'cat-file', b'symbolic-ref'):
1490 if command in (b'cat-file', b'symbolic-ref'):
1476 return retdata, p.returncode
1491 return retdata, p.returncode
1477 # for all others, abort
1492 # for all others, abort
1478 raise error.Abort(
1493 raise error.Abort(
1479 _(b'git %s error %d in %s')
1494 _(b'git %s error %d in %s')
1480 % (command, p.returncode, self._relpath)
1495 % (command, p.returncode, self._relpath)
1481 )
1496 )
1482
1497
1483 return retdata, p.returncode
1498 return retdata, p.returncode
1484
1499
1485 def _gitmissing(self):
1500 def _gitmissing(self):
1486 return not self.wvfs.exists(b'.git')
1501 return not self.wvfs.exists(b'.git')
1487
1502
1488 def _gitstate(self):
1503 def _gitstate(self):
1489 return self._gitcommand([b'rev-parse', b'HEAD'])
1504 return self._gitcommand([b'rev-parse', b'HEAD'])
1490
1505
1491 def _gitcurrentbranch(self):
1506 def _gitcurrentbranch(self):
1492 current, err = self._gitdir([b'symbolic-ref', b'HEAD', b'--quiet'])
1507 current, err = self._gitdir([b'symbolic-ref', b'HEAD', b'--quiet'])
1493 if err:
1508 if err:
1494 current = None
1509 current = None
1495 return current
1510 return current
1496
1511
1497 def _gitremote(self, remote):
1512 def _gitremote(self, remote):
1498 out = self._gitcommand([b'remote', b'show', b'-n', remote])
1513 out = self._gitcommand([b'remote', b'show', b'-n', remote])
1499 line = out.split(b'\n')[1]
1514 line = out.split(b'\n')[1]
1500 i = line.index(b'URL: ') + len(b'URL: ')
1515 i = line.index(b'URL: ') + len(b'URL: ')
1501 return line[i:]
1516 return line[i:]
1502
1517
1503 def _githavelocally(self, revision):
1518 def _githavelocally(self, revision):
1504 out, code = self._gitdir([b'cat-file', b'-e', revision])
1519 out, code = self._gitdir([b'cat-file', b'-e', revision])
1505 return code == 0
1520 return code == 0
1506
1521
1507 def _gitisancestor(self, r1, r2):
1522 def _gitisancestor(self, r1, r2):
1508 base = self._gitcommand([b'merge-base', r1, r2])
1523 base = self._gitcommand([b'merge-base', r1, r2])
1509 return base == r1
1524 return base == r1
1510
1525
1511 def _gitisbare(self):
1526 def _gitisbare(self):
1512 return self._gitcommand([b'config', b'--bool', b'core.bare']) == b'true'
1527 return self._gitcommand([b'config', b'--bool', b'core.bare']) == b'true'
1513
1528
1514 def _gitupdatestat(self):
1529 def _gitupdatestat(self):
1515 """This must be run before git diff-index.
1530 """This must be run before git diff-index.
1516 diff-index only looks at changes to file stat;
1531 diff-index only looks at changes to file stat;
1517 this command looks at file contents and updates the stat."""
1532 this command looks at file contents and updates the stat."""
1518 self._gitcommand([b'update-index', b'-q', b'--refresh'])
1533 self._gitcommand([b'update-index', b'-q', b'--refresh'])
1519
1534
1520 def _gitbranchmap(self):
1535 def _gitbranchmap(self):
1521 """returns 2 things:
1536 """returns 2 things:
1522 a map from git branch to revision
1537 a map from git branch to revision
1523 a map from revision to branches"""
1538 a map from revision to branches"""
1524 branch2rev = {}
1539 branch2rev = {}
1525 rev2branch = {}
1540 rev2branch = {}
1526
1541
1527 out = self._gitcommand(
1542 out = self._gitcommand(
1528 [b'for-each-ref', b'--format', b'%(objectname) %(refname)']
1543 [b'for-each-ref', b'--format', b'%(objectname) %(refname)']
1529 )
1544 )
1530 for line in out.split(b'\n'):
1545 for line in out.split(b'\n'):
1531 revision, ref = line.split(b' ')
1546 revision, ref = line.split(b' ')
1532 if not ref.startswith(b'refs/heads/') and not ref.startswith(
1547 if not ref.startswith(b'refs/heads/') and not ref.startswith(
1533 b'refs/remotes/'
1548 b'refs/remotes/'
1534 ):
1549 ):
1535 continue
1550 continue
1536 if ref.startswith(b'refs/remotes/') and ref.endswith(b'/HEAD'):
1551 if ref.startswith(b'refs/remotes/') and ref.endswith(b'/HEAD'):
1537 continue # ignore remote/HEAD redirects
1552 continue # ignore remote/HEAD redirects
1538 branch2rev[ref] = revision
1553 branch2rev[ref] = revision
1539 rev2branch.setdefault(revision, []).append(ref)
1554 rev2branch.setdefault(revision, []).append(ref)
1540 return branch2rev, rev2branch
1555 return branch2rev, rev2branch
1541
1556
1542 def _gittracking(self, branches):
1557 def _gittracking(self, branches):
1543 """return map of remote branch to local tracking branch"""
1558 """return map of remote branch to local tracking branch"""
1544 # assumes no more than one local tracking branch for each remote
1559 # assumes no more than one local tracking branch for each remote
1545 tracking = {}
1560 tracking = {}
1546 for b in branches:
1561 for b in branches:
1547 if b.startswith(b'refs/remotes/'):
1562 if b.startswith(b'refs/remotes/'):
1548 continue
1563 continue
1549 bname = b.split(b'/', 2)[2]
1564 bname = b.split(b'/', 2)[2]
1550 remote = self._gitcommand([b'config', b'branch.%s.remote' % bname])
1565 remote = self._gitcommand([b'config', b'branch.%s.remote' % bname])
1551 if remote:
1566 if remote:
1552 ref = self._gitcommand([b'config', b'branch.%s.merge' % bname])
1567 ref = self._gitcommand([b'config', b'branch.%s.merge' % bname])
1553 tracking[
1568 tracking[
1554 b'refs/remotes/%s/%s' % (remote, ref.split(b'/', 2)[2])
1569 b'refs/remotes/%s/%s' % (remote, ref.split(b'/', 2)[2])
1555 ] = b
1570 ] = b
1556 return tracking
1571 return tracking
1557
1572
1558 def _abssource(self, source):
1573 def _abssource(self, source):
1559 if b'://' not in source:
1574 if b'://' not in source:
1560 # recognize the scp syntax as an absolute source
1575 # recognize the scp syntax as an absolute source
1561 colon = source.find(b':')
1576 colon = source.find(b':')
1562 if colon != -1 and b'/' not in source[:colon]:
1577 if colon != -1 and b'/' not in source[:colon]:
1563 return source
1578 return source
1564 self._subsource = source
1579 self._subsource = source
1565 return _abssource(self)
1580 return _abssource(self)
1566
1581
1567 def _fetch(self, source, revision):
1582 def _fetch(self, source, revision):
1568 if self._gitmissing():
1583 if self._gitmissing():
1569 # SEC: check for safe ssh url
1584 # SEC: check for safe ssh url
1570 util.checksafessh(source)
1585 util.checksafessh(source)
1571
1586
1572 source = self._abssource(source)
1587 source = self._abssource(source)
1573 self.ui.status(
1588 self.ui.status(
1574 _(b'cloning subrepo %s from %s\n') % (self._relpath, source)
1589 _(b'cloning subrepo %s from %s\n') % (self._relpath, source)
1575 )
1590 )
1576 self._gitnodir([b'clone', source, self._abspath])
1591 self._gitnodir([b'clone', source, self._abspath])
1577 if self._githavelocally(revision):
1592 if self._githavelocally(revision):
1578 return
1593 return
1579 self.ui.status(
1594 self.ui.status(
1580 _(b'pulling subrepo %s from %s\n')
1595 _(b'pulling subrepo %s from %s\n')
1581 % (self._relpath, self._gitremote(b'origin'))
1596 % (self._relpath, self._gitremote(b'origin'))
1582 )
1597 )
1583 # try only origin: the originally cloned repo
1598 # try only origin: the originally cloned repo
1584 self._gitcommand([b'fetch'])
1599 self._gitcommand([b'fetch'])
1585 if not self._githavelocally(revision):
1600 if not self._githavelocally(revision):
1586 raise error.Abort(
1601 raise error.Abort(
1587 _(b'revision %s does not exist in subrepository "%s"\n')
1602 _(b'revision %s does not exist in subrepository "%s"\n')
1588 % (revision, self._relpath)
1603 % (revision, self._relpath)
1589 )
1604 )
1590
1605
1591 @annotatesubrepoerror
1606 @annotatesubrepoerror
1592 def dirty(self, ignoreupdate=False, missing=False):
1607 def dirty(self, ignoreupdate=False, missing=False):
1593 if self._gitmissing():
1608 if self._gitmissing():
1594 return self._state[1] != b''
1609 return self._state[1] != b''
1595 if self._gitisbare():
1610 if self._gitisbare():
1596 return True
1611 return True
1597 if not ignoreupdate and self._state[1] != self._gitstate():
1612 if not ignoreupdate and self._state[1] != self._gitstate():
1598 # different version checked out
1613 # different version checked out
1599 return True
1614 return True
1600 # check for staged changes or modified files; ignore untracked files
1615 # check for staged changes or modified files; ignore untracked files
1601 self._gitupdatestat()
1616 self._gitupdatestat()
1602 out, code = self._gitdir([b'diff-index', b'--quiet', b'HEAD'])
1617 out, code = self._gitdir([b'diff-index', b'--quiet', b'HEAD'])
1603 return code == 1
1618 return code == 1
1604
1619
1605 def basestate(self):
1620 def basestate(self):
1606 return self._gitstate()
1621 return self._gitstate()
1607
1622
1608 @annotatesubrepoerror
1623 @annotatesubrepoerror
1609 def get(self, state, overwrite=False):
1624 def get(self, state, overwrite=False):
1610 source, revision, kind = state
1625 source, revision, kind = state
1611 if not revision:
1626 if not revision:
1612 self.remove()
1627 self.remove()
1613 return
1628 return
1614 self._fetch(source, revision)
1629 self._fetch(source, revision)
1615 # if the repo was set to be bare, unbare it
1630 # if the repo was set to be bare, unbare it
1616 if self._gitisbare():
1631 if self._gitisbare():
1617 self._gitcommand([b'config', b'core.bare', b'false'])
1632 self._gitcommand([b'config', b'core.bare', b'false'])
1618 if self._gitstate() == revision:
1633 if self._gitstate() == revision:
1619 self._gitcommand([b'reset', b'--hard', b'HEAD'])
1634 self._gitcommand([b'reset', b'--hard', b'HEAD'])
1620 return
1635 return
1621 elif self._gitstate() == revision:
1636 elif self._gitstate() == revision:
1622 if overwrite:
1637 if overwrite:
1623 # first reset the index to unmark new files for commit, because
1638 # first reset the index to unmark new files for commit, because
1624 # reset --hard will otherwise throw away files added for commit,
1639 # reset --hard will otherwise throw away files added for commit,
1625 # not just unmark them.
1640 # not just unmark them.
1626 self._gitcommand([b'reset', b'HEAD'])
1641 self._gitcommand([b'reset', b'HEAD'])
1627 self._gitcommand([b'reset', b'--hard', b'HEAD'])
1642 self._gitcommand([b'reset', b'--hard', b'HEAD'])
1628 return
1643 return
1629 branch2rev, rev2branch = self._gitbranchmap()
1644 branch2rev, rev2branch = self._gitbranchmap()
1630
1645
1631 def checkout(args):
1646 def checkout(args):
1632 cmd = [b'checkout']
1647 cmd = [b'checkout']
1633 if overwrite:
1648 if overwrite:
1634 # first reset the index to unmark new files for commit, because
1649 # first reset the index to unmark new files for commit, because
1635 # the -f option will otherwise throw away files added for
1650 # the -f option will otherwise throw away files added for
1636 # commit, not just unmark them.
1651 # commit, not just unmark them.
1637 self._gitcommand([b'reset', b'HEAD'])
1652 self._gitcommand([b'reset', b'HEAD'])
1638 cmd.append(b'-f')
1653 cmd.append(b'-f')
1639 self._gitcommand(cmd + args)
1654 self._gitcommand(cmd + args)
1640 _sanitize(self.ui, self.wvfs, b'.git')
1655 _sanitize(self.ui, self.wvfs, b'.git')
1641
1656
1642 def rawcheckout():
1657 def rawcheckout():
1643 # no branch to checkout, check it out with no branch
1658 # no branch to checkout, check it out with no branch
1644 self.ui.warn(
1659 self.ui.warn(
1645 _(b'checking out detached HEAD in subrepository "%s"\n')
1660 _(b'checking out detached HEAD in subrepository "%s"\n')
1646 % self._relpath
1661 % self._relpath
1647 )
1662 )
1648 self.ui.warn(
1663 self.ui.warn(
1649 _(b'check out a git branch if you intend to make changes\n')
1664 _(b'check out a git branch if you intend to make changes\n')
1650 )
1665 )
1651 checkout([b'-q', revision])
1666 checkout([b'-q', revision])
1652
1667
1653 if revision not in rev2branch:
1668 if revision not in rev2branch:
1654 rawcheckout()
1669 rawcheckout()
1655 return
1670 return
1656 branches = rev2branch[revision]
1671 branches = rev2branch[revision]
1657 firstlocalbranch = None
1672 firstlocalbranch = None
1658 for b in branches:
1673 for b in branches:
1659 if b == b'refs/heads/master':
1674 if b == b'refs/heads/master':
1660 # master trumps all other branches
1675 # master trumps all other branches
1661 checkout([b'refs/heads/master'])
1676 checkout([b'refs/heads/master'])
1662 return
1677 return
1663 if not firstlocalbranch and not b.startswith(b'refs/remotes/'):
1678 if not firstlocalbranch and not b.startswith(b'refs/remotes/'):
1664 firstlocalbranch = b
1679 firstlocalbranch = b
1665 if firstlocalbranch:
1680 if firstlocalbranch:
1666 checkout([firstlocalbranch])
1681 checkout([firstlocalbranch])
1667 return
1682 return
1668
1683
1669 tracking = self._gittracking(branch2rev.keys())
1684 tracking = self._gittracking(branch2rev.keys())
1670 # choose a remote branch already tracked if possible
1685 # choose a remote branch already tracked if possible
1671 remote = branches[0]
1686 remote = branches[0]
1672 if remote not in tracking:
1687 if remote not in tracking:
1673 for b in branches:
1688 for b in branches:
1674 if b in tracking:
1689 if b in tracking:
1675 remote = b
1690 remote = b
1676 break
1691 break
1677
1692
1678 if remote not in tracking:
1693 if remote not in tracking:
1679 # create a new local tracking branch
1694 # create a new local tracking branch
1680 local = remote.split(b'/', 3)[3]
1695 local = remote.split(b'/', 3)[3]
1681 checkout([b'-b', local, remote])
1696 checkout([b'-b', local, remote])
1682 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1697 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1683 # When updating to a tracked remote branch,
1698 # When updating to a tracked remote branch,
1684 # if the local tracking branch is downstream of it,
1699 # if the local tracking branch is downstream of it,
1685 # a normal `git pull` would have performed a "fast-forward merge"
1700 # a normal `git pull` would have performed a "fast-forward merge"
1686 # which is equivalent to updating the local branch to the remote.
1701 # which is equivalent to updating the local branch to the remote.
1687 # Since we are only looking at branching at update, we need to
1702 # Since we are only looking at branching at update, we need to
1688 # detect this situation and perform this action lazily.
1703 # detect this situation and perform this action lazily.
1689 if tracking[remote] != self._gitcurrentbranch():
1704 if tracking[remote] != self._gitcurrentbranch():
1690 checkout([tracking[remote]])
1705 checkout([tracking[remote]])
1691 self._gitcommand([b'merge', b'--ff', remote])
1706 self._gitcommand([b'merge', b'--ff', remote])
1692 _sanitize(self.ui, self.wvfs, b'.git')
1707 _sanitize(self.ui, self.wvfs, b'.git')
1693 else:
1708 else:
1694 # a real merge would be required, just checkout the revision
1709 # a real merge would be required, just checkout the revision
1695 rawcheckout()
1710 rawcheckout()
1696
1711
1697 @annotatesubrepoerror
1712 @annotatesubrepoerror
1698 def commit(self, text, user, date):
1713 def commit(self, text, user, date):
1699 if self._gitmissing():
1714 if self._gitmissing():
1700 raise error.Abort(_(b"subrepo %s is missing") % self._relpath)
1715 raise error.Abort(_(b"subrepo %s is missing") % self._relpath)
1701 cmd = [b'commit', b'-a', b'-m', text]
1716 cmd = [b'commit', b'-a', b'-m', text]
1702 env = encoding.environ.copy()
1717 env = encoding.environ.copy()
1703 if user:
1718 if user:
1704 cmd += [b'--author', user]
1719 cmd += [b'--author', user]
1705 if date:
1720 if date:
1706 # git's date parser silently ignores when seconds < 1e9
1721 # git's date parser silently ignores when seconds < 1e9
1707 # convert to ISO8601
1722 # convert to ISO8601
1708 env[b'GIT_AUTHOR_DATE'] = dateutil.datestr(
1723 env[b'GIT_AUTHOR_DATE'] = dateutil.datestr(
1709 date, b'%Y-%m-%dT%H:%M:%S %1%2'
1724 date, b'%Y-%m-%dT%H:%M:%S %1%2'
1710 )
1725 )
1711 self._gitcommand(cmd, env=env)
1726 self._gitcommand(cmd, env=env)
1712 # make sure commit works otherwise HEAD might not exist under certain
1727 # make sure commit works otherwise HEAD might not exist under certain
1713 # circumstances
1728 # circumstances
1714 return self._gitstate()
1729 return self._gitstate()
1715
1730
1716 @annotatesubrepoerror
1731 @annotatesubrepoerror
1717 def merge(self, state):
1732 def merge(self, state):
1718 source, revision, kind = state
1733 source, revision, kind = state
1719 self._fetch(source, revision)
1734 self._fetch(source, revision)
1720 base = self._gitcommand([b'merge-base', revision, self._state[1]])
1735 base = self._gitcommand([b'merge-base', revision, self._state[1]])
1721 self._gitupdatestat()
1736 self._gitupdatestat()
1722 out, code = self._gitdir([b'diff-index', b'--quiet', b'HEAD'])
1737 out, code = self._gitdir([b'diff-index', b'--quiet', b'HEAD'])
1723
1738
1724 def mergefunc():
1739 def mergefunc():
1725 if base == revision:
1740 if base == revision:
1726 self.get(state) # fast forward merge
1741 self.get(state) # fast forward merge
1727 elif base != self._state[1]:
1742 elif base != self._state[1]:
1728 self._gitcommand([b'merge', b'--no-commit', revision])
1743 self._gitcommand([b'merge', b'--no-commit', revision])
1729 _sanitize(self.ui, self.wvfs, b'.git')
1744 _sanitize(self.ui, self.wvfs, b'.git')
1730
1745
1731 if self.dirty():
1746 if self.dirty():
1732 if self._gitstate() != revision:
1747 if self._gitstate() != revision:
1733 dirty = self._gitstate() == self._state[1] or code != 0
1748 dirty = self._gitstate() == self._state[1] or code != 0
1734 if _updateprompt(
1749 if _updateprompt(
1735 self.ui, self, dirty, self._state[1][:7], revision[:7]
1750 self.ui, self, dirty, self._state[1][:7], revision[:7]
1736 ):
1751 ):
1737 mergefunc()
1752 mergefunc()
1738 else:
1753 else:
1739 mergefunc()
1754 mergefunc()
1740
1755
1741 @annotatesubrepoerror
1756 @annotatesubrepoerror
1742 def push(self, opts):
1757 def push(self, opts):
1743 force = opts.get(b'force')
1758 force = opts.get(b'force')
1744
1759
1745 if not self._state[1]:
1760 if not self._state[1]:
1746 return True
1761 return True
1747 if self._gitmissing():
1762 if self._gitmissing():
1748 raise error.Abort(_(b"subrepo %s is missing") % self._relpath)
1763 raise error.Abort(_(b"subrepo %s is missing") % self._relpath)
1749 # if a branch in origin contains the revision, nothing to do
1764 # if a branch in origin contains the revision, nothing to do
1750 branch2rev, rev2branch = self._gitbranchmap()
1765 branch2rev, rev2branch = self._gitbranchmap()
1751 if self._state[1] in rev2branch:
1766 if self._state[1] in rev2branch:
1752 for b in rev2branch[self._state[1]]:
1767 for b in rev2branch[self._state[1]]:
1753 if b.startswith(b'refs/remotes/origin/'):
1768 if b.startswith(b'refs/remotes/origin/'):
1754 return True
1769 return True
1755 for b, revision in pycompat.iteritems(branch2rev):
1770 for b, revision in pycompat.iteritems(branch2rev):
1756 if b.startswith(b'refs/remotes/origin/'):
1771 if b.startswith(b'refs/remotes/origin/'):
1757 if self._gitisancestor(self._state[1], revision):
1772 if self._gitisancestor(self._state[1], revision):
1758 return True
1773 return True
1759 # otherwise, try to push the currently checked out branch
1774 # otherwise, try to push the currently checked out branch
1760 cmd = [b'push']
1775 cmd = [b'push']
1761 if force:
1776 if force:
1762 cmd.append(b'--force')
1777 cmd.append(b'--force')
1763
1778
1764 current = self._gitcurrentbranch()
1779 current = self._gitcurrentbranch()
1765 if current:
1780 if current:
1766 # determine if the current branch is even useful
1781 # determine if the current branch is even useful
1767 if not self._gitisancestor(self._state[1], current):
1782 if not self._gitisancestor(self._state[1], current):
1768 self.ui.warn(
1783 self.ui.warn(
1769 _(
1784 _(
1770 b'unrelated git branch checked out '
1785 b'unrelated git branch checked out '
1771 b'in subrepository "%s"\n'
1786 b'in subrepository "%s"\n'
1772 )
1787 )
1773 % self._relpath
1788 % self._relpath
1774 )
1789 )
1775 return False
1790 return False
1776 self.ui.status(
1791 self.ui.status(
1777 _(b'pushing branch %s of subrepository "%s"\n')
1792 _(b'pushing branch %s of subrepository "%s"\n')
1778 % (current.split(b'/', 2)[2], self._relpath)
1793 % (current.split(b'/', 2)[2], self._relpath)
1779 )
1794 )
1780 ret = self._gitdir(cmd + [b'origin', current])
1795 ret = self._gitdir(cmd + [b'origin', current])
1781 return ret[1] == 0
1796 return ret[1] == 0
1782 else:
1797 else:
1783 self.ui.warn(
1798 self.ui.warn(
1784 _(
1799 _(
1785 b'no branch checked out in subrepository "%s"\n'
1800 b'no branch checked out in subrepository "%s"\n'
1786 b'cannot push revision %s\n'
1801 b'cannot push revision %s\n'
1787 )
1802 )
1788 % (self._relpath, self._state[1])
1803 % (self._relpath, self._state[1])
1789 )
1804 )
1790 return False
1805 return False
1791
1806
1792 @annotatesubrepoerror
1807 @annotatesubrepoerror
1793 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
1808 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
1794 if self._gitmissing():
1809 if self._gitmissing():
1795 return []
1810 return []
1796
1811
1797 s = self.status(None, unknown=True, clean=True)
1812 s = self.status(None, unknown=True, clean=True)
1798
1813
1799 tracked = set()
1814 tracked = set()
1800 # dirstates 'amn' warn, 'r' is added again
1815 # dirstates 'amn' warn, 'r' is added again
1801 for l in (s.modified, s.added, s.deleted, s.clean):
1816 for l in (s.modified, s.added, s.deleted, s.clean):
1802 tracked.update(l)
1817 tracked.update(l)
1803
1818
1804 # Unknown files not of interest will be rejected by the matcher
1819 # Unknown files not of interest will be rejected by the matcher
1805 files = s.unknown
1820 files = s.unknown
1806 files.extend(match.files())
1821 files.extend(match.files())
1807
1822
1808 rejected = []
1823 rejected = []
1809
1824
1810 files = [f for f in sorted(set(files)) if match(f)]
1825 files = [f for f in sorted(set(files)) if match(f)]
1811 for f in files:
1826 for f in files:
1812 exact = match.exact(f)
1827 exact = match.exact(f)
1813 command = [b"add"]
1828 command = [b"add"]
1814 if exact:
1829 if exact:
1815 command.append(b"-f") # should be added, even if ignored
1830 command.append(b"-f") # should be added, even if ignored
1816 if ui.verbose or not exact:
1831 if ui.verbose or not exact:
1817 ui.status(_(b'adding %s\n') % uipathfn(f))
1832 ui.status(_(b'adding %s\n') % uipathfn(f))
1818
1833
1819 if f in tracked: # hg prints 'adding' even if already tracked
1834 if f in tracked: # hg prints 'adding' even if already tracked
1820 if exact:
1835 if exact:
1821 rejected.append(f)
1836 rejected.append(f)
1822 continue
1837 continue
1823 if not opts.get('dry_run'):
1838 if not opts.get('dry_run'):
1824 self._gitcommand(command + [f])
1839 self._gitcommand(command + [f])
1825
1840
1826 for f in rejected:
1841 for f in rejected:
1827 ui.warn(_(b"%s already tracked!\n") % uipathfn(f))
1842 ui.warn(_(b"%s already tracked!\n") % uipathfn(f))
1828
1843
1829 return rejected
1844 return rejected
1830
1845
1831 @annotatesubrepoerror
1846 @annotatesubrepoerror
1832 def remove(self):
1847 def remove(self):
1833 if self._gitmissing():
1848 if self._gitmissing():
1834 return
1849 return
1835 if self.dirty():
1850 if self.dirty():
1836 self.ui.warn(
1851 self.ui.warn(
1837 _(b'not removing repo %s because it has changes.\n')
1852 _(b'not removing repo %s because it has changes.\n')
1838 % self._relpath
1853 % self._relpath
1839 )
1854 )
1840 return
1855 return
1841 # we can't fully delete the repository as it may contain
1856 # we can't fully delete the repository as it may contain
1842 # local-only history
1857 # local-only history
1843 self.ui.note(_(b'removing subrepo %s\n') % self._relpath)
1858 self.ui.note(_(b'removing subrepo %s\n') % self._relpath)
1844 self._gitcommand([b'config', b'core.bare', b'true'])
1859 self._gitcommand([b'config', b'core.bare', b'true'])
1845 for f, kind in self.wvfs.readdir():
1860 for f, kind in self.wvfs.readdir():
1846 if f == b'.git':
1861 if f == b'.git':
1847 continue
1862 continue
1848 if kind == stat.S_IFDIR:
1863 if kind == stat.S_IFDIR:
1849 self.wvfs.rmtree(f)
1864 self.wvfs.rmtree(f)
1850 else:
1865 else:
1851 self.wvfs.unlink(f)
1866 self.wvfs.unlink(f)
1852
1867
1853 def archive(self, archiver, prefix, match=None, decode=True):
1868 def archive(self, archiver, prefix, match=None, decode=True):
1854 total = 0
1869 total = 0
1855 source, revision = self._state
1870 source, revision = self._state
1856 if not revision:
1871 if not revision:
1857 return total
1872 return total
1858 self._fetch(source, revision)
1873 self._fetch(source, revision)
1859
1874
1860 # Parse git's native archive command.
1875 # Parse git's native archive command.
1861 # This should be much faster than manually traversing the trees
1876 # This should be much faster than manually traversing the trees
1862 # and objects with many subprocess calls.
1877 # and objects with many subprocess calls.
1863 tarstream = self._gitcommand([b'archive', revision], stream=True)
1878 tarstream = self._gitcommand([b'archive', revision], stream=True)
1864 tar = tarfile.open(fileobj=tarstream, mode='r|')
1879 tar = tarfile.open(fileobj=tarstream, mode='r|')
1865 relpath = subrelpath(self)
1880 relpath = subrelpath(self)
1866 progress = self.ui.makeprogress(
1881 progress = self.ui.makeprogress(
1867 _(b'archiving (%s)') % relpath, unit=_(b'files')
1882 _(b'archiving (%s)') % relpath, unit=_(b'files')
1868 )
1883 )
1869 progress.update(0)
1884 progress.update(0)
1870 for info in tar:
1885 for info in tar:
1871 if info.isdir():
1886 if info.isdir():
1872 continue
1887 continue
1873 bname = pycompat.fsencode(info.name)
1888 bname = pycompat.fsencode(info.name)
1874 if match and not match(bname):
1889 if match and not match(bname):
1875 continue
1890 continue
1876 if info.issym():
1891 if info.issym():
1877 data = info.linkname
1892 data = info.linkname
1878 else:
1893 else:
1879 data = tar.extractfile(info).read()
1894 data = tar.extractfile(info).read()
1880 archiver.addfile(prefix + bname, info.mode, info.issym(), data)
1895 archiver.addfile(prefix + bname, info.mode, info.issym(), data)
1881 total += 1
1896 total += 1
1882 progress.increment()
1897 progress.increment()
1883 progress.complete()
1898 progress.complete()
1884 return total
1899 return total
1885
1900
1886 @annotatesubrepoerror
1901 @annotatesubrepoerror
1887 def cat(self, match, fm, fntemplate, prefix, **opts):
1902 def cat(self, match, fm, fntemplate, prefix, **opts):
1888 rev = self._state[1]
1903 rev = self._state[1]
1889 if match.anypats():
1904 if match.anypats():
1890 return 1 # No support for include/exclude yet
1905 return 1 # No support for include/exclude yet
1891
1906
1892 if not match.files():
1907 if not match.files():
1893 return 1
1908 return 1
1894
1909
1895 # TODO: add support for non-plain formatter (see cmdutil.cat())
1910 # TODO: add support for non-plain formatter (see cmdutil.cat())
1896 for f in match.files():
1911 for f in match.files():
1897 output = self._gitcommand([b"show", b"%s:%s" % (rev, f)])
1912 output = self._gitcommand([b"show", b"%s:%s" % (rev, f)])
1898 fp = cmdutil.makefileobj(
1913 fp = cmdutil.makefileobj(
1899 self._ctx, fntemplate, pathname=self.wvfs.reljoin(prefix, f)
1914 self._ctx, fntemplate, pathname=self.wvfs.reljoin(prefix, f)
1900 )
1915 )
1901 fp.write(output)
1916 fp.write(output)
1902 fp.close()
1917 fp.close()
1903 return 0
1918 return 0
1904
1919
1905 @annotatesubrepoerror
1920 @annotatesubrepoerror
1906 def status(self, rev2, **opts):
1921 def status(self, rev2, **opts):
1907 rev1 = self._state[1]
1922 rev1 = self._state[1]
1908 if self._gitmissing() or not rev1:
1923 if self._gitmissing() or not rev1:
1909 # if the repo is missing, return no results
1924 # if the repo is missing, return no results
1910 return scmutil.status([], [], [], [], [], [], [])
1925 return scmutil.status([], [], [], [], [], [], [])
1911 modified, added, removed = [], [], []
1926 modified, added, removed = [], [], []
1912 self._gitupdatestat()
1927 self._gitupdatestat()
1913 if rev2:
1928 if rev2:
1914 command = [b'diff-tree', b'--no-renames', b'-r', rev1, rev2]
1929 command = [b'diff-tree', b'--no-renames', b'-r', rev1, rev2]
1915 else:
1930 else:
1916 command = [b'diff-index', b'--no-renames', rev1]
1931 command = [b'diff-index', b'--no-renames', rev1]
1917 out = self._gitcommand(command)
1932 out = self._gitcommand(command)
1918 for line in out.split(b'\n'):
1933 for line in out.split(b'\n'):
1919 tab = line.find(b'\t')
1934 tab = line.find(b'\t')
1920 if tab == -1:
1935 if tab == -1:
1921 continue
1936 continue
1922 status, f = line[tab - 1 : tab], line[tab + 1 :]
1937 status, f = line[tab - 1 : tab], line[tab + 1 :]
1923 if status == b'M':
1938 if status == b'M':
1924 modified.append(f)
1939 modified.append(f)
1925 elif status == b'A':
1940 elif status == b'A':
1926 added.append(f)
1941 added.append(f)
1927 elif status == b'D':
1942 elif status == b'D':
1928 removed.append(f)
1943 removed.append(f)
1929
1944
1930 deleted, unknown, ignored, clean = [], [], [], []
1945 deleted, unknown, ignored, clean = [], [], [], []
1931
1946
1932 command = [b'status', b'--porcelain', b'-z']
1947 command = [b'status', b'--porcelain', b'-z']
1933 if opts.get('unknown'):
1948 if opts.get('unknown'):
1934 command += [b'--untracked-files=all']
1949 command += [b'--untracked-files=all']
1935 if opts.get('ignored'):
1950 if opts.get('ignored'):
1936 command += [b'--ignored']
1951 command += [b'--ignored']
1937 out = self._gitcommand(command)
1952 out = self._gitcommand(command)
1938
1953
1939 changedfiles = set()
1954 changedfiles = set()
1940 changedfiles.update(modified)
1955 changedfiles.update(modified)
1941 changedfiles.update(added)
1956 changedfiles.update(added)
1942 changedfiles.update(removed)
1957 changedfiles.update(removed)
1943 for line in out.split(b'\0'):
1958 for line in out.split(b'\0'):
1944 if not line:
1959 if not line:
1945 continue
1960 continue
1946 st = line[0:2]
1961 st = line[0:2]
1947 # moves and copies show 2 files on one line
1962 # moves and copies show 2 files on one line
1948 if line.find(b'\0') >= 0:
1963 if line.find(b'\0') >= 0:
1949 filename1, filename2 = line[3:].split(b'\0')
1964 filename1, filename2 = line[3:].split(b'\0')
1950 else:
1965 else:
1951 filename1 = line[3:]
1966 filename1 = line[3:]
1952 filename2 = None
1967 filename2 = None
1953
1968
1954 changedfiles.add(filename1)
1969 changedfiles.add(filename1)
1955 if filename2:
1970 if filename2:
1956 changedfiles.add(filename2)
1971 changedfiles.add(filename2)
1957
1972
1958 if st == b'??':
1973 if st == b'??':
1959 unknown.append(filename1)
1974 unknown.append(filename1)
1960 elif st == b'!!':
1975 elif st == b'!!':
1961 ignored.append(filename1)
1976 ignored.append(filename1)
1962
1977
1963 if opts.get('clean'):
1978 if opts.get('clean'):
1964 out = self._gitcommand([b'ls-files'])
1979 out = self._gitcommand([b'ls-files'])
1965 for f in out.split(b'\n'):
1980 for f in out.split(b'\n'):
1966 if not f in changedfiles:
1981 if not f in changedfiles:
1967 clean.append(f)
1982 clean.append(f)
1968
1983
1969 return scmutil.status(
1984 return scmutil.status(
1970 modified, added, removed, deleted, unknown, ignored, clean
1985 modified, added, removed, deleted, unknown, ignored, clean
1971 )
1986 )
1972
1987
1973 @annotatesubrepoerror
1988 @annotatesubrepoerror
1974 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1989 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1975 node1 = self._state[1]
1990 node1 = self._state[1]
1976 cmd = [b'diff', b'--no-renames']
1991 cmd = [b'diff', b'--no-renames']
1977 if opts['stat']:
1992 if opts['stat']:
1978 cmd.append(b'--stat')
1993 cmd.append(b'--stat')
1979 else:
1994 else:
1980 # for Git, this also implies '-p'
1995 # for Git, this also implies '-p'
1981 cmd.append(b'-U%d' % diffopts.context)
1996 cmd.append(b'-U%d' % diffopts.context)
1982
1997
1983 if diffopts.noprefix:
1998 if diffopts.noprefix:
1984 cmd.extend(
1999 cmd.extend(
1985 [b'--src-prefix=%s/' % prefix, b'--dst-prefix=%s/' % prefix]
2000 [b'--src-prefix=%s/' % prefix, b'--dst-prefix=%s/' % prefix]
1986 )
2001 )
1987 else:
2002 else:
1988 cmd.extend(
2003 cmd.extend(
1989 [b'--src-prefix=a/%s/' % prefix, b'--dst-prefix=b/%s/' % prefix]
2004 [b'--src-prefix=a/%s/' % prefix, b'--dst-prefix=b/%s/' % prefix]
1990 )
2005 )
1991
2006
1992 if diffopts.ignorews:
2007 if diffopts.ignorews:
1993 cmd.append(b'--ignore-all-space')
2008 cmd.append(b'--ignore-all-space')
1994 if diffopts.ignorewsamount:
2009 if diffopts.ignorewsamount:
1995 cmd.append(b'--ignore-space-change')
2010 cmd.append(b'--ignore-space-change')
1996 if (
2011 if (
1997 self._gitversion(self._gitcommand([b'--version'])) >= (1, 8, 4)
2012 self._gitversion(self._gitcommand([b'--version'])) >= (1, 8, 4)
1998 and diffopts.ignoreblanklines
2013 and diffopts.ignoreblanklines
1999 ):
2014 ):
2000 cmd.append(b'--ignore-blank-lines')
2015 cmd.append(b'--ignore-blank-lines')
2001
2016
2002 cmd.append(node1)
2017 cmd.append(node1)
2003 if node2:
2018 if node2:
2004 cmd.append(node2)
2019 cmd.append(node2)
2005
2020
2006 output = b""
2021 output = b""
2007 if match.always():
2022 if match.always():
2008 output += self._gitcommand(cmd) + b'\n'
2023 output += self._gitcommand(cmd) + b'\n'
2009 else:
2024 else:
2010 st = self.status(node2)
2025 st = self.status(node2)
2011 files = [
2026 files = [
2012 f
2027 f
2013 for sublist in (st.modified, st.added, st.removed)
2028 for sublist in (st.modified, st.added, st.removed)
2014 for f in sublist
2029 for f in sublist
2015 ]
2030 ]
2016 for f in files:
2031 for f in files:
2017 if match(f):
2032 if match(f):
2018 output += self._gitcommand(cmd + [b'--', f]) + b'\n'
2033 output += self._gitcommand(cmd + [b'--', f]) + b'\n'
2019
2034
2020 if output.strip():
2035 if output.strip():
2021 ui.write(output)
2036 ui.write(output)
2022
2037
2023 @annotatesubrepoerror
2038 @annotatesubrepoerror
2024 def revert(self, substate, *pats, **opts):
2039 def revert(self, substate, *pats, **opts):
2025 self.ui.status(_(b'reverting subrepo %s\n') % substate[0])
2040 self.ui.status(_(b'reverting subrepo %s\n') % substate[0])
2026 if not opts.get('no_backup'):
2041 if not opts.get('no_backup'):
2027 status = self.status(None)
2042 status = self.status(None)
2028 names = status.modified
2043 names = status.modified
2029 for name in names:
2044 for name in names:
2030 # backuppath() expects a path relative to the parent repo (the
2045 # backuppath() expects a path relative to the parent repo (the
2031 # repo that ui.origbackuppath is relative to)
2046 # repo that ui.origbackuppath is relative to)
2032 parentname = os.path.join(self._path, name)
2047 parentname = os.path.join(self._path, name)
2033 bakname = scmutil.backuppath(
2048 bakname = scmutil.backuppath(
2034 self.ui, self._subparent, parentname
2049 self.ui, self._subparent, parentname
2035 )
2050 )
2036 self.ui.note(
2051 self.ui.note(
2037 _(b'saving current version of %s as %s\n')
2052 _(b'saving current version of %s as %s\n')
2038 % (name, os.path.relpath(bakname))
2053 % (name, os.path.relpath(bakname))
2039 )
2054 )
2040 util.rename(self.wvfs.join(name), bakname)
2055 util.rename(self.wvfs.join(name), bakname)
2041
2056
2042 if not opts.get('dry_run'):
2057 if not opts.get('dry_run'):
2043 self.get(substate, overwrite=True)
2058 self.get(substate, overwrite=True)
2044 return []
2059 return []
2045
2060
2046 def shortid(self, revid):
2061 def shortid(self, revid):
2047 return revid[:7]
2062 return revid[:7]
2048
2063
2049
2064
2050 types = {
2065 types = {
2051 b'hg': hgsubrepo,
2066 b'hg': hgsubrepo,
2052 b'svn': svnsubrepo,
2067 b'svn': svnsubrepo,
2053 b'git': gitsubrepo,
2068 b'git': gitsubrepo,
2054 }
2069 }
@@ -1,30 +1,33 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 from mercurial.i18n import _
3 from mercurial.i18n import _
4 from mercurial import (
4 from mercurial import (
5 hg,
5 hg,
6 registrar,
6 registrar,
7 )
7 )
8
8
9 cmdtable = {}
9 cmdtable = {}
10 command = registrar.command(cmdtable)
10 command = registrar.command(cmdtable)
11
11
12
12
13 @command(b'getflogheads', [], b'path')
13 @command(b'getflogheads', [], b'path')
14 def getflogheads(ui, repo, path):
14 def getflogheads(ui, repo, path):
15 """
15 """
16 Extension printing a remotefilelog's heads
16 Extension printing a remotefilelog's heads
17
17
18 Used for testing purpose
18 Used for testing purpose
19 """
19 """
20
20
21 dest = repo.ui.expandpath(b'default')
21 dest = repo.ui.expandpath(b'default')
22 peer = hg.peer(repo, {}, dest)
22 peer = hg.peer(repo, {}, dest)
23
23
24 flogheads = peer.x_rfl_getflogheads(path)
24 try:
25 flogheads = peer.x_rfl_getflogheads(path)
26 finally:
27 peer.close()
25
28
26 if flogheads:
29 if flogheads:
27 for head in flogheads:
30 for head in flogheads:
28 ui.write(head + b'\n')
31 ui.write(head + b'\n')
29 else:
32 else:
30 ui.write(_(b'EMPTY\n'))
33 ui.write(_(b'EMPTY\n'))
@@ -1,2444 +1,2445 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 > cat > posixgetuser.py <<'EOF'
24 > cat > posixgetuser.py <<'EOF'
25 > import getpass
25 > import getpass
26 > from mercurial import pycompat
26 > from mercurial import pycompat
27 > from mercurial.utils import procutil
27 > from mercurial.utils import procutil
28 > def posixgetuser():
28 > def posixgetuser():
29 > return pycompat.fsencode(getpass.getuser())
29 > return pycompat.fsencode(getpass.getuser())
30 > if not pycompat.isposix:
30 > if not pycompat.isposix:
31 > procutil.getuser = posixgetuser # forcibly trust $LOGNAME
31 > procutil.getuser = posixgetuser # forcibly trust $LOGNAME
32 > EOF
32 > EOF
33
33
34 > init_config()
34 > init_config()
35 > {
35 > {
36 > cat > fakegroups.py <<EOF
36 > cat > fakegroups.py <<EOF
37 > from hgext import acl
37 > from hgext import acl
38 > def fakegetusers(ui, group):
38 > def fakegetusers(ui, group):
39 > try:
39 > try:
40 > return acl._getusersorig(ui, group)
40 > return acl._getusersorig(ui, group)
41 > except BaseException:
41 > except BaseException:
42 > return [b"fred", b"betty"]
42 > return [b"fred", b"betty"]
43 > acl._getusersorig = acl._getusers
43 > acl._getusersorig = acl._getusers
44 > acl._getusers = fakegetusers
44 > acl._getusers = fakegetusers
45 > EOF
45 > EOF
46 > rm -f acl.config
46 > rm -f acl.config
47 > cat > $config <<EOF
47 > cat > $config <<EOF
48 > [hooks]
48 > [hooks]
49 > pretxnchangegroup.acl = python:hgext.acl.hook
49 > pretxnchangegroup.acl = python:hgext.acl.hook
50 > prepushkey.acl = python:hgext.acl.hook
50 > prepushkey.acl = python:hgext.acl.hook
51 > [acl]
51 > [acl]
52 > sources = push
52 > sources = push
53 > [extensions]
53 > [extensions]
54 > f=`pwd`/fakegroups.py
54 > f=`pwd`/fakegroups.py
55 > posixgetuser=$TESTTMP/posixgetuser.py
55 > posixgetuser=$TESTTMP/posixgetuser.py
56 > EOF
56 > EOF
57 > }
57 > }
58
58
59 $ hg init a
59 $ hg init a
60 $ cd a
60 $ cd a
61 $ mkdir foo foo/Bar quux
61 $ mkdir foo foo/Bar quux
62 $ echo 'in foo' > foo/file.txt
62 $ echo 'in foo' > foo/file.txt
63 $ echo 'in foo/Bar' > foo/Bar/file.txt
63 $ echo 'in foo/Bar' > foo/Bar/file.txt
64 $ echo 'in quux' > quux/file.py
64 $ echo 'in quux' > quux/file.py
65 $ hg add -q
65 $ hg add -q
66 $ hg ci -m 'add files' -d '1000000 0'
66 $ hg ci -m 'add files' -d '1000000 0'
67 $ echo >> foo/file.txt
67 $ echo >> foo/file.txt
68 $ hg ci -m 'change foo/file' -d '1000001 0'
68 $ hg ci -m 'change foo/file' -d '1000001 0'
69 $ echo >> foo/Bar/file.txt
69 $ echo >> foo/Bar/file.txt
70 $ hg ci -m 'change foo/Bar/file' -d '1000002 0'
70 $ hg ci -m 'change foo/Bar/file' -d '1000002 0'
71 $ echo >> quux/file.py
71 $ echo >> quux/file.py
72 $ hg ci -m 'change quux/file' -d '1000003 0'
72 $ hg ci -m 'change quux/file' -d '1000003 0'
73 $ hg tip --quiet
73 $ hg tip --quiet
74 3:911600dab2ae
74 3:911600dab2ae
75
75
76 $ cd ..
76 $ cd ..
77 $ hg clone -r 0 a b
77 $ hg clone -r 0 a b
78 adding changesets
78 adding changesets
79 adding manifests
79 adding manifests
80 adding file changes
80 adding file changes
81 added 1 changesets with 3 changes to 3 files
81 added 1 changesets with 3 changes to 3 files
82 new changesets 6675d58eff77
82 new changesets 6675d58eff77
83 updating to branch default
83 updating to branch default
84 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
85
85
86 $ config=b/.hg/hgrc
86 $ config=b/.hg/hgrc
87 $ cat >> "$config" <<EOF
87 $ cat >> "$config" <<EOF
88 > [extensions]
88 > [extensions]
89 > posixgetuser=$TESTTMP/posixgetuser.py
89 > posixgetuser=$TESTTMP/posixgetuser.py
90 > EOF
90 > EOF
91
91
92 Extension disabled for lack of a hook
92 Extension disabled for lack of a hook
93
93
94 $ do_push fred
94 $ do_push fred
95 Pushing as user fred
95 Pushing as user fred
96 hgrc = """
96 hgrc = """
97 """
97 """
98 pushing to ../b
98 pushing to ../b
99 query 1; heads
99 query 1; heads
100 searching for changes
100 searching for changes
101 all remote heads known locally
101 all remote heads known locally
102 listing keys for "phases"
102 listing keys for "phases"
103 checking for updated bookmarks
103 checking for updated bookmarks
104 listing keys for "bookmarks"
104 listing keys for "bookmarks"
105 listing keys for "bookmarks"
105 listing keys for "bookmarks"
106 3 changesets found
106 3 changesets found
107 list of changesets:
107 list of changesets:
108 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
108 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
109 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
109 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
110 911600dab2ae7a9baff75958b84fe606851ce955
110 911600dab2ae7a9baff75958b84fe606851ce955
111 bundle2-output-bundle: "HG20", 5 parts total
111 bundle2-output-bundle: "HG20", 5 parts total
112 bundle2-output-part: "replycaps" 207 bytes payload
112 bundle2-output-part: "replycaps" 207 bytes payload
113 bundle2-output-part: "check:phases" 24 bytes payload
113 bundle2-output-part: "check:phases" 24 bytes payload
114 bundle2-output-part: "check:updated-heads" streamed payload
114 bundle2-output-part: "check:updated-heads" streamed payload
115 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
115 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
116 bundle2-output-part: "phase-heads" 24 bytes payload
116 bundle2-output-part: "phase-heads" 24 bytes payload
117 bundle2-input-bundle: with-transaction
117 bundle2-input-bundle: with-transaction
118 bundle2-input-part: "replycaps" supported
118 bundle2-input-part: "replycaps" supported
119 bundle2-input-part: total payload size 207
119 bundle2-input-part: total payload size 207
120 bundle2-input-part: "check:phases" supported
120 bundle2-input-part: "check:phases" supported
121 bundle2-input-part: total payload size 24
121 bundle2-input-part: total payload size 24
122 bundle2-input-part: "check:updated-heads" supported
122 bundle2-input-part: "check:updated-heads" supported
123 bundle2-input-part: total payload size 20
123 bundle2-input-part: total payload size 20
124 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
124 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
125 adding changesets
125 adding changesets
126 add changeset ef1ea85a6374
126 add changeset ef1ea85a6374
127 add changeset f9cafe1212c8
127 add changeset f9cafe1212c8
128 add changeset 911600dab2ae
128 add changeset 911600dab2ae
129 adding manifests
129 adding manifests
130 adding file changes
130 adding file changes
131 adding foo/Bar/file.txt revisions
131 adding foo/Bar/file.txt revisions
132 adding foo/file.txt revisions
132 adding foo/file.txt revisions
133 adding quux/file.py revisions
133 adding quux/file.py revisions
134 bundle2-input-part: total payload size 1553
134 bundle2-input-part: total payload size 1553
135 bundle2-input-part: "phase-heads" supported
135 bundle2-input-part: "phase-heads" supported
136 bundle2-input-part: total payload size 24
136 bundle2-input-part: total payload size 24
137 bundle2-input-bundle: 5 parts total
137 bundle2-input-bundle: 5 parts total
138 updating the branch cache
138 updating the branch cache
139 added 3 changesets with 3 changes to 3 files
139 added 3 changesets with 3 changes to 3 files
140 bundle2-output-bundle: "HG20", 1 parts total
140 bundle2-output-bundle: "HG20", 1 parts total
141 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
141 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
142 bundle2-input-bundle: no-transaction
142 bundle2-input-bundle: no-transaction
143 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
143 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
144 bundle2-input-bundle: 1 parts total
144 bundle2-input-bundle: 1 parts total
145 listing keys for "phases"
145 listing keys for "phases"
146 repository tip rolled back to revision 0 (undo push)
146 repository tip rolled back to revision 0 (undo push)
147 0:6675d58eff77
147 0:6675d58eff77
148
148
149
149
150 $ echo '[hooks]' >> $config
150 $ echo '[hooks]' >> $config
151 $ echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config
151 $ echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config
152 $ echo 'prepushkey.acl = python:hgext.acl.hook' >> $config
152 $ echo 'prepushkey.acl = python:hgext.acl.hook' >> $config
153
153
154 Extension disabled for lack of acl.sources
154 Extension disabled for lack of acl.sources
155
155
156 $ do_push fred
156 $ do_push fred
157 Pushing as user fred
157 Pushing as user fred
158 hgrc = """
158 hgrc = """
159 [hooks]
159 [hooks]
160 pretxnchangegroup.acl = python:hgext.acl.hook
160 pretxnchangegroup.acl = python:hgext.acl.hook
161 prepushkey.acl = python:hgext.acl.hook
161 prepushkey.acl = python:hgext.acl.hook
162 """
162 """
163 pushing to ../b
163 pushing to ../b
164 query 1; heads
164 query 1; heads
165 searching for changes
165 searching for changes
166 all remote heads known locally
166 all remote heads known locally
167 listing keys for "phases"
167 listing keys for "phases"
168 checking for updated bookmarks
168 checking for updated bookmarks
169 listing keys for "bookmarks"
169 listing keys for "bookmarks"
170 invalid branch cache (served): tip differs
170 invalid branch cache (served): tip differs
171 listing keys for "bookmarks"
171 listing keys for "bookmarks"
172 3 changesets found
172 3 changesets found
173 list of changesets:
173 list of changesets:
174 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
174 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
175 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
175 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
176 911600dab2ae7a9baff75958b84fe606851ce955
176 911600dab2ae7a9baff75958b84fe606851ce955
177 bundle2-output-bundle: "HG20", 5 parts total
177 bundle2-output-bundle: "HG20", 5 parts total
178 bundle2-output-part: "replycaps" 207 bytes payload
178 bundle2-output-part: "replycaps" 207 bytes payload
179 bundle2-output-part: "check:phases" 24 bytes payload
179 bundle2-output-part: "check:phases" 24 bytes payload
180 bundle2-output-part: "check:updated-heads" streamed payload
180 bundle2-output-part: "check:updated-heads" streamed payload
181 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
181 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
182 bundle2-output-part: "phase-heads" 24 bytes payload
182 bundle2-output-part: "phase-heads" 24 bytes payload
183 bundle2-input-bundle: with-transaction
183 bundle2-input-bundle: with-transaction
184 bundle2-input-part: "replycaps" supported
184 bundle2-input-part: "replycaps" supported
185 bundle2-input-part: total payload size 207
185 bundle2-input-part: total payload size 207
186 bundle2-input-part: "check:phases" supported
186 bundle2-input-part: "check:phases" supported
187 bundle2-input-part: total payload size 24
187 bundle2-input-part: total payload size 24
188 bundle2-input-part: "check:updated-heads" supported
188 bundle2-input-part: "check:updated-heads" supported
189 bundle2-input-part: total payload size 20
189 bundle2-input-part: total payload size 20
190 invalid branch cache (served): tip differs
190 invalid branch cache (served): tip differs
191 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
191 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
192 adding changesets
192 adding changesets
193 add changeset ef1ea85a6374
193 add changeset ef1ea85a6374
194 add changeset f9cafe1212c8
194 add changeset f9cafe1212c8
195 add changeset 911600dab2ae
195 add changeset 911600dab2ae
196 adding manifests
196 adding manifests
197 adding file changes
197 adding file changes
198 adding foo/Bar/file.txt revisions
198 adding foo/Bar/file.txt revisions
199 adding foo/file.txt revisions
199 adding foo/file.txt revisions
200 adding quux/file.py revisions
200 adding quux/file.py revisions
201 calling hook pretxnchangegroup.acl: hgext.acl.hook
201 calling hook pretxnchangegroup.acl: hgext.acl.hook
202 acl: changes have source "push" - skipping
202 acl: changes have source "push" - skipping
203 bundle2-input-part: total payload size 1553
203 bundle2-input-part: total payload size 1553
204 bundle2-input-part: "phase-heads" supported
204 bundle2-input-part: "phase-heads" supported
205 bundle2-input-part: total payload size 24
205 bundle2-input-part: total payload size 24
206 bundle2-input-bundle: 5 parts total
206 bundle2-input-bundle: 5 parts total
207 truncating cache/rbc-revs-v1 to 8
207 truncating cache/rbc-revs-v1 to 8
208 updating the branch cache
208 updating the branch cache
209 added 3 changesets with 3 changes to 3 files
209 added 3 changesets with 3 changes to 3 files
210 bundle2-output-bundle: "HG20", 1 parts total
210 bundle2-output-bundle: "HG20", 1 parts total
211 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
211 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
212 bundle2-input-bundle: no-transaction
212 bundle2-input-bundle: no-transaction
213 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
213 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
214 bundle2-input-bundle: 1 parts total
214 bundle2-input-bundle: 1 parts total
215 listing keys for "phases"
215 listing keys for "phases"
216 repository tip rolled back to revision 0 (undo push)
216 repository tip rolled back to revision 0 (undo push)
217 0:6675d58eff77
217 0:6675d58eff77
218
218
219
219
220 No [acl.allow]/[acl.deny]
220 No [acl.allow]/[acl.deny]
221
221
222 $ echo '[acl]' >> $config
222 $ echo '[acl]' >> $config
223 $ echo 'sources = push' >> $config
223 $ echo 'sources = push' >> $config
224 $ do_push fred
224 $ do_push fred
225 Pushing as user fred
225 Pushing as user fred
226 hgrc = """
226 hgrc = """
227 [hooks]
227 [hooks]
228 pretxnchangegroup.acl = python:hgext.acl.hook
228 pretxnchangegroup.acl = python:hgext.acl.hook
229 prepushkey.acl = python:hgext.acl.hook
229 prepushkey.acl = python:hgext.acl.hook
230 [acl]
230 [acl]
231 sources = push
231 sources = push
232 """
232 """
233 pushing to ../b
233 pushing to ../b
234 query 1; heads
234 query 1; heads
235 searching for changes
235 searching for changes
236 all remote heads known locally
236 all remote heads known locally
237 listing keys for "phases"
237 listing keys for "phases"
238 checking for updated bookmarks
238 checking for updated bookmarks
239 listing keys for "bookmarks"
239 listing keys for "bookmarks"
240 invalid branch cache (served): tip differs
240 invalid branch cache (served): tip differs
241 listing keys for "bookmarks"
241 listing keys for "bookmarks"
242 3 changesets found
242 3 changesets found
243 list of changesets:
243 list of changesets:
244 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
244 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
245 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
245 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
246 911600dab2ae7a9baff75958b84fe606851ce955
246 911600dab2ae7a9baff75958b84fe606851ce955
247 bundle2-output-bundle: "HG20", 5 parts total
247 bundle2-output-bundle: "HG20", 5 parts total
248 bundle2-output-part: "replycaps" 207 bytes payload
248 bundle2-output-part: "replycaps" 207 bytes payload
249 bundle2-output-part: "check:phases" 24 bytes payload
249 bundle2-output-part: "check:phases" 24 bytes payload
250 bundle2-output-part: "check:updated-heads" streamed payload
250 bundle2-output-part: "check:updated-heads" streamed payload
251 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
251 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
252 bundle2-output-part: "phase-heads" 24 bytes payload
252 bundle2-output-part: "phase-heads" 24 bytes payload
253 bundle2-input-bundle: with-transaction
253 bundle2-input-bundle: with-transaction
254 bundle2-input-part: "replycaps" supported
254 bundle2-input-part: "replycaps" supported
255 bundle2-input-part: total payload size 207
255 bundle2-input-part: total payload size 207
256 bundle2-input-part: "check:phases" supported
256 bundle2-input-part: "check:phases" supported
257 bundle2-input-part: total payload size 24
257 bundle2-input-part: total payload size 24
258 bundle2-input-part: "check:updated-heads" supported
258 bundle2-input-part: "check:updated-heads" supported
259 bundle2-input-part: total payload size 20
259 bundle2-input-part: total payload size 20
260 invalid branch cache (served): tip differs
260 invalid branch cache (served): tip differs
261 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
261 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
262 adding changesets
262 adding changesets
263 add changeset ef1ea85a6374
263 add changeset ef1ea85a6374
264 add changeset f9cafe1212c8
264 add changeset f9cafe1212c8
265 add changeset 911600dab2ae
265 add changeset 911600dab2ae
266 adding manifests
266 adding manifests
267 adding file changes
267 adding file changes
268 adding foo/Bar/file.txt revisions
268 adding foo/Bar/file.txt revisions
269 adding foo/file.txt revisions
269 adding foo/file.txt revisions
270 adding quux/file.py revisions
270 adding quux/file.py revisions
271 calling hook pretxnchangegroup.acl: hgext.acl.hook
271 calling hook pretxnchangegroup.acl: hgext.acl.hook
272 acl: checking access for user "fred"
272 acl: checking access for user "fred"
273 acl: acl.allow.branches not enabled
273 acl: acl.allow.branches not enabled
274 acl: acl.deny.branches not enabled
274 acl: acl.deny.branches not enabled
275 acl: acl.allow not enabled
275 acl: acl.allow not enabled
276 acl: acl.deny not enabled
276 acl: acl.deny not enabled
277 acl: branch access granted: "ef1ea85a6374" on branch "default"
277 acl: branch access granted: "ef1ea85a6374" on branch "default"
278 acl: path access granted: "ef1ea85a6374"
278 acl: path access granted: "ef1ea85a6374"
279 acl: branch access granted: "f9cafe1212c8" on branch "default"
279 acl: branch access granted: "f9cafe1212c8" on branch "default"
280 acl: path access granted: "f9cafe1212c8"
280 acl: path access granted: "f9cafe1212c8"
281 acl: branch access granted: "911600dab2ae" on branch "default"
281 acl: branch access granted: "911600dab2ae" on branch "default"
282 acl: path access granted: "911600dab2ae"
282 acl: path access granted: "911600dab2ae"
283 bundle2-input-part: total payload size 1553
283 bundle2-input-part: total payload size 1553
284 bundle2-input-part: "phase-heads" supported
284 bundle2-input-part: "phase-heads" supported
285 bundle2-input-part: total payload size 24
285 bundle2-input-part: total payload size 24
286 bundle2-input-bundle: 5 parts total
286 bundle2-input-bundle: 5 parts total
287 truncating cache/rbc-revs-v1 to 8
287 truncating cache/rbc-revs-v1 to 8
288 updating the branch cache
288 updating the branch cache
289 added 3 changesets with 3 changes to 3 files
289 added 3 changesets with 3 changes to 3 files
290 bundle2-output-bundle: "HG20", 1 parts total
290 bundle2-output-bundle: "HG20", 1 parts total
291 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
291 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
292 bundle2-input-bundle: no-transaction
292 bundle2-input-bundle: no-transaction
293 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
293 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
294 bundle2-input-bundle: 1 parts total
294 bundle2-input-bundle: 1 parts total
295 listing keys for "phases"
295 listing keys for "phases"
296 repository tip rolled back to revision 0 (undo push)
296 repository tip rolled back to revision 0 (undo push)
297 0:6675d58eff77
297 0:6675d58eff77
298
298
299
299
300 Empty [acl.allow]
300 Empty [acl.allow]
301
301
302 $ echo '[acl.allow]' >> $config
302 $ echo '[acl.allow]' >> $config
303 $ do_push fred
303 $ do_push fred
304 Pushing as user fred
304 Pushing as user fred
305 hgrc = """
305 hgrc = """
306 [hooks]
306 [hooks]
307 pretxnchangegroup.acl = python:hgext.acl.hook
307 pretxnchangegroup.acl = python:hgext.acl.hook
308 prepushkey.acl = python:hgext.acl.hook
308 prepushkey.acl = python:hgext.acl.hook
309 [acl]
309 [acl]
310 sources = push
310 sources = push
311 [acl.allow]
311 [acl.allow]
312 """
312 """
313 pushing to ../b
313 pushing to ../b
314 query 1; heads
314 query 1; heads
315 searching for changes
315 searching for changes
316 all remote heads known locally
316 all remote heads known locally
317 listing keys for "phases"
317 listing keys for "phases"
318 checking for updated bookmarks
318 checking for updated bookmarks
319 listing keys for "bookmarks"
319 listing keys for "bookmarks"
320 invalid branch cache (served): tip differs
320 invalid branch cache (served): tip differs
321 listing keys for "bookmarks"
321 listing keys for "bookmarks"
322 3 changesets found
322 3 changesets found
323 list of changesets:
323 list of changesets:
324 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
324 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
325 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
325 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
326 911600dab2ae7a9baff75958b84fe606851ce955
326 911600dab2ae7a9baff75958b84fe606851ce955
327 bundle2-output-bundle: "HG20", 5 parts total
327 bundle2-output-bundle: "HG20", 5 parts total
328 bundle2-output-part: "replycaps" 207 bytes payload
328 bundle2-output-part: "replycaps" 207 bytes payload
329 bundle2-output-part: "check:phases" 24 bytes payload
329 bundle2-output-part: "check:phases" 24 bytes payload
330 bundle2-output-part: "check:updated-heads" streamed payload
330 bundle2-output-part: "check:updated-heads" streamed payload
331 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
331 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
332 bundle2-output-part: "phase-heads" 24 bytes payload
332 bundle2-output-part: "phase-heads" 24 bytes payload
333 bundle2-input-bundle: with-transaction
333 bundle2-input-bundle: with-transaction
334 bundle2-input-part: "replycaps" supported
334 bundle2-input-part: "replycaps" supported
335 bundle2-input-part: total payload size 207
335 bundle2-input-part: total payload size 207
336 bundle2-input-part: "check:phases" supported
336 bundle2-input-part: "check:phases" supported
337 bundle2-input-part: total payload size 24
337 bundle2-input-part: total payload size 24
338 bundle2-input-part: "check:updated-heads" supported
338 bundle2-input-part: "check:updated-heads" supported
339 bundle2-input-part: total payload size 20
339 bundle2-input-part: total payload size 20
340 invalid branch cache (served): tip differs
340 invalid branch cache (served): tip differs
341 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
341 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
342 adding changesets
342 adding changesets
343 add changeset ef1ea85a6374
343 add changeset ef1ea85a6374
344 add changeset f9cafe1212c8
344 add changeset f9cafe1212c8
345 add changeset 911600dab2ae
345 add changeset 911600dab2ae
346 adding manifests
346 adding manifests
347 adding file changes
347 adding file changes
348 adding foo/Bar/file.txt revisions
348 adding foo/Bar/file.txt revisions
349 adding foo/file.txt revisions
349 adding foo/file.txt revisions
350 adding quux/file.py revisions
350 adding quux/file.py revisions
351 calling hook pretxnchangegroup.acl: hgext.acl.hook
351 calling hook pretxnchangegroup.acl: hgext.acl.hook
352 acl: checking access for user "fred"
352 acl: checking access for user "fred"
353 acl: acl.allow.branches not enabled
353 acl: acl.allow.branches not enabled
354 acl: acl.deny.branches not enabled
354 acl: acl.deny.branches not enabled
355 acl: acl.allow enabled, 0 entries for user fred
355 acl: acl.allow enabled, 0 entries for user fred
356 acl: acl.deny not enabled
356 acl: acl.deny not enabled
357 acl: branch access granted: "ef1ea85a6374" on branch "default"
357 acl: branch access granted: "ef1ea85a6374" on branch "default"
358 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
358 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
359 bundle2-input-part: total payload size 1553
359 bundle2-input-part: total payload size 1553
360 bundle2-input-part: total payload size 24
360 bundle2-input-part: total payload size 24
361 bundle2-input-bundle: 5 parts total
361 bundle2-input-bundle: 5 parts total
362 transaction abort!
362 transaction abort!
363 rollback completed
363 rollback completed
364 truncating cache/rbc-revs-v1 to 8
364 abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
365 abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
365 no rollback information available
366 no rollback information available
366 0:6675d58eff77
367 0:6675d58eff77
367
368
368
369
369 fred is allowed inside foo/
370 fred is allowed inside foo/
370
371
371 $ echo 'foo/** = fred' >> $config
372 $ echo 'foo/** = fred' >> $config
372 $ do_push fred
373 $ do_push fred
373 Pushing as user fred
374 Pushing as user fred
374 hgrc = """
375 hgrc = """
375 [hooks]
376 [hooks]
376 pretxnchangegroup.acl = python:hgext.acl.hook
377 pretxnchangegroup.acl = python:hgext.acl.hook
377 prepushkey.acl = python:hgext.acl.hook
378 prepushkey.acl = python:hgext.acl.hook
378 [acl]
379 [acl]
379 sources = push
380 sources = push
380 [acl.allow]
381 [acl.allow]
381 foo/** = fred
382 foo/** = fred
382 """
383 """
383 pushing to ../b
384 pushing to ../b
384 query 1; heads
385 query 1; heads
385 searching for changes
386 searching for changes
386 all remote heads known locally
387 all remote heads known locally
387 listing keys for "phases"
388 listing keys for "phases"
388 checking for updated bookmarks
389 checking for updated bookmarks
389 listing keys for "bookmarks"
390 listing keys for "bookmarks"
390 invalid branch cache (served): tip differs
391 invalid branch cache (served): tip differs
391 listing keys for "bookmarks"
392 listing keys for "bookmarks"
392 3 changesets found
393 3 changesets found
393 list of changesets:
394 list of changesets:
394 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
395 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
395 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
396 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
396 911600dab2ae7a9baff75958b84fe606851ce955
397 911600dab2ae7a9baff75958b84fe606851ce955
397 bundle2-output-bundle: "HG20", 5 parts total
398 bundle2-output-bundle: "HG20", 5 parts total
398 bundle2-output-part: "replycaps" 207 bytes payload
399 bundle2-output-part: "replycaps" 207 bytes payload
399 bundle2-output-part: "check:phases" 24 bytes payload
400 bundle2-output-part: "check:phases" 24 bytes payload
400 bundle2-output-part: "check:updated-heads" streamed payload
401 bundle2-output-part: "check:updated-heads" streamed payload
401 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
402 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
402 bundle2-output-part: "phase-heads" 24 bytes payload
403 bundle2-output-part: "phase-heads" 24 bytes payload
403 bundle2-input-bundle: with-transaction
404 bundle2-input-bundle: with-transaction
404 bundle2-input-part: "replycaps" supported
405 bundle2-input-part: "replycaps" supported
405 bundle2-input-part: total payload size 207
406 bundle2-input-part: total payload size 207
406 bundle2-input-part: "check:phases" supported
407 bundle2-input-part: "check:phases" supported
407 bundle2-input-part: total payload size 24
408 bundle2-input-part: total payload size 24
408 bundle2-input-part: "check:updated-heads" supported
409 bundle2-input-part: "check:updated-heads" supported
409 bundle2-input-part: total payload size 20
410 bundle2-input-part: total payload size 20
410 invalid branch cache (served): tip differs
411 invalid branch cache (served): tip differs
411 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
412 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
412 adding changesets
413 adding changesets
413 add changeset ef1ea85a6374
414 add changeset ef1ea85a6374
414 add changeset f9cafe1212c8
415 add changeset f9cafe1212c8
415 add changeset 911600dab2ae
416 add changeset 911600dab2ae
416 adding manifests
417 adding manifests
417 adding file changes
418 adding file changes
418 adding foo/Bar/file.txt revisions
419 adding foo/Bar/file.txt revisions
419 adding foo/file.txt revisions
420 adding foo/file.txt revisions
420 adding quux/file.py revisions
421 adding quux/file.py revisions
421 calling hook pretxnchangegroup.acl: hgext.acl.hook
422 calling hook pretxnchangegroup.acl: hgext.acl.hook
422 acl: checking access for user "fred"
423 acl: checking access for user "fred"
423 acl: acl.allow.branches not enabled
424 acl: acl.allow.branches not enabled
424 acl: acl.deny.branches not enabled
425 acl: acl.deny.branches not enabled
425 acl: acl.allow enabled, 1 entries for user fred
426 acl: acl.allow enabled, 1 entries for user fred
426 acl: acl.deny not enabled
427 acl: acl.deny not enabled
427 acl: branch access granted: "ef1ea85a6374" on branch "default"
428 acl: branch access granted: "ef1ea85a6374" on branch "default"
428 acl: path access granted: "ef1ea85a6374"
429 acl: path access granted: "ef1ea85a6374"
429 acl: branch access granted: "f9cafe1212c8" on branch "default"
430 acl: branch access granted: "f9cafe1212c8" on branch "default"
430 acl: path access granted: "f9cafe1212c8"
431 acl: path access granted: "f9cafe1212c8"
431 acl: branch access granted: "911600dab2ae" on branch "default"
432 acl: branch access granted: "911600dab2ae" on branch "default"
432 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
433 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
433 bundle2-input-part: total payload size 1553
434 bundle2-input-part: total payload size 1553
434 bundle2-input-part: total payload size 24
435 bundle2-input-part: total payload size 24
435 bundle2-input-bundle: 5 parts total
436 bundle2-input-bundle: 5 parts total
436 transaction abort!
437 transaction abort!
437 rollback completed
438 rollback completed
438 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
439 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
439 no rollback information available
440 no rollback information available
440 0:6675d58eff77
441 0:6675d58eff77
441
442
442
443
443 Empty [acl.deny]
444 Empty [acl.deny]
444
445
445 $ echo '[acl.deny]' >> $config
446 $ echo '[acl.deny]' >> $config
446 $ do_push barney
447 $ do_push barney
447 Pushing as user barney
448 Pushing as user barney
448 hgrc = """
449 hgrc = """
449 [hooks]
450 [hooks]
450 pretxnchangegroup.acl = python:hgext.acl.hook
451 pretxnchangegroup.acl = python:hgext.acl.hook
451 prepushkey.acl = python:hgext.acl.hook
452 prepushkey.acl = python:hgext.acl.hook
452 [acl]
453 [acl]
453 sources = push
454 sources = push
454 [acl.allow]
455 [acl.allow]
455 foo/** = fred
456 foo/** = fred
456 [acl.deny]
457 [acl.deny]
457 """
458 """
458 pushing to ../b
459 pushing to ../b
459 query 1; heads
460 query 1; heads
460 searching for changes
461 searching for changes
461 all remote heads known locally
462 all remote heads known locally
462 listing keys for "phases"
463 listing keys for "phases"
463 checking for updated bookmarks
464 checking for updated bookmarks
464 listing keys for "bookmarks"
465 listing keys for "bookmarks"
465 invalid branch cache (served): tip differs
466 invalid branch cache (served): tip differs
466 listing keys for "bookmarks"
467 listing keys for "bookmarks"
467 3 changesets found
468 3 changesets found
468 list of changesets:
469 list of changesets:
469 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
470 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
470 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
471 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
471 911600dab2ae7a9baff75958b84fe606851ce955
472 911600dab2ae7a9baff75958b84fe606851ce955
472 bundle2-output-bundle: "HG20", 5 parts total
473 bundle2-output-bundle: "HG20", 5 parts total
473 bundle2-output-part: "replycaps" 207 bytes payload
474 bundle2-output-part: "replycaps" 207 bytes payload
474 bundle2-output-part: "check:phases" 24 bytes payload
475 bundle2-output-part: "check:phases" 24 bytes payload
475 bundle2-output-part: "check:updated-heads" streamed payload
476 bundle2-output-part: "check:updated-heads" streamed payload
476 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
477 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
477 bundle2-output-part: "phase-heads" 24 bytes payload
478 bundle2-output-part: "phase-heads" 24 bytes payload
478 bundle2-input-bundle: with-transaction
479 bundle2-input-bundle: with-transaction
479 bundle2-input-part: "replycaps" supported
480 bundle2-input-part: "replycaps" supported
480 bundle2-input-part: total payload size 207
481 bundle2-input-part: total payload size 207
481 bundle2-input-part: "check:phases" supported
482 bundle2-input-part: "check:phases" supported
482 bundle2-input-part: total payload size 24
483 bundle2-input-part: total payload size 24
483 bundle2-input-part: "check:updated-heads" supported
484 bundle2-input-part: "check:updated-heads" supported
484 bundle2-input-part: total payload size 20
485 bundle2-input-part: total payload size 20
485 invalid branch cache (served): tip differs
486 invalid branch cache (served): tip differs
486 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
487 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
487 adding changesets
488 adding changesets
488 add changeset ef1ea85a6374
489 add changeset ef1ea85a6374
489 add changeset f9cafe1212c8
490 add changeset f9cafe1212c8
490 add changeset 911600dab2ae
491 add changeset 911600dab2ae
491 adding manifests
492 adding manifests
492 adding file changes
493 adding file changes
493 adding foo/Bar/file.txt revisions
494 adding foo/Bar/file.txt revisions
494 adding foo/file.txt revisions
495 adding foo/file.txt revisions
495 adding quux/file.py revisions
496 adding quux/file.py revisions
496 calling hook pretxnchangegroup.acl: hgext.acl.hook
497 calling hook pretxnchangegroup.acl: hgext.acl.hook
497 acl: checking access for user "barney"
498 acl: checking access for user "barney"
498 acl: acl.allow.branches not enabled
499 acl: acl.allow.branches not enabled
499 acl: acl.deny.branches not enabled
500 acl: acl.deny.branches not enabled
500 acl: acl.allow enabled, 0 entries for user barney
501 acl: acl.allow enabled, 0 entries for user barney
501 acl: acl.deny enabled, 0 entries for user barney
502 acl: acl.deny enabled, 0 entries for user barney
502 acl: branch access granted: "ef1ea85a6374" on branch "default"
503 acl: branch access granted: "ef1ea85a6374" on branch "default"
503 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
504 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
504 bundle2-input-part: total payload size 1553
505 bundle2-input-part: total payload size 1553
505 bundle2-input-part: total payload size 24
506 bundle2-input-part: total payload size 24
506 bundle2-input-bundle: 5 parts total
507 bundle2-input-bundle: 5 parts total
507 transaction abort!
508 transaction abort!
508 rollback completed
509 rollback completed
509 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
510 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
510 no rollback information available
511 no rollback information available
511 0:6675d58eff77
512 0:6675d58eff77
512
513
513
514
514 fred is allowed inside foo/, but not foo/bar/ (case matters)
515 fred is allowed inside foo/, but not foo/bar/ (case matters)
515
516
516 $ echo 'foo/bar/** = fred' >> $config
517 $ echo 'foo/bar/** = fred' >> $config
517 $ do_push fred
518 $ do_push fred
518 Pushing as user fred
519 Pushing as user fred
519 hgrc = """
520 hgrc = """
520 [hooks]
521 [hooks]
521 pretxnchangegroup.acl = python:hgext.acl.hook
522 pretxnchangegroup.acl = python:hgext.acl.hook
522 prepushkey.acl = python:hgext.acl.hook
523 prepushkey.acl = python:hgext.acl.hook
523 [acl]
524 [acl]
524 sources = push
525 sources = push
525 [acl.allow]
526 [acl.allow]
526 foo/** = fred
527 foo/** = fred
527 [acl.deny]
528 [acl.deny]
528 foo/bar/** = fred
529 foo/bar/** = fred
529 """
530 """
530 pushing to ../b
531 pushing to ../b
531 query 1; heads
532 query 1; heads
532 searching for changes
533 searching for changes
533 all remote heads known locally
534 all remote heads known locally
534 listing keys for "phases"
535 listing keys for "phases"
535 checking for updated bookmarks
536 checking for updated bookmarks
536 listing keys for "bookmarks"
537 listing keys for "bookmarks"
537 invalid branch cache (served): tip differs
538 invalid branch cache (served): tip differs
538 listing keys for "bookmarks"
539 listing keys for "bookmarks"
539 3 changesets found
540 3 changesets found
540 list of changesets:
541 list of changesets:
541 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
542 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
542 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
543 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
543 911600dab2ae7a9baff75958b84fe606851ce955
544 911600dab2ae7a9baff75958b84fe606851ce955
544 bundle2-output-bundle: "HG20", 5 parts total
545 bundle2-output-bundle: "HG20", 5 parts total
545 bundle2-output-part: "replycaps" 207 bytes payload
546 bundle2-output-part: "replycaps" 207 bytes payload
546 bundle2-output-part: "check:phases" 24 bytes payload
547 bundle2-output-part: "check:phases" 24 bytes payload
547 bundle2-output-part: "check:updated-heads" streamed payload
548 bundle2-output-part: "check:updated-heads" streamed payload
548 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
549 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
549 bundle2-output-part: "phase-heads" 24 bytes payload
550 bundle2-output-part: "phase-heads" 24 bytes payload
550 bundle2-input-bundle: with-transaction
551 bundle2-input-bundle: with-transaction
551 bundle2-input-part: "replycaps" supported
552 bundle2-input-part: "replycaps" supported
552 bundle2-input-part: total payload size 207
553 bundle2-input-part: total payload size 207
553 bundle2-input-part: "check:phases" supported
554 bundle2-input-part: "check:phases" supported
554 bundle2-input-part: total payload size 24
555 bundle2-input-part: total payload size 24
555 bundle2-input-part: "check:updated-heads" supported
556 bundle2-input-part: "check:updated-heads" supported
556 bundle2-input-part: total payload size 20
557 bundle2-input-part: total payload size 20
557 invalid branch cache (served): tip differs
558 invalid branch cache (served): tip differs
558 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
559 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
559 adding changesets
560 adding changesets
560 add changeset ef1ea85a6374
561 add changeset ef1ea85a6374
561 add changeset f9cafe1212c8
562 add changeset f9cafe1212c8
562 add changeset 911600dab2ae
563 add changeset 911600dab2ae
563 adding manifests
564 adding manifests
564 adding file changes
565 adding file changes
565 adding foo/Bar/file.txt revisions
566 adding foo/Bar/file.txt revisions
566 adding foo/file.txt revisions
567 adding foo/file.txt revisions
567 adding quux/file.py revisions
568 adding quux/file.py revisions
568 calling hook pretxnchangegroup.acl: hgext.acl.hook
569 calling hook pretxnchangegroup.acl: hgext.acl.hook
569 acl: checking access for user "fred"
570 acl: checking access for user "fred"
570 acl: acl.allow.branches not enabled
571 acl: acl.allow.branches not enabled
571 acl: acl.deny.branches not enabled
572 acl: acl.deny.branches not enabled
572 acl: acl.allow enabled, 1 entries for user fred
573 acl: acl.allow enabled, 1 entries for user fred
573 acl: acl.deny enabled, 1 entries for user fred
574 acl: acl.deny enabled, 1 entries for user fred
574 acl: branch access granted: "ef1ea85a6374" on branch "default"
575 acl: branch access granted: "ef1ea85a6374" on branch "default"
575 acl: path access granted: "ef1ea85a6374"
576 acl: path access granted: "ef1ea85a6374"
576 acl: branch access granted: "f9cafe1212c8" on branch "default"
577 acl: branch access granted: "f9cafe1212c8" on branch "default"
577 acl: path access granted: "f9cafe1212c8"
578 acl: path access granted: "f9cafe1212c8"
578 acl: branch access granted: "911600dab2ae" on branch "default"
579 acl: branch access granted: "911600dab2ae" on branch "default"
579 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
580 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
580 bundle2-input-part: total payload size 1553
581 bundle2-input-part: total payload size 1553
581 bundle2-input-part: total payload size 24
582 bundle2-input-part: total payload size 24
582 bundle2-input-bundle: 5 parts total
583 bundle2-input-bundle: 5 parts total
583 transaction abort!
584 transaction abort!
584 rollback completed
585 rollback completed
585 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
586 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
586 no rollback information available
587 no rollback information available
587 0:6675d58eff77
588 0:6675d58eff77
588
589
589
590
590 fred is allowed inside foo/, but not foo/Bar/
591 fred is allowed inside foo/, but not foo/Bar/
591
592
592 $ echo 'foo/Bar/** = fred' >> $config
593 $ echo 'foo/Bar/** = fred' >> $config
593 $ do_push fred
594 $ do_push fred
594 Pushing as user fred
595 Pushing as user fred
595 hgrc = """
596 hgrc = """
596 [hooks]
597 [hooks]
597 pretxnchangegroup.acl = python:hgext.acl.hook
598 pretxnchangegroup.acl = python:hgext.acl.hook
598 prepushkey.acl = python:hgext.acl.hook
599 prepushkey.acl = python:hgext.acl.hook
599 [acl]
600 [acl]
600 sources = push
601 sources = push
601 [acl.allow]
602 [acl.allow]
602 foo/** = fred
603 foo/** = fred
603 [acl.deny]
604 [acl.deny]
604 foo/bar/** = fred
605 foo/bar/** = fred
605 foo/Bar/** = fred
606 foo/Bar/** = fred
606 """
607 """
607 pushing to ../b
608 pushing to ../b
608 query 1; heads
609 query 1; heads
609 searching for changes
610 searching for changes
610 all remote heads known locally
611 all remote heads known locally
611 listing keys for "phases"
612 listing keys for "phases"
612 checking for updated bookmarks
613 checking for updated bookmarks
613 listing keys for "bookmarks"
614 listing keys for "bookmarks"
614 invalid branch cache (served): tip differs
615 invalid branch cache (served): tip differs
615 listing keys for "bookmarks"
616 listing keys for "bookmarks"
616 3 changesets found
617 3 changesets found
617 list of changesets:
618 list of changesets:
618 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
619 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
619 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
620 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
620 911600dab2ae7a9baff75958b84fe606851ce955
621 911600dab2ae7a9baff75958b84fe606851ce955
621 bundle2-output-bundle: "HG20", 5 parts total
622 bundle2-output-bundle: "HG20", 5 parts total
622 bundle2-output-part: "replycaps" 207 bytes payload
623 bundle2-output-part: "replycaps" 207 bytes payload
623 bundle2-output-part: "check:phases" 24 bytes payload
624 bundle2-output-part: "check:phases" 24 bytes payload
624 bundle2-output-part: "check:updated-heads" streamed payload
625 bundle2-output-part: "check:updated-heads" streamed payload
625 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
626 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
626 bundle2-output-part: "phase-heads" 24 bytes payload
627 bundle2-output-part: "phase-heads" 24 bytes payload
627 bundle2-input-bundle: with-transaction
628 bundle2-input-bundle: with-transaction
628 bundle2-input-part: "replycaps" supported
629 bundle2-input-part: "replycaps" supported
629 bundle2-input-part: total payload size 207
630 bundle2-input-part: total payload size 207
630 bundle2-input-part: "check:phases" supported
631 bundle2-input-part: "check:phases" supported
631 bundle2-input-part: total payload size 24
632 bundle2-input-part: total payload size 24
632 bundle2-input-part: "check:updated-heads" supported
633 bundle2-input-part: "check:updated-heads" supported
633 bundle2-input-part: total payload size 20
634 bundle2-input-part: total payload size 20
634 invalid branch cache (served): tip differs
635 invalid branch cache (served): tip differs
635 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
636 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
636 adding changesets
637 adding changesets
637 add changeset ef1ea85a6374
638 add changeset ef1ea85a6374
638 add changeset f9cafe1212c8
639 add changeset f9cafe1212c8
639 add changeset 911600dab2ae
640 add changeset 911600dab2ae
640 adding manifests
641 adding manifests
641 adding file changes
642 adding file changes
642 adding foo/Bar/file.txt revisions
643 adding foo/Bar/file.txt revisions
643 adding foo/file.txt revisions
644 adding foo/file.txt revisions
644 adding quux/file.py revisions
645 adding quux/file.py revisions
645 calling hook pretxnchangegroup.acl: hgext.acl.hook
646 calling hook pretxnchangegroup.acl: hgext.acl.hook
646 acl: checking access for user "fred"
647 acl: checking access for user "fred"
647 acl: acl.allow.branches not enabled
648 acl: acl.allow.branches not enabled
648 acl: acl.deny.branches not enabled
649 acl: acl.deny.branches not enabled
649 acl: acl.allow enabled, 1 entries for user fred
650 acl: acl.allow enabled, 1 entries for user fred
650 acl: acl.deny enabled, 2 entries for user fred
651 acl: acl.deny enabled, 2 entries for user fred
651 acl: branch access granted: "ef1ea85a6374" on branch "default"
652 acl: branch access granted: "ef1ea85a6374" on branch "default"
652 acl: path access granted: "ef1ea85a6374"
653 acl: path access granted: "ef1ea85a6374"
653 acl: branch access granted: "f9cafe1212c8" on branch "default"
654 acl: branch access granted: "f9cafe1212c8" on branch "default"
654 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
655 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
655 bundle2-input-part: total payload size 1553
656 bundle2-input-part: total payload size 1553
656 bundle2-input-part: total payload size 24
657 bundle2-input-part: total payload size 24
657 bundle2-input-bundle: 5 parts total
658 bundle2-input-bundle: 5 parts total
658 transaction abort!
659 transaction abort!
659 rollback completed
660 rollback completed
660 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
661 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
661 no rollback information available
662 no rollback information available
662 0:6675d58eff77
663 0:6675d58eff77
663
664
664
665
665 $ echo 'barney is not mentioned => not allowed anywhere'
666 $ echo 'barney is not mentioned => not allowed anywhere'
666 barney is not mentioned => not allowed anywhere
667 barney is not mentioned => not allowed anywhere
667 $ do_push barney
668 $ do_push barney
668 Pushing as user barney
669 Pushing as user barney
669 hgrc = """
670 hgrc = """
670 [hooks]
671 [hooks]
671 pretxnchangegroup.acl = python:hgext.acl.hook
672 pretxnchangegroup.acl = python:hgext.acl.hook
672 prepushkey.acl = python:hgext.acl.hook
673 prepushkey.acl = python:hgext.acl.hook
673 [acl]
674 [acl]
674 sources = push
675 sources = push
675 [acl.allow]
676 [acl.allow]
676 foo/** = fred
677 foo/** = fred
677 [acl.deny]
678 [acl.deny]
678 foo/bar/** = fred
679 foo/bar/** = fred
679 foo/Bar/** = fred
680 foo/Bar/** = fred
680 """
681 """
681 pushing to ../b
682 pushing to ../b
682 query 1; heads
683 query 1; heads
683 searching for changes
684 searching for changes
684 all remote heads known locally
685 all remote heads known locally
685 listing keys for "phases"
686 listing keys for "phases"
686 checking for updated bookmarks
687 checking for updated bookmarks
687 listing keys for "bookmarks"
688 listing keys for "bookmarks"
688 invalid branch cache (served): tip differs
689 invalid branch cache (served): tip differs
689 listing keys for "bookmarks"
690 listing keys for "bookmarks"
690 3 changesets found
691 3 changesets found
691 list of changesets:
692 list of changesets:
692 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
693 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
693 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
694 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
694 911600dab2ae7a9baff75958b84fe606851ce955
695 911600dab2ae7a9baff75958b84fe606851ce955
695 bundle2-output-bundle: "HG20", 5 parts total
696 bundle2-output-bundle: "HG20", 5 parts total
696 bundle2-output-part: "replycaps" 207 bytes payload
697 bundle2-output-part: "replycaps" 207 bytes payload
697 bundle2-output-part: "check:phases" 24 bytes payload
698 bundle2-output-part: "check:phases" 24 bytes payload
698 bundle2-output-part: "check:updated-heads" streamed payload
699 bundle2-output-part: "check:updated-heads" streamed payload
699 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
700 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
700 bundle2-output-part: "phase-heads" 24 bytes payload
701 bundle2-output-part: "phase-heads" 24 bytes payload
701 bundle2-input-bundle: with-transaction
702 bundle2-input-bundle: with-transaction
702 bundle2-input-part: "replycaps" supported
703 bundle2-input-part: "replycaps" supported
703 bundle2-input-part: total payload size 207
704 bundle2-input-part: total payload size 207
704 bundle2-input-part: "check:phases" supported
705 bundle2-input-part: "check:phases" supported
705 bundle2-input-part: total payload size 24
706 bundle2-input-part: total payload size 24
706 bundle2-input-part: "check:updated-heads" supported
707 bundle2-input-part: "check:updated-heads" supported
707 bundle2-input-part: total payload size 20
708 bundle2-input-part: total payload size 20
708 invalid branch cache (served): tip differs
709 invalid branch cache (served): tip differs
709 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
710 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
710 adding changesets
711 adding changesets
711 add changeset ef1ea85a6374
712 add changeset ef1ea85a6374
712 add changeset f9cafe1212c8
713 add changeset f9cafe1212c8
713 add changeset 911600dab2ae
714 add changeset 911600dab2ae
714 adding manifests
715 adding manifests
715 adding file changes
716 adding file changes
716 adding foo/Bar/file.txt revisions
717 adding foo/Bar/file.txt revisions
717 adding foo/file.txt revisions
718 adding foo/file.txt revisions
718 adding quux/file.py revisions
719 adding quux/file.py revisions
719 calling hook pretxnchangegroup.acl: hgext.acl.hook
720 calling hook pretxnchangegroup.acl: hgext.acl.hook
720 acl: checking access for user "barney"
721 acl: checking access for user "barney"
721 acl: acl.allow.branches not enabled
722 acl: acl.allow.branches not enabled
722 acl: acl.deny.branches not enabled
723 acl: acl.deny.branches not enabled
723 acl: acl.allow enabled, 0 entries for user barney
724 acl: acl.allow enabled, 0 entries for user barney
724 acl: acl.deny enabled, 0 entries for user barney
725 acl: acl.deny enabled, 0 entries for user barney
725 acl: branch access granted: "ef1ea85a6374" on branch "default"
726 acl: branch access granted: "ef1ea85a6374" on branch "default"
726 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
727 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
727 bundle2-input-part: total payload size 1553
728 bundle2-input-part: total payload size 1553
728 bundle2-input-part: total payload size 24
729 bundle2-input-part: total payload size 24
729 bundle2-input-bundle: 5 parts total
730 bundle2-input-bundle: 5 parts total
730 transaction abort!
731 transaction abort!
731 rollback completed
732 rollback completed
732 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
733 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
733 no rollback information available
734 no rollback information available
734 0:6675d58eff77
735 0:6675d58eff77
735
736
736
737
737 fred is not blocked from moving bookmarks
738 fred is not blocked from moving bookmarks
738
739
739 $ hg -R a book -q moving-bookmark -r 1
740 $ hg -R a book -q moving-bookmark -r 1
740 $ hg -R b book -q moving-bookmark -r 0
741 $ hg -R b book -q moving-bookmark -r 0
741 $ cp $config normalconfig
742 $ cp $config normalconfig
742 $ do_push fred -r 1
743 $ do_push fred -r 1
743 Pushing as user fred
744 Pushing as user fred
744 hgrc = """
745 hgrc = """
745 [hooks]
746 [hooks]
746 pretxnchangegroup.acl = python:hgext.acl.hook
747 pretxnchangegroup.acl = python:hgext.acl.hook
747 prepushkey.acl = python:hgext.acl.hook
748 prepushkey.acl = python:hgext.acl.hook
748 [acl]
749 [acl]
749 sources = push
750 sources = push
750 [acl.allow]
751 [acl.allow]
751 foo/** = fred
752 foo/** = fred
752 [acl.deny]
753 [acl.deny]
753 foo/bar/** = fred
754 foo/bar/** = fred
754 foo/Bar/** = fred
755 foo/Bar/** = fred
755 """
756 """
756 pushing to ../b
757 pushing to ../b
757 query 1; heads
758 query 1; heads
758 searching for changes
759 searching for changes
759 all remote heads known locally
760 all remote heads known locally
760 listing keys for "phases"
761 listing keys for "phases"
761 checking for updated bookmarks
762 checking for updated bookmarks
762 listing keys for "bookmarks"
763 listing keys for "bookmarks"
763 invalid branch cache (served): tip differs
764 invalid branch cache (served): tip differs
764 listing keys for "bookmarks"
765 listing keys for "bookmarks"
765 1 changesets found
766 1 changesets found
766 list of changesets:
767 list of changesets:
767 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
768 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
768 bundle2-output-bundle: "HG20", 7 parts total
769 bundle2-output-bundle: "HG20", 7 parts total
769 bundle2-output-part: "replycaps" 207 bytes payload
770 bundle2-output-part: "replycaps" 207 bytes payload
770 bundle2-output-part: "check:bookmarks" 37 bytes payload
771 bundle2-output-part: "check:bookmarks" 37 bytes payload
771 bundle2-output-part: "check:phases" 24 bytes payload
772 bundle2-output-part: "check:phases" 24 bytes payload
772 bundle2-output-part: "check:updated-heads" streamed payload
773 bundle2-output-part: "check:updated-heads" streamed payload
773 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
774 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
774 bundle2-output-part: "phase-heads" 24 bytes payload
775 bundle2-output-part: "phase-heads" 24 bytes payload
775 bundle2-output-part: "bookmarks" 37 bytes payload
776 bundle2-output-part: "bookmarks" 37 bytes payload
776 bundle2-input-bundle: with-transaction
777 bundle2-input-bundle: with-transaction
777 bundle2-input-part: "replycaps" supported
778 bundle2-input-part: "replycaps" supported
778 bundle2-input-part: total payload size 207
779 bundle2-input-part: total payload size 207
779 bundle2-input-part: "check:bookmarks" supported
780 bundle2-input-part: "check:bookmarks" supported
780 bundle2-input-part: total payload size 37
781 bundle2-input-part: total payload size 37
781 bundle2-input-part: "check:phases" supported
782 bundle2-input-part: "check:phases" supported
782 bundle2-input-part: total payload size 24
783 bundle2-input-part: total payload size 24
783 bundle2-input-part: "check:updated-heads" supported
784 bundle2-input-part: "check:updated-heads" supported
784 bundle2-input-part: total payload size 20
785 bundle2-input-part: total payload size 20
785 invalid branch cache (served): tip differs
786 invalid branch cache (served): tip differs
786 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
787 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
787 adding changesets
788 adding changesets
788 add changeset ef1ea85a6374
789 add changeset ef1ea85a6374
789 adding manifests
790 adding manifests
790 adding file changes
791 adding file changes
791 adding foo/file.txt revisions
792 adding foo/file.txt revisions
792 calling hook pretxnchangegroup.acl: hgext.acl.hook
793 calling hook pretxnchangegroup.acl: hgext.acl.hook
793 acl: checking access for user "fred"
794 acl: checking access for user "fred"
794 acl: acl.allow.branches not enabled
795 acl: acl.allow.branches not enabled
795 acl: acl.deny.branches not enabled
796 acl: acl.deny.branches not enabled
796 acl: acl.allow enabled, 1 entries for user fred
797 acl: acl.allow enabled, 1 entries for user fred
797 acl: acl.deny enabled, 2 entries for user fred
798 acl: acl.deny enabled, 2 entries for user fred
798 acl: branch access granted: "ef1ea85a6374" on branch "default"
799 acl: branch access granted: "ef1ea85a6374" on branch "default"
799 acl: path access granted: "ef1ea85a6374"
800 acl: path access granted: "ef1ea85a6374"
800 bundle2-input-part: total payload size 520
801 bundle2-input-part: total payload size 520
801 bundle2-input-part: "phase-heads" supported
802 bundle2-input-part: "phase-heads" supported
802 bundle2-input-part: total payload size 24
803 bundle2-input-part: total payload size 24
803 bundle2-input-part: "bookmarks" supported
804 bundle2-input-part: "bookmarks" supported
804 bundle2-input-part: total payload size 37
805 bundle2-input-part: total payload size 37
805 calling hook prepushkey.acl: hgext.acl.hook
806 calling hook prepushkey.acl: hgext.acl.hook
806 acl: checking access for user "fred"
807 acl: checking access for user "fred"
807 acl: acl.allow.bookmarks not enabled
808 acl: acl.allow.bookmarks not enabled
808 acl: acl.deny.bookmarks not enabled
809 acl: acl.deny.bookmarks not enabled
809 acl: bookmark access granted: "ef1ea85a6374b77d6da9dcda9541f498f2d17df7" on bookmark "moving-bookmark"
810 acl: bookmark access granted: "ef1ea85a6374b77d6da9dcda9541f498f2d17df7" on bookmark "moving-bookmark"
810 bundle2-input-bundle: 7 parts total
811 bundle2-input-bundle: 7 parts total
811 truncating cache/rbc-revs-v1 to 8
812 updating the branch cache
812 updating the branch cache
813 invalid branch cache (served.hidden): tip differs
813 invalid branch cache (served.hidden): tip differs
814 added 1 changesets with 1 changes to 1 files
814 added 1 changesets with 1 changes to 1 files
815 bundle2-output-bundle: "HG20", 1 parts total
815 bundle2-output-bundle: "HG20", 1 parts total
816 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
816 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
817 bundle2-input-bundle: no-transaction
817 bundle2-input-bundle: no-transaction
818 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
818 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
819 bundle2-input-bundle: 1 parts total
819 bundle2-input-bundle: 1 parts total
820 updating bookmark moving-bookmark
820 updating bookmark moving-bookmark
821 listing keys for "phases"
821 listing keys for "phases"
822 repository tip rolled back to revision 0 (undo push)
822 repository tip rolled back to revision 0 (undo push)
823 0:6675d58eff77
823 0:6675d58eff77
824
824
825
825
826 fred is not allowed to move bookmarks
826 fred is not allowed to move bookmarks
827
827
828 $ echo '[acl.deny.bookmarks]' >> $config
828 $ echo '[acl.deny.bookmarks]' >> $config
829 $ echo '* = fred' >> $config
829 $ echo '* = fred' >> $config
830 $ do_push fred -r 1
830 $ do_push fred -r 1
831 Pushing as user fred
831 Pushing as user fred
832 hgrc = """
832 hgrc = """
833 [hooks]
833 [hooks]
834 pretxnchangegroup.acl = python:hgext.acl.hook
834 pretxnchangegroup.acl = python:hgext.acl.hook
835 prepushkey.acl = python:hgext.acl.hook
835 prepushkey.acl = python:hgext.acl.hook
836 [acl]
836 [acl]
837 sources = push
837 sources = push
838 [acl.allow]
838 [acl.allow]
839 foo/** = fred
839 foo/** = fred
840 [acl.deny]
840 [acl.deny]
841 foo/bar/** = fred
841 foo/bar/** = fred
842 foo/Bar/** = fred
842 foo/Bar/** = fred
843 [acl.deny.bookmarks]
843 [acl.deny.bookmarks]
844 * = fred
844 * = fred
845 """
845 """
846 pushing to ../b
846 pushing to ../b
847 query 1; heads
847 query 1; heads
848 searching for changes
848 searching for changes
849 all remote heads known locally
849 all remote heads known locally
850 listing keys for "phases"
850 listing keys for "phases"
851 checking for updated bookmarks
851 checking for updated bookmarks
852 listing keys for "bookmarks"
852 listing keys for "bookmarks"
853 invalid branch cache (served): tip differs
853 invalid branch cache (served): tip differs
854 listing keys for "bookmarks"
854 listing keys for "bookmarks"
855 1 changesets found
855 1 changesets found
856 list of changesets:
856 list of changesets:
857 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
857 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
858 bundle2-output-bundle: "HG20", 7 parts total
858 bundle2-output-bundle: "HG20", 7 parts total
859 bundle2-output-part: "replycaps" 207 bytes payload
859 bundle2-output-part: "replycaps" 207 bytes payload
860 bundle2-output-part: "check:bookmarks" 37 bytes payload
860 bundle2-output-part: "check:bookmarks" 37 bytes payload
861 bundle2-output-part: "check:phases" 24 bytes payload
861 bundle2-output-part: "check:phases" 24 bytes payload
862 bundle2-output-part: "check:updated-heads" streamed payload
862 bundle2-output-part: "check:updated-heads" streamed payload
863 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
863 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
864 bundle2-output-part: "phase-heads" 24 bytes payload
864 bundle2-output-part: "phase-heads" 24 bytes payload
865 bundle2-output-part: "bookmarks" 37 bytes payload
865 bundle2-output-part: "bookmarks" 37 bytes payload
866 bundle2-input-bundle: with-transaction
866 bundle2-input-bundle: with-transaction
867 bundle2-input-part: "replycaps" supported
867 bundle2-input-part: "replycaps" supported
868 bundle2-input-part: total payload size 207
868 bundle2-input-part: total payload size 207
869 bundle2-input-part: "check:bookmarks" supported
869 bundle2-input-part: "check:bookmarks" supported
870 bundle2-input-part: total payload size 37
870 bundle2-input-part: total payload size 37
871 bundle2-input-part: "check:phases" supported
871 bundle2-input-part: "check:phases" supported
872 bundle2-input-part: total payload size 24
872 bundle2-input-part: total payload size 24
873 bundle2-input-part: "check:updated-heads" supported
873 bundle2-input-part: "check:updated-heads" supported
874 bundle2-input-part: total payload size 20
874 bundle2-input-part: total payload size 20
875 invalid branch cache (served): tip differs
875 invalid branch cache (served): tip differs
876 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
876 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
877 adding changesets
877 adding changesets
878 add changeset ef1ea85a6374
878 add changeset ef1ea85a6374
879 adding manifests
879 adding manifests
880 adding file changes
880 adding file changes
881 adding foo/file.txt revisions
881 adding foo/file.txt revisions
882 calling hook pretxnchangegroup.acl: hgext.acl.hook
882 calling hook pretxnchangegroup.acl: hgext.acl.hook
883 acl: checking access for user "fred"
883 acl: checking access for user "fred"
884 acl: acl.allow.branches not enabled
884 acl: acl.allow.branches not enabled
885 acl: acl.deny.branches not enabled
885 acl: acl.deny.branches not enabled
886 acl: acl.allow enabled, 1 entries for user fred
886 acl: acl.allow enabled, 1 entries for user fred
887 acl: acl.deny enabled, 2 entries for user fred
887 acl: acl.deny enabled, 2 entries for user fred
888 acl: branch access granted: "ef1ea85a6374" on branch "default"
888 acl: branch access granted: "ef1ea85a6374" on branch "default"
889 acl: path access granted: "ef1ea85a6374"
889 acl: path access granted: "ef1ea85a6374"
890 bundle2-input-part: total payload size 520
890 bundle2-input-part: total payload size 520
891 bundle2-input-part: "phase-heads" supported
891 bundle2-input-part: "phase-heads" supported
892 bundle2-input-part: total payload size 24
892 bundle2-input-part: total payload size 24
893 bundle2-input-part: "bookmarks" supported
893 bundle2-input-part: "bookmarks" supported
894 bundle2-input-part: total payload size 37
894 bundle2-input-part: total payload size 37
895 calling hook prepushkey.acl: hgext.acl.hook
895 calling hook prepushkey.acl: hgext.acl.hook
896 acl: checking access for user "fred"
896 acl: checking access for user "fred"
897 acl: acl.allow.bookmarks not enabled
897 acl: acl.allow.bookmarks not enabled
898 acl: acl.deny.bookmarks enabled, 1 entries for user fred
898 acl: acl.deny.bookmarks enabled, 1 entries for user fred
899 error: prepushkey.acl hook failed: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
899 error: prepushkey.acl hook failed: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
900 bundle2-input-bundle: 7 parts total
900 bundle2-input-bundle: 7 parts total
901 transaction abort!
901 transaction abort!
902 rollback completed
902 rollback completed
903 truncating cache/rbc-revs-v1 to 8
903 abort: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
904 abort: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
904 no rollback information available
905 no rollback information available
905 0:6675d58eff77
906 0:6675d58eff77
906
907
907
908
908 cleanup bookmark stuff
909 cleanup bookmark stuff
909
910
910 $ hg book -R a -d moving-bookmark
911 $ hg book -R a -d moving-bookmark
911 $ hg book -R b -d moving-bookmark
912 $ hg book -R b -d moving-bookmark
912 $ cp normalconfig $config
913 $ cp normalconfig $config
913
914
914 barney is allowed everywhere
915 barney is allowed everywhere
915
916
916 $ echo '[acl.allow]' >> $config
917 $ echo '[acl.allow]' >> $config
917 $ echo '** = barney' >> $config
918 $ echo '** = barney' >> $config
918 $ do_push barney
919 $ do_push barney
919 Pushing as user barney
920 Pushing as user barney
920 hgrc = """
921 hgrc = """
921 [hooks]
922 [hooks]
922 pretxnchangegroup.acl = python:hgext.acl.hook
923 pretxnchangegroup.acl = python:hgext.acl.hook
923 prepushkey.acl = python:hgext.acl.hook
924 prepushkey.acl = python:hgext.acl.hook
924 [acl]
925 [acl]
925 sources = push
926 sources = push
926 [acl.allow]
927 [acl.allow]
927 foo/** = fred
928 foo/** = fred
928 [acl.deny]
929 [acl.deny]
929 foo/bar/** = fred
930 foo/bar/** = fred
930 foo/Bar/** = fred
931 foo/Bar/** = fred
931 [acl.allow]
932 [acl.allow]
932 ** = barney
933 ** = barney
933 """
934 """
934 pushing to ../b
935 pushing to ../b
935 query 1; heads
936 query 1; heads
936 searching for changes
937 searching for changes
937 all remote heads known locally
938 all remote heads known locally
938 listing keys for "phases"
939 listing keys for "phases"
939 checking for updated bookmarks
940 checking for updated bookmarks
940 listing keys for "bookmarks"
941 listing keys for "bookmarks"
941 invalid branch cache (served): tip differs
942 invalid branch cache (served): tip differs
942 listing keys for "bookmarks"
943 listing keys for "bookmarks"
943 3 changesets found
944 3 changesets found
944 list of changesets:
945 list of changesets:
945 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
946 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
946 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
947 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
947 911600dab2ae7a9baff75958b84fe606851ce955
948 911600dab2ae7a9baff75958b84fe606851ce955
948 bundle2-output-bundle: "HG20", 5 parts total
949 bundle2-output-bundle: "HG20", 5 parts total
949 bundle2-output-part: "replycaps" 207 bytes payload
950 bundle2-output-part: "replycaps" 207 bytes payload
950 bundle2-output-part: "check:phases" 24 bytes payload
951 bundle2-output-part: "check:phases" 24 bytes payload
951 bundle2-output-part: "check:updated-heads" streamed payload
952 bundle2-output-part: "check:updated-heads" streamed payload
952 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
953 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
953 bundle2-output-part: "phase-heads" 24 bytes payload
954 bundle2-output-part: "phase-heads" 24 bytes payload
954 bundle2-input-bundle: with-transaction
955 bundle2-input-bundle: with-transaction
955 bundle2-input-part: "replycaps" supported
956 bundle2-input-part: "replycaps" supported
956 bundle2-input-part: total payload size 207
957 bundle2-input-part: total payload size 207
957 bundle2-input-part: "check:phases" supported
958 bundle2-input-part: "check:phases" supported
958 bundle2-input-part: total payload size 24
959 bundle2-input-part: total payload size 24
959 bundle2-input-part: "check:updated-heads" supported
960 bundle2-input-part: "check:updated-heads" supported
960 bundle2-input-part: total payload size 20
961 bundle2-input-part: total payload size 20
961 invalid branch cache (served): tip differs
962 invalid branch cache (served): tip differs
962 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
963 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
963 adding changesets
964 adding changesets
964 add changeset ef1ea85a6374
965 add changeset ef1ea85a6374
965 add changeset f9cafe1212c8
966 add changeset f9cafe1212c8
966 add changeset 911600dab2ae
967 add changeset 911600dab2ae
967 adding manifests
968 adding manifests
968 adding file changes
969 adding file changes
969 adding foo/Bar/file.txt revisions
970 adding foo/Bar/file.txt revisions
970 adding foo/file.txt revisions
971 adding foo/file.txt revisions
971 adding quux/file.py revisions
972 adding quux/file.py revisions
972 calling hook pretxnchangegroup.acl: hgext.acl.hook
973 calling hook pretxnchangegroup.acl: hgext.acl.hook
973 acl: checking access for user "barney"
974 acl: checking access for user "barney"
974 acl: acl.allow.branches not enabled
975 acl: acl.allow.branches not enabled
975 acl: acl.deny.branches not enabled
976 acl: acl.deny.branches not enabled
976 acl: acl.allow enabled, 1 entries for user barney
977 acl: acl.allow enabled, 1 entries for user barney
977 acl: acl.deny enabled, 0 entries for user barney
978 acl: acl.deny enabled, 0 entries for user barney
978 acl: branch access granted: "ef1ea85a6374" on branch "default"
979 acl: branch access granted: "ef1ea85a6374" on branch "default"
979 acl: path access granted: "ef1ea85a6374"
980 acl: path access granted: "ef1ea85a6374"
980 acl: branch access granted: "f9cafe1212c8" on branch "default"
981 acl: branch access granted: "f9cafe1212c8" on branch "default"
981 acl: path access granted: "f9cafe1212c8"
982 acl: path access granted: "f9cafe1212c8"
982 acl: branch access granted: "911600dab2ae" on branch "default"
983 acl: branch access granted: "911600dab2ae" on branch "default"
983 acl: path access granted: "911600dab2ae"
984 acl: path access granted: "911600dab2ae"
984 bundle2-input-part: total payload size 1553
985 bundle2-input-part: total payload size 1553
985 bundle2-input-part: "phase-heads" supported
986 bundle2-input-part: "phase-heads" supported
986 bundle2-input-part: total payload size 24
987 bundle2-input-part: total payload size 24
987 bundle2-input-bundle: 5 parts total
988 bundle2-input-bundle: 5 parts total
988 truncating cache/rbc-revs-v1 to 8
989 updating the branch cache
989 updating the branch cache
990 added 3 changesets with 3 changes to 3 files
990 added 3 changesets with 3 changes to 3 files
991 bundle2-output-bundle: "HG20", 1 parts total
991 bundle2-output-bundle: "HG20", 1 parts total
992 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
992 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
993 bundle2-input-bundle: no-transaction
993 bundle2-input-bundle: no-transaction
994 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
994 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
995 bundle2-input-bundle: 1 parts total
995 bundle2-input-bundle: 1 parts total
996 listing keys for "phases"
996 listing keys for "phases"
997 repository tip rolled back to revision 0 (undo push)
997 repository tip rolled back to revision 0 (undo push)
998 0:6675d58eff77
998 0:6675d58eff77
999
999
1000
1000
1001 wilma can change files with a .txt extension
1001 wilma can change files with a .txt extension
1002
1002
1003 $ echo '**/*.txt = wilma' >> $config
1003 $ echo '**/*.txt = wilma' >> $config
1004 $ do_push wilma
1004 $ do_push wilma
1005 Pushing as user wilma
1005 Pushing as user wilma
1006 hgrc = """
1006 hgrc = """
1007 [hooks]
1007 [hooks]
1008 pretxnchangegroup.acl = python:hgext.acl.hook
1008 pretxnchangegroup.acl = python:hgext.acl.hook
1009 prepushkey.acl = python:hgext.acl.hook
1009 prepushkey.acl = python:hgext.acl.hook
1010 [acl]
1010 [acl]
1011 sources = push
1011 sources = push
1012 [acl.allow]
1012 [acl.allow]
1013 foo/** = fred
1013 foo/** = fred
1014 [acl.deny]
1014 [acl.deny]
1015 foo/bar/** = fred
1015 foo/bar/** = fred
1016 foo/Bar/** = fred
1016 foo/Bar/** = fred
1017 [acl.allow]
1017 [acl.allow]
1018 ** = barney
1018 ** = barney
1019 **/*.txt = wilma
1019 **/*.txt = wilma
1020 """
1020 """
1021 pushing to ../b
1021 pushing to ../b
1022 query 1; heads
1022 query 1; heads
1023 searching for changes
1023 searching for changes
1024 all remote heads known locally
1024 all remote heads known locally
1025 listing keys for "phases"
1025 listing keys for "phases"
1026 checking for updated bookmarks
1026 checking for updated bookmarks
1027 listing keys for "bookmarks"
1027 listing keys for "bookmarks"
1028 invalid branch cache (served): tip differs
1028 invalid branch cache (served): tip differs
1029 listing keys for "bookmarks"
1029 listing keys for "bookmarks"
1030 3 changesets found
1030 3 changesets found
1031 list of changesets:
1031 list of changesets:
1032 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1032 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1033 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1033 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1034 911600dab2ae7a9baff75958b84fe606851ce955
1034 911600dab2ae7a9baff75958b84fe606851ce955
1035 bundle2-output-bundle: "HG20", 5 parts total
1035 bundle2-output-bundle: "HG20", 5 parts total
1036 bundle2-output-part: "replycaps" 207 bytes payload
1036 bundle2-output-part: "replycaps" 207 bytes payload
1037 bundle2-output-part: "check:phases" 24 bytes payload
1037 bundle2-output-part: "check:phases" 24 bytes payload
1038 bundle2-output-part: "check:updated-heads" streamed payload
1038 bundle2-output-part: "check:updated-heads" streamed payload
1039 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1039 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1040 bundle2-output-part: "phase-heads" 24 bytes payload
1040 bundle2-output-part: "phase-heads" 24 bytes payload
1041 bundle2-input-bundle: with-transaction
1041 bundle2-input-bundle: with-transaction
1042 bundle2-input-part: "replycaps" supported
1042 bundle2-input-part: "replycaps" supported
1043 bundle2-input-part: total payload size 207
1043 bundle2-input-part: total payload size 207
1044 bundle2-input-part: "check:phases" supported
1044 bundle2-input-part: "check:phases" supported
1045 bundle2-input-part: total payload size 24
1045 bundle2-input-part: total payload size 24
1046 bundle2-input-part: "check:updated-heads" supported
1046 bundle2-input-part: "check:updated-heads" supported
1047 bundle2-input-part: total payload size 20
1047 bundle2-input-part: total payload size 20
1048 invalid branch cache (served): tip differs
1048 invalid branch cache (served): tip differs
1049 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1049 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1050 adding changesets
1050 adding changesets
1051 add changeset ef1ea85a6374
1051 add changeset ef1ea85a6374
1052 add changeset f9cafe1212c8
1052 add changeset f9cafe1212c8
1053 add changeset 911600dab2ae
1053 add changeset 911600dab2ae
1054 adding manifests
1054 adding manifests
1055 adding file changes
1055 adding file changes
1056 adding foo/Bar/file.txt revisions
1056 adding foo/Bar/file.txt revisions
1057 adding foo/file.txt revisions
1057 adding foo/file.txt revisions
1058 adding quux/file.py revisions
1058 adding quux/file.py revisions
1059 calling hook pretxnchangegroup.acl: hgext.acl.hook
1059 calling hook pretxnchangegroup.acl: hgext.acl.hook
1060 acl: checking access for user "wilma"
1060 acl: checking access for user "wilma"
1061 acl: acl.allow.branches not enabled
1061 acl: acl.allow.branches not enabled
1062 acl: acl.deny.branches not enabled
1062 acl: acl.deny.branches not enabled
1063 acl: acl.allow enabled, 1 entries for user wilma
1063 acl: acl.allow enabled, 1 entries for user wilma
1064 acl: acl.deny enabled, 0 entries for user wilma
1064 acl: acl.deny enabled, 0 entries for user wilma
1065 acl: branch access granted: "ef1ea85a6374" on branch "default"
1065 acl: branch access granted: "ef1ea85a6374" on branch "default"
1066 acl: path access granted: "ef1ea85a6374"
1066 acl: path access granted: "ef1ea85a6374"
1067 acl: branch access granted: "f9cafe1212c8" on branch "default"
1067 acl: branch access granted: "f9cafe1212c8" on branch "default"
1068 acl: path access granted: "f9cafe1212c8"
1068 acl: path access granted: "f9cafe1212c8"
1069 acl: branch access granted: "911600dab2ae" on branch "default"
1069 acl: branch access granted: "911600dab2ae" on branch "default"
1070 error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
1070 error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
1071 bundle2-input-part: total payload size 1553
1071 bundle2-input-part: total payload size 1553
1072 bundle2-input-part: total payload size 24
1072 bundle2-input-part: total payload size 24
1073 bundle2-input-bundle: 5 parts total
1073 bundle2-input-bundle: 5 parts total
1074 transaction abort!
1074 transaction abort!
1075 rollback completed
1075 rollback completed
1076 truncating cache/rbc-revs-v1 to 8
1076 abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
1077 abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
1077 no rollback information available
1078 no rollback information available
1078 0:6675d58eff77
1079 0:6675d58eff77
1079
1080
1080
1081
1081 file specified by acl.config does not exist
1082 file specified by acl.config does not exist
1082
1083
1083 $ echo '[acl]' >> $config
1084 $ echo '[acl]' >> $config
1084 $ echo 'config = ../acl.config' >> $config
1085 $ echo 'config = ../acl.config' >> $config
1085 $ do_push barney
1086 $ do_push barney
1086 Pushing as user barney
1087 Pushing as user barney
1087 hgrc = """
1088 hgrc = """
1088 [hooks]
1089 [hooks]
1089 pretxnchangegroup.acl = python:hgext.acl.hook
1090 pretxnchangegroup.acl = python:hgext.acl.hook
1090 prepushkey.acl = python:hgext.acl.hook
1091 prepushkey.acl = python:hgext.acl.hook
1091 [acl]
1092 [acl]
1092 sources = push
1093 sources = push
1093 [acl.allow]
1094 [acl.allow]
1094 foo/** = fred
1095 foo/** = fred
1095 [acl.deny]
1096 [acl.deny]
1096 foo/bar/** = fred
1097 foo/bar/** = fred
1097 foo/Bar/** = fred
1098 foo/Bar/** = fred
1098 [acl.allow]
1099 [acl.allow]
1099 ** = barney
1100 ** = barney
1100 **/*.txt = wilma
1101 **/*.txt = wilma
1101 [acl]
1102 [acl]
1102 config = ../acl.config
1103 config = ../acl.config
1103 """
1104 """
1104 pushing to ../b
1105 pushing to ../b
1105 query 1; heads
1106 query 1; heads
1106 searching for changes
1107 searching for changes
1107 all remote heads known locally
1108 all remote heads known locally
1108 listing keys for "phases"
1109 listing keys for "phases"
1109 checking for updated bookmarks
1110 checking for updated bookmarks
1110 listing keys for "bookmarks"
1111 listing keys for "bookmarks"
1111 invalid branch cache (served): tip differs
1112 invalid branch cache (served): tip differs
1112 listing keys for "bookmarks"
1113 listing keys for "bookmarks"
1113 3 changesets found
1114 3 changesets found
1114 list of changesets:
1115 list of changesets:
1115 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1116 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1116 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1117 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1117 911600dab2ae7a9baff75958b84fe606851ce955
1118 911600dab2ae7a9baff75958b84fe606851ce955
1118 bundle2-output-bundle: "HG20", 5 parts total
1119 bundle2-output-bundle: "HG20", 5 parts total
1119 bundle2-output-part: "replycaps" 207 bytes payload
1120 bundle2-output-part: "replycaps" 207 bytes payload
1120 bundle2-output-part: "check:phases" 24 bytes payload
1121 bundle2-output-part: "check:phases" 24 bytes payload
1121 bundle2-output-part: "check:updated-heads" streamed payload
1122 bundle2-output-part: "check:updated-heads" streamed payload
1122 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1123 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1123 bundle2-output-part: "phase-heads" 24 bytes payload
1124 bundle2-output-part: "phase-heads" 24 bytes payload
1124 bundle2-input-bundle: with-transaction
1125 bundle2-input-bundle: with-transaction
1125 bundle2-input-part: "replycaps" supported
1126 bundle2-input-part: "replycaps" supported
1126 bundle2-input-part: total payload size 207
1127 bundle2-input-part: total payload size 207
1127 bundle2-input-part: "check:phases" supported
1128 bundle2-input-part: "check:phases" supported
1128 bundle2-input-part: total payload size 24
1129 bundle2-input-part: total payload size 24
1129 bundle2-input-part: "check:updated-heads" supported
1130 bundle2-input-part: "check:updated-heads" supported
1130 bundle2-input-part: total payload size 20
1131 bundle2-input-part: total payload size 20
1131 invalid branch cache (served): tip differs
1132 invalid branch cache (served): tip differs
1132 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1133 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1133 adding changesets
1134 adding changesets
1134 add changeset ef1ea85a6374
1135 add changeset ef1ea85a6374
1135 add changeset f9cafe1212c8
1136 add changeset f9cafe1212c8
1136 add changeset 911600dab2ae
1137 add changeset 911600dab2ae
1137 adding manifests
1138 adding manifests
1138 adding file changes
1139 adding file changes
1139 adding foo/Bar/file.txt revisions
1140 adding foo/Bar/file.txt revisions
1140 adding foo/file.txt revisions
1141 adding foo/file.txt revisions
1141 adding quux/file.py revisions
1142 adding quux/file.py revisions
1142 calling hook pretxnchangegroup.acl: hgext.acl.hook
1143 calling hook pretxnchangegroup.acl: hgext.acl.hook
1143 acl: checking access for user "barney"
1144 acl: checking access for user "barney"
1144 error: pretxnchangegroup.acl hook raised an exception: [Errno *] * (glob)
1145 error: pretxnchangegroup.acl hook raised an exception: [Errno *] * (glob)
1145 bundle2-input-part: total payload size 1553
1146 bundle2-input-part: total payload size 1553
1146 bundle2-input-part: total payload size 24
1147 bundle2-input-part: total payload size 24
1147 bundle2-input-bundle: 5 parts total
1148 bundle2-input-bundle: 5 parts total
1148 transaction abort!
1149 transaction abort!
1149 rollback completed
1150 rollback completed
1150 abort: $ENOENT$: '../acl.config'
1151 abort: $ENOENT$: '../acl.config'
1151 no rollback information available
1152 no rollback information available
1152 0:6675d58eff77
1153 0:6675d58eff77
1153
1154
1154
1155
1155 betty is allowed inside foo/ by a acl.config file
1156 betty is allowed inside foo/ by a acl.config file
1156
1157
1157 $ echo '[acl.allow]' >> acl.config
1158 $ echo '[acl.allow]' >> acl.config
1158 $ echo 'foo/** = betty' >> acl.config
1159 $ echo 'foo/** = betty' >> acl.config
1159 $ do_push betty
1160 $ do_push betty
1160 Pushing as user betty
1161 Pushing as user betty
1161 hgrc = """
1162 hgrc = """
1162 [hooks]
1163 [hooks]
1163 pretxnchangegroup.acl = python:hgext.acl.hook
1164 pretxnchangegroup.acl = python:hgext.acl.hook
1164 prepushkey.acl = python:hgext.acl.hook
1165 prepushkey.acl = python:hgext.acl.hook
1165 [acl]
1166 [acl]
1166 sources = push
1167 sources = push
1167 [acl.allow]
1168 [acl.allow]
1168 foo/** = fred
1169 foo/** = fred
1169 [acl.deny]
1170 [acl.deny]
1170 foo/bar/** = fred
1171 foo/bar/** = fred
1171 foo/Bar/** = fred
1172 foo/Bar/** = fred
1172 [acl.allow]
1173 [acl.allow]
1173 ** = barney
1174 ** = barney
1174 **/*.txt = wilma
1175 **/*.txt = wilma
1175 [acl]
1176 [acl]
1176 config = ../acl.config
1177 config = ../acl.config
1177 """
1178 """
1178 acl.config = """
1179 acl.config = """
1179 [acl.allow]
1180 [acl.allow]
1180 foo/** = betty
1181 foo/** = betty
1181 """
1182 """
1182 pushing to ../b
1183 pushing to ../b
1183 query 1; heads
1184 query 1; heads
1184 searching for changes
1185 searching for changes
1185 all remote heads known locally
1186 all remote heads known locally
1186 listing keys for "phases"
1187 listing keys for "phases"
1187 checking for updated bookmarks
1188 checking for updated bookmarks
1188 listing keys for "bookmarks"
1189 listing keys for "bookmarks"
1189 invalid branch cache (served): tip differs
1190 invalid branch cache (served): tip differs
1190 listing keys for "bookmarks"
1191 listing keys for "bookmarks"
1191 3 changesets found
1192 3 changesets found
1192 list of changesets:
1193 list of changesets:
1193 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1194 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1194 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1195 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1195 911600dab2ae7a9baff75958b84fe606851ce955
1196 911600dab2ae7a9baff75958b84fe606851ce955
1196 bundle2-output-bundle: "HG20", 5 parts total
1197 bundle2-output-bundle: "HG20", 5 parts total
1197 bundle2-output-part: "replycaps" 207 bytes payload
1198 bundle2-output-part: "replycaps" 207 bytes payload
1198 bundle2-output-part: "check:phases" 24 bytes payload
1199 bundle2-output-part: "check:phases" 24 bytes payload
1199 bundle2-output-part: "check:updated-heads" streamed payload
1200 bundle2-output-part: "check:updated-heads" streamed payload
1200 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1201 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1201 bundle2-output-part: "phase-heads" 24 bytes payload
1202 bundle2-output-part: "phase-heads" 24 bytes payload
1202 bundle2-input-bundle: with-transaction
1203 bundle2-input-bundle: with-transaction
1203 bundle2-input-part: "replycaps" supported
1204 bundle2-input-part: "replycaps" supported
1204 bundle2-input-part: total payload size 207
1205 bundle2-input-part: total payload size 207
1205 bundle2-input-part: "check:phases" supported
1206 bundle2-input-part: "check:phases" supported
1206 bundle2-input-part: total payload size 24
1207 bundle2-input-part: total payload size 24
1207 bundle2-input-part: "check:updated-heads" supported
1208 bundle2-input-part: "check:updated-heads" supported
1208 bundle2-input-part: total payload size 20
1209 bundle2-input-part: total payload size 20
1209 invalid branch cache (served): tip differs
1210 invalid branch cache (served): tip differs
1210 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1211 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1211 adding changesets
1212 adding changesets
1212 add changeset ef1ea85a6374
1213 add changeset ef1ea85a6374
1213 add changeset f9cafe1212c8
1214 add changeset f9cafe1212c8
1214 add changeset 911600dab2ae
1215 add changeset 911600dab2ae
1215 adding manifests
1216 adding manifests
1216 adding file changes
1217 adding file changes
1217 adding foo/Bar/file.txt revisions
1218 adding foo/Bar/file.txt revisions
1218 adding foo/file.txt revisions
1219 adding foo/file.txt revisions
1219 adding quux/file.py revisions
1220 adding quux/file.py revisions
1220 calling hook pretxnchangegroup.acl: hgext.acl.hook
1221 calling hook pretxnchangegroup.acl: hgext.acl.hook
1221 acl: checking access for user "betty"
1222 acl: checking access for user "betty"
1222 acl: acl.allow.branches not enabled
1223 acl: acl.allow.branches not enabled
1223 acl: acl.deny.branches not enabled
1224 acl: acl.deny.branches not enabled
1224 acl: acl.allow enabled, 1 entries for user betty
1225 acl: acl.allow enabled, 1 entries for user betty
1225 acl: acl.deny enabled, 0 entries for user betty
1226 acl: acl.deny enabled, 0 entries for user betty
1226 acl: branch access granted: "ef1ea85a6374" on branch "default"
1227 acl: branch access granted: "ef1ea85a6374" on branch "default"
1227 acl: path access granted: "ef1ea85a6374"
1228 acl: path access granted: "ef1ea85a6374"
1228 acl: branch access granted: "f9cafe1212c8" on branch "default"
1229 acl: branch access granted: "f9cafe1212c8" on branch "default"
1229 acl: path access granted: "f9cafe1212c8"
1230 acl: path access granted: "f9cafe1212c8"
1230 acl: branch access granted: "911600dab2ae" on branch "default"
1231 acl: branch access granted: "911600dab2ae" on branch "default"
1231 error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
1232 error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
1232 bundle2-input-part: total payload size 1553
1233 bundle2-input-part: total payload size 1553
1233 bundle2-input-part: total payload size 24
1234 bundle2-input-part: total payload size 24
1234 bundle2-input-bundle: 5 parts total
1235 bundle2-input-bundle: 5 parts total
1235 transaction abort!
1236 transaction abort!
1236 rollback completed
1237 rollback completed
1237 abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
1238 abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
1238 no rollback information available
1239 no rollback information available
1239 0:6675d58eff77
1240 0:6675d58eff77
1240
1241
1241
1242
1242 acl.config can set only [acl.allow]/[acl.deny]
1243 acl.config can set only [acl.allow]/[acl.deny]
1243
1244
1244 $ echo '[hooks]' >> acl.config
1245 $ echo '[hooks]' >> acl.config
1245 $ echo 'changegroup.acl = false' >> acl.config
1246 $ echo 'changegroup.acl = false' >> acl.config
1246 $ do_push barney
1247 $ do_push barney
1247 Pushing as user barney
1248 Pushing as user barney
1248 hgrc = """
1249 hgrc = """
1249 [hooks]
1250 [hooks]
1250 pretxnchangegroup.acl = python:hgext.acl.hook
1251 pretxnchangegroup.acl = python:hgext.acl.hook
1251 prepushkey.acl = python:hgext.acl.hook
1252 prepushkey.acl = python:hgext.acl.hook
1252 [acl]
1253 [acl]
1253 sources = push
1254 sources = push
1254 [acl.allow]
1255 [acl.allow]
1255 foo/** = fred
1256 foo/** = fred
1256 [acl.deny]
1257 [acl.deny]
1257 foo/bar/** = fred
1258 foo/bar/** = fred
1258 foo/Bar/** = fred
1259 foo/Bar/** = fred
1259 [acl.allow]
1260 [acl.allow]
1260 ** = barney
1261 ** = barney
1261 **/*.txt = wilma
1262 **/*.txt = wilma
1262 [acl]
1263 [acl]
1263 config = ../acl.config
1264 config = ../acl.config
1264 """
1265 """
1265 acl.config = """
1266 acl.config = """
1266 [acl.allow]
1267 [acl.allow]
1267 foo/** = betty
1268 foo/** = betty
1268 [hooks]
1269 [hooks]
1269 changegroup.acl = false
1270 changegroup.acl = false
1270 """
1271 """
1271 pushing to ../b
1272 pushing to ../b
1272 query 1; heads
1273 query 1; heads
1273 searching for changes
1274 searching for changes
1274 all remote heads known locally
1275 all remote heads known locally
1275 listing keys for "phases"
1276 listing keys for "phases"
1276 checking for updated bookmarks
1277 checking for updated bookmarks
1277 listing keys for "bookmarks"
1278 listing keys for "bookmarks"
1278 invalid branch cache (served): tip differs
1279 invalid branch cache (served): tip differs
1279 listing keys for "bookmarks"
1280 listing keys for "bookmarks"
1280 3 changesets found
1281 3 changesets found
1281 list of changesets:
1282 list of changesets:
1282 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1283 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1283 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1284 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1284 911600dab2ae7a9baff75958b84fe606851ce955
1285 911600dab2ae7a9baff75958b84fe606851ce955
1285 bundle2-output-bundle: "HG20", 5 parts total
1286 bundle2-output-bundle: "HG20", 5 parts total
1286 bundle2-output-part: "replycaps" 207 bytes payload
1287 bundle2-output-part: "replycaps" 207 bytes payload
1287 bundle2-output-part: "check:phases" 24 bytes payload
1288 bundle2-output-part: "check:phases" 24 bytes payload
1288 bundle2-output-part: "check:updated-heads" streamed payload
1289 bundle2-output-part: "check:updated-heads" streamed payload
1289 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1290 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1290 bundle2-output-part: "phase-heads" 24 bytes payload
1291 bundle2-output-part: "phase-heads" 24 bytes payload
1291 bundle2-input-bundle: with-transaction
1292 bundle2-input-bundle: with-transaction
1292 bundle2-input-part: "replycaps" supported
1293 bundle2-input-part: "replycaps" supported
1293 bundle2-input-part: total payload size 207
1294 bundle2-input-part: total payload size 207
1294 bundle2-input-part: "check:phases" supported
1295 bundle2-input-part: "check:phases" supported
1295 bundle2-input-part: total payload size 24
1296 bundle2-input-part: total payload size 24
1296 bundle2-input-part: "check:updated-heads" supported
1297 bundle2-input-part: "check:updated-heads" supported
1297 bundle2-input-part: total payload size 20
1298 bundle2-input-part: total payload size 20
1298 invalid branch cache (served): tip differs
1299 invalid branch cache (served): tip differs
1299 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1300 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1300 adding changesets
1301 adding changesets
1301 add changeset ef1ea85a6374
1302 add changeset ef1ea85a6374
1302 add changeset f9cafe1212c8
1303 add changeset f9cafe1212c8
1303 add changeset 911600dab2ae
1304 add changeset 911600dab2ae
1304 adding manifests
1305 adding manifests
1305 adding file changes
1306 adding file changes
1306 adding foo/Bar/file.txt revisions
1307 adding foo/Bar/file.txt revisions
1307 adding foo/file.txt revisions
1308 adding foo/file.txt revisions
1308 adding quux/file.py revisions
1309 adding quux/file.py revisions
1309 calling hook pretxnchangegroup.acl: hgext.acl.hook
1310 calling hook pretxnchangegroup.acl: hgext.acl.hook
1310 acl: checking access for user "barney"
1311 acl: checking access for user "barney"
1311 acl: acl.allow.branches not enabled
1312 acl: acl.allow.branches not enabled
1312 acl: acl.deny.branches not enabled
1313 acl: acl.deny.branches not enabled
1313 acl: acl.allow enabled, 1 entries for user barney
1314 acl: acl.allow enabled, 1 entries for user barney
1314 acl: acl.deny enabled, 0 entries for user barney
1315 acl: acl.deny enabled, 0 entries for user barney
1315 acl: branch access granted: "ef1ea85a6374" on branch "default"
1316 acl: branch access granted: "ef1ea85a6374" on branch "default"
1316 acl: path access granted: "ef1ea85a6374"
1317 acl: path access granted: "ef1ea85a6374"
1317 acl: branch access granted: "f9cafe1212c8" on branch "default"
1318 acl: branch access granted: "f9cafe1212c8" on branch "default"
1318 acl: path access granted: "f9cafe1212c8"
1319 acl: path access granted: "f9cafe1212c8"
1319 acl: branch access granted: "911600dab2ae" on branch "default"
1320 acl: branch access granted: "911600dab2ae" on branch "default"
1320 acl: path access granted: "911600dab2ae"
1321 acl: path access granted: "911600dab2ae"
1321 bundle2-input-part: total payload size 1553
1322 bundle2-input-part: total payload size 1553
1322 bundle2-input-part: "phase-heads" supported
1323 bundle2-input-part: "phase-heads" supported
1323 bundle2-input-part: total payload size 24
1324 bundle2-input-part: total payload size 24
1324 bundle2-input-bundle: 5 parts total
1325 bundle2-input-bundle: 5 parts total
1325 truncating cache/rbc-revs-v1 to 8
1326 updating the branch cache
1326 updating the branch cache
1327 added 3 changesets with 3 changes to 3 files
1327 added 3 changesets with 3 changes to 3 files
1328 bundle2-output-bundle: "HG20", 1 parts total
1328 bundle2-output-bundle: "HG20", 1 parts total
1329 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1329 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1330 bundle2-input-bundle: no-transaction
1330 bundle2-input-bundle: no-transaction
1331 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1331 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1332 bundle2-input-bundle: 1 parts total
1332 bundle2-input-bundle: 1 parts total
1333 listing keys for "phases"
1333 listing keys for "phases"
1334 repository tip rolled back to revision 0 (undo push)
1334 repository tip rolled back to revision 0 (undo push)
1335 0:6675d58eff77
1335 0:6675d58eff77
1336
1336
1337
1337
1338 asterisk
1338 asterisk
1339
1339
1340 $ init_config
1340 $ init_config
1341
1341
1342 asterisk test
1342 asterisk test
1343
1343
1344 $ echo '[acl.allow]' >> $config
1344 $ echo '[acl.allow]' >> $config
1345 $ echo "** = fred" >> $config
1345 $ echo "** = fred" >> $config
1346
1346
1347 fred is always allowed
1347 fred is always allowed
1348
1348
1349 $ do_push fred
1349 $ do_push fred
1350 Pushing as user fred
1350 Pushing as user fred
1351 hgrc = """
1351 hgrc = """
1352 [hooks]
1352 [hooks]
1353 pretxnchangegroup.acl = python:hgext.acl.hook
1353 pretxnchangegroup.acl = python:hgext.acl.hook
1354 prepushkey.acl = python:hgext.acl.hook
1354 prepushkey.acl = python:hgext.acl.hook
1355 [acl]
1355 [acl]
1356 sources = push
1356 sources = push
1357 [extensions]
1357 [extensions]
1358 posixgetuser=$TESTTMP/posixgetuser.py
1358 posixgetuser=$TESTTMP/posixgetuser.py
1359 [acl.allow]
1359 [acl.allow]
1360 ** = fred
1360 ** = fred
1361 """
1361 """
1362 pushing to ../b
1362 pushing to ../b
1363 query 1; heads
1363 query 1; heads
1364 searching for changes
1364 searching for changes
1365 all remote heads known locally
1365 all remote heads known locally
1366 listing keys for "phases"
1366 listing keys for "phases"
1367 checking for updated bookmarks
1367 checking for updated bookmarks
1368 listing keys for "bookmarks"
1368 listing keys for "bookmarks"
1369 invalid branch cache (served): tip differs
1369 invalid branch cache (served): tip differs
1370 listing keys for "bookmarks"
1370 listing keys for "bookmarks"
1371 3 changesets found
1371 3 changesets found
1372 list of changesets:
1372 list of changesets:
1373 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1373 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1374 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1374 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1375 911600dab2ae7a9baff75958b84fe606851ce955
1375 911600dab2ae7a9baff75958b84fe606851ce955
1376 bundle2-output-bundle: "HG20", 5 parts total
1376 bundle2-output-bundle: "HG20", 5 parts total
1377 bundle2-output-part: "replycaps" 207 bytes payload
1377 bundle2-output-part: "replycaps" 207 bytes payload
1378 bundle2-output-part: "check:phases" 24 bytes payload
1378 bundle2-output-part: "check:phases" 24 bytes payload
1379 bundle2-output-part: "check:updated-heads" streamed payload
1379 bundle2-output-part: "check:updated-heads" streamed payload
1380 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1380 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1381 bundle2-output-part: "phase-heads" 24 bytes payload
1381 bundle2-output-part: "phase-heads" 24 bytes payload
1382 bundle2-input-bundle: with-transaction
1382 bundle2-input-bundle: with-transaction
1383 bundle2-input-part: "replycaps" supported
1383 bundle2-input-part: "replycaps" supported
1384 bundle2-input-part: total payload size 207
1384 bundle2-input-part: total payload size 207
1385 bundle2-input-part: "check:phases" supported
1385 bundle2-input-part: "check:phases" supported
1386 bundle2-input-part: total payload size 24
1386 bundle2-input-part: total payload size 24
1387 bundle2-input-part: "check:updated-heads" supported
1387 bundle2-input-part: "check:updated-heads" supported
1388 bundle2-input-part: total payload size 20
1388 bundle2-input-part: total payload size 20
1389 invalid branch cache (served): tip differs
1389 invalid branch cache (served): tip differs
1390 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1390 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1391 adding changesets
1391 adding changesets
1392 add changeset ef1ea85a6374
1392 add changeset ef1ea85a6374
1393 add changeset f9cafe1212c8
1393 add changeset f9cafe1212c8
1394 add changeset 911600dab2ae
1394 add changeset 911600dab2ae
1395 adding manifests
1395 adding manifests
1396 adding file changes
1396 adding file changes
1397 adding foo/Bar/file.txt revisions
1397 adding foo/Bar/file.txt revisions
1398 adding foo/file.txt revisions
1398 adding foo/file.txt revisions
1399 adding quux/file.py revisions
1399 adding quux/file.py revisions
1400 calling hook pretxnchangegroup.acl: hgext.acl.hook
1400 calling hook pretxnchangegroup.acl: hgext.acl.hook
1401 acl: checking access for user "fred"
1401 acl: checking access for user "fred"
1402 acl: acl.allow.branches not enabled
1402 acl: acl.allow.branches not enabled
1403 acl: acl.deny.branches not enabled
1403 acl: acl.deny.branches not enabled
1404 acl: acl.allow enabled, 1 entries for user fred
1404 acl: acl.allow enabled, 1 entries for user fred
1405 acl: acl.deny not enabled
1405 acl: acl.deny not enabled
1406 acl: branch access granted: "ef1ea85a6374" on branch "default"
1406 acl: branch access granted: "ef1ea85a6374" on branch "default"
1407 acl: path access granted: "ef1ea85a6374"
1407 acl: path access granted: "ef1ea85a6374"
1408 acl: branch access granted: "f9cafe1212c8" on branch "default"
1408 acl: branch access granted: "f9cafe1212c8" on branch "default"
1409 acl: path access granted: "f9cafe1212c8"
1409 acl: path access granted: "f9cafe1212c8"
1410 acl: branch access granted: "911600dab2ae" on branch "default"
1410 acl: branch access granted: "911600dab2ae" on branch "default"
1411 acl: path access granted: "911600dab2ae"
1411 acl: path access granted: "911600dab2ae"
1412 bundle2-input-part: total payload size 1553
1412 bundle2-input-part: total payload size 1553
1413 bundle2-input-part: "phase-heads" supported
1413 bundle2-input-part: "phase-heads" supported
1414 bundle2-input-part: total payload size 24
1414 bundle2-input-part: total payload size 24
1415 bundle2-input-bundle: 5 parts total
1415 bundle2-input-bundle: 5 parts total
1416 truncating cache/rbc-revs-v1 to 8
1416 truncating cache/rbc-revs-v1 to 8
1417 updating the branch cache
1417 updating the branch cache
1418 added 3 changesets with 3 changes to 3 files
1418 added 3 changesets with 3 changes to 3 files
1419 bundle2-output-bundle: "HG20", 1 parts total
1419 bundle2-output-bundle: "HG20", 1 parts total
1420 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1420 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1421 bundle2-input-bundle: no-transaction
1421 bundle2-input-bundle: no-transaction
1422 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1422 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1423 bundle2-input-bundle: 1 parts total
1423 bundle2-input-bundle: 1 parts total
1424 listing keys for "phases"
1424 listing keys for "phases"
1425 repository tip rolled back to revision 0 (undo push)
1425 repository tip rolled back to revision 0 (undo push)
1426 0:6675d58eff77
1426 0:6675d58eff77
1427
1427
1428
1428
1429 $ echo '[acl.deny]' >> $config
1429 $ echo '[acl.deny]' >> $config
1430 $ echo "foo/Bar/** = *" >> $config
1430 $ echo "foo/Bar/** = *" >> $config
1431
1431
1432 no one is allowed inside foo/Bar/
1432 no one is allowed inside foo/Bar/
1433
1433
1434 $ do_push fred
1434 $ do_push fred
1435 Pushing as user fred
1435 Pushing as user fred
1436 hgrc = """
1436 hgrc = """
1437 [hooks]
1437 [hooks]
1438 pretxnchangegroup.acl = python:hgext.acl.hook
1438 pretxnchangegroup.acl = python:hgext.acl.hook
1439 prepushkey.acl = python:hgext.acl.hook
1439 prepushkey.acl = python:hgext.acl.hook
1440 [acl]
1440 [acl]
1441 sources = push
1441 sources = push
1442 [extensions]
1442 [extensions]
1443 posixgetuser=$TESTTMP/posixgetuser.py
1443 posixgetuser=$TESTTMP/posixgetuser.py
1444 [acl.allow]
1444 [acl.allow]
1445 ** = fred
1445 ** = fred
1446 [acl.deny]
1446 [acl.deny]
1447 foo/Bar/** = *
1447 foo/Bar/** = *
1448 """
1448 """
1449 pushing to ../b
1449 pushing to ../b
1450 query 1; heads
1450 query 1; heads
1451 searching for changes
1451 searching for changes
1452 all remote heads known locally
1452 all remote heads known locally
1453 listing keys for "phases"
1453 listing keys for "phases"
1454 checking for updated bookmarks
1454 checking for updated bookmarks
1455 listing keys for "bookmarks"
1455 listing keys for "bookmarks"
1456 invalid branch cache (served): tip differs
1456 invalid branch cache (served): tip differs
1457 listing keys for "bookmarks"
1457 listing keys for "bookmarks"
1458 3 changesets found
1458 3 changesets found
1459 list of changesets:
1459 list of changesets:
1460 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1460 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1461 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1461 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1462 911600dab2ae7a9baff75958b84fe606851ce955
1462 911600dab2ae7a9baff75958b84fe606851ce955
1463 bundle2-output-bundle: "HG20", 5 parts total
1463 bundle2-output-bundle: "HG20", 5 parts total
1464 bundle2-output-part: "replycaps" 207 bytes payload
1464 bundle2-output-part: "replycaps" 207 bytes payload
1465 bundle2-output-part: "check:phases" 24 bytes payload
1465 bundle2-output-part: "check:phases" 24 bytes payload
1466 bundle2-output-part: "check:updated-heads" streamed payload
1466 bundle2-output-part: "check:updated-heads" streamed payload
1467 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1467 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1468 bundle2-output-part: "phase-heads" 24 bytes payload
1468 bundle2-output-part: "phase-heads" 24 bytes payload
1469 bundle2-input-bundle: with-transaction
1469 bundle2-input-bundle: with-transaction
1470 bundle2-input-part: "replycaps" supported
1470 bundle2-input-part: "replycaps" supported
1471 bundle2-input-part: total payload size 207
1471 bundle2-input-part: total payload size 207
1472 bundle2-input-part: "check:phases" supported
1472 bundle2-input-part: "check:phases" supported
1473 bundle2-input-part: total payload size 24
1473 bundle2-input-part: total payload size 24
1474 bundle2-input-part: "check:updated-heads" supported
1474 bundle2-input-part: "check:updated-heads" supported
1475 bundle2-input-part: total payload size 20
1475 bundle2-input-part: total payload size 20
1476 invalid branch cache (served): tip differs
1476 invalid branch cache (served): tip differs
1477 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1477 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1478 adding changesets
1478 adding changesets
1479 add changeset ef1ea85a6374
1479 add changeset ef1ea85a6374
1480 add changeset f9cafe1212c8
1480 add changeset f9cafe1212c8
1481 add changeset 911600dab2ae
1481 add changeset 911600dab2ae
1482 adding manifests
1482 adding manifests
1483 adding file changes
1483 adding file changes
1484 adding foo/Bar/file.txt revisions
1484 adding foo/Bar/file.txt revisions
1485 adding foo/file.txt revisions
1485 adding foo/file.txt revisions
1486 adding quux/file.py revisions
1486 adding quux/file.py revisions
1487 calling hook pretxnchangegroup.acl: hgext.acl.hook
1487 calling hook pretxnchangegroup.acl: hgext.acl.hook
1488 acl: checking access for user "fred"
1488 acl: checking access for user "fred"
1489 acl: acl.allow.branches not enabled
1489 acl: acl.allow.branches not enabled
1490 acl: acl.deny.branches not enabled
1490 acl: acl.deny.branches not enabled
1491 acl: acl.allow enabled, 1 entries for user fred
1491 acl: acl.allow enabled, 1 entries for user fred
1492 acl: acl.deny enabled, 1 entries for user fred
1492 acl: acl.deny enabled, 1 entries for user fred
1493 acl: branch access granted: "ef1ea85a6374" on branch "default"
1493 acl: branch access granted: "ef1ea85a6374" on branch "default"
1494 acl: path access granted: "ef1ea85a6374"
1494 acl: path access granted: "ef1ea85a6374"
1495 acl: branch access granted: "f9cafe1212c8" on branch "default"
1495 acl: branch access granted: "f9cafe1212c8" on branch "default"
1496 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1496 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1497 bundle2-input-part: total payload size 1553
1497 bundle2-input-part: total payload size 1553
1498 bundle2-input-part: total payload size 24
1498 bundle2-input-part: total payload size 24
1499 bundle2-input-bundle: 5 parts total
1499 bundle2-input-bundle: 5 parts total
1500 transaction abort!
1500 transaction abort!
1501 rollback completed
1501 rollback completed
1502 truncating cache/rbc-revs-v1 to 8
1502 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1503 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1503 no rollback information available
1504 no rollback information available
1504 0:6675d58eff77
1505 0:6675d58eff77
1505
1506
1506
1507
1507 Groups
1508 Groups
1508
1509
1509 $ init_config
1510 $ init_config
1510
1511
1511 OS-level groups
1512 OS-level groups
1512
1513
1513 $ echo '[acl.allow]' >> $config
1514 $ echo '[acl.allow]' >> $config
1514 $ echo "** = @group1" >> $config
1515 $ echo "** = @group1" >> $config
1515
1516
1516 @group1 is always allowed
1517 @group1 is always allowed
1517
1518
1518 $ do_push fred
1519 $ do_push fred
1519 Pushing as user fred
1520 Pushing as user fred
1520 hgrc = """
1521 hgrc = """
1521 [hooks]
1522 [hooks]
1522 pretxnchangegroup.acl = python:hgext.acl.hook
1523 pretxnchangegroup.acl = python:hgext.acl.hook
1523 prepushkey.acl = python:hgext.acl.hook
1524 prepushkey.acl = python:hgext.acl.hook
1524 [acl]
1525 [acl]
1525 sources = push
1526 sources = push
1526 [extensions]
1527 [extensions]
1527 posixgetuser=$TESTTMP/posixgetuser.py
1528 posixgetuser=$TESTTMP/posixgetuser.py
1528 [acl.allow]
1529 [acl.allow]
1529 ** = @group1
1530 ** = @group1
1530 """
1531 """
1531 pushing to ../b
1532 pushing to ../b
1532 query 1; heads
1533 query 1; heads
1533 searching for changes
1534 searching for changes
1534 all remote heads known locally
1535 all remote heads known locally
1535 listing keys for "phases"
1536 listing keys for "phases"
1536 checking for updated bookmarks
1537 checking for updated bookmarks
1537 listing keys for "bookmarks"
1538 listing keys for "bookmarks"
1538 invalid branch cache (served): tip differs
1539 invalid branch cache (served): tip differs
1539 listing keys for "bookmarks"
1540 listing keys for "bookmarks"
1540 3 changesets found
1541 3 changesets found
1541 list of changesets:
1542 list of changesets:
1542 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1543 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1543 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1544 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1544 911600dab2ae7a9baff75958b84fe606851ce955
1545 911600dab2ae7a9baff75958b84fe606851ce955
1545 bundle2-output-bundle: "HG20", 5 parts total
1546 bundle2-output-bundle: "HG20", 5 parts total
1546 bundle2-output-part: "replycaps" 207 bytes payload
1547 bundle2-output-part: "replycaps" 207 bytes payload
1547 bundle2-output-part: "check:phases" 24 bytes payload
1548 bundle2-output-part: "check:phases" 24 bytes payload
1548 bundle2-output-part: "check:updated-heads" streamed payload
1549 bundle2-output-part: "check:updated-heads" streamed payload
1549 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1550 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1550 bundle2-output-part: "phase-heads" 24 bytes payload
1551 bundle2-output-part: "phase-heads" 24 bytes payload
1551 bundle2-input-bundle: with-transaction
1552 bundle2-input-bundle: with-transaction
1552 bundle2-input-part: "replycaps" supported
1553 bundle2-input-part: "replycaps" supported
1553 bundle2-input-part: total payload size 207
1554 bundle2-input-part: total payload size 207
1554 bundle2-input-part: "check:phases" supported
1555 bundle2-input-part: "check:phases" supported
1555 bundle2-input-part: total payload size 24
1556 bundle2-input-part: total payload size 24
1556 bundle2-input-part: "check:updated-heads" supported
1557 bundle2-input-part: "check:updated-heads" supported
1557 bundle2-input-part: total payload size 20
1558 bundle2-input-part: total payload size 20
1558 invalid branch cache (served): tip differs
1559 invalid branch cache (served): tip differs
1559 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1560 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1560 adding changesets
1561 adding changesets
1561 add changeset ef1ea85a6374
1562 add changeset ef1ea85a6374
1562 add changeset f9cafe1212c8
1563 add changeset f9cafe1212c8
1563 add changeset 911600dab2ae
1564 add changeset 911600dab2ae
1564 adding manifests
1565 adding manifests
1565 adding file changes
1566 adding file changes
1566 adding foo/Bar/file.txt revisions
1567 adding foo/Bar/file.txt revisions
1567 adding foo/file.txt revisions
1568 adding foo/file.txt revisions
1568 adding quux/file.py revisions
1569 adding quux/file.py revisions
1569 calling hook pretxnchangegroup.acl: hgext.acl.hook
1570 calling hook pretxnchangegroup.acl: hgext.acl.hook
1570 acl: checking access for user "fred"
1571 acl: checking access for user "fred"
1571 acl: acl.allow.branches not enabled
1572 acl: acl.allow.branches not enabled
1572 acl: acl.deny.branches not enabled
1573 acl: acl.deny.branches not enabled
1573 acl: "group1" not defined in [acl.groups]
1574 acl: "group1" not defined in [acl.groups]
1574 acl: acl.allow enabled, 1 entries for user fred
1575 acl: acl.allow enabled, 1 entries for user fred
1575 acl: acl.deny not enabled
1576 acl: acl.deny not enabled
1576 acl: branch access granted: "ef1ea85a6374" on branch "default"
1577 acl: branch access granted: "ef1ea85a6374" on branch "default"
1577 acl: path access granted: "ef1ea85a6374"
1578 acl: path access granted: "ef1ea85a6374"
1578 acl: branch access granted: "f9cafe1212c8" on branch "default"
1579 acl: branch access granted: "f9cafe1212c8" on branch "default"
1579 acl: path access granted: "f9cafe1212c8"
1580 acl: path access granted: "f9cafe1212c8"
1580 acl: branch access granted: "911600dab2ae" on branch "default"
1581 acl: branch access granted: "911600dab2ae" on branch "default"
1581 acl: path access granted: "911600dab2ae"
1582 acl: path access granted: "911600dab2ae"
1582 bundle2-input-part: total payload size 1553
1583 bundle2-input-part: total payload size 1553
1583 bundle2-input-part: "phase-heads" supported
1584 bundle2-input-part: "phase-heads" supported
1584 bundle2-input-part: total payload size 24
1585 bundle2-input-part: total payload size 24
1585 bundle2-input-bundle: 5 parts total
1586 bundle2-input-bundle: 5 parts total
1586 truncating cache/rbc-revs-v1 to 8
1587 updating the branch cache
1587 updating the branch cache
1588 added 3 changesets with 3 changes to 3 files
1588 added 3 changesets with 3 changes to 3 files
1589 bundle2-output-bundle: "HG20", 1 parts total
1589 bundle2-output-bundle: "HG20", 1 parts total
1590 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1590 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1591 bundle2-input-bundle: no-transaction
1591 bundle2-input-bundle: no-transaction
1592 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1592 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1593 bundle2-input-bundle: 1 parts total
1593 bundle2-input-bundle: 1 parts total
1594 listing keys for "phases"
1594 listing keys for "phases"
1595 repository tip rolled back to revision 0 (undo push)
1595 repository tip rolled back to revision 0 (undo push)
1596 0:6675d58eff77
1596 0:6675d58eff77
1597
1597
1598
1598
1599 $ echo '[acl.deny]' >> $config
1599 $ echo '[acl.deny]' >> $config
1600 $ echo "foo/Bar/** = @group1" >> $config
1600 $ echo "foo/Bar/** = @group1" >> $config
1601
1601
1602 @group is allowed inside anything but foo/Bar/
1602 @group is allowed inside anything but foo/Bar/
1603
1603
1604 $ do_push fred
1604 $ do_push fred
1605 Pushing as user fred
1605 Pushing as user fred
1606 hgrc = """
1606 hgrc = """
1607 [hooks]
1607 [hooks]
1608 pretxnchangegroup.acl = python:hgext.acl.hook
1608 pretxnchangegroup.acl = python:hgext.acl.hook
1609 prepushkey.acl = python:hgext.acl.hook
1609 prepushkey.acl = python:hgext.acl.hook
1610 [acl]
1610 [acl]
1611 sources = push
1611 sources = push
1612 [extensions]
1612 [extensions]
1613 posixgetuser=$TESTTMP/posixgetuser.py
1613 posixgetuser=$TESTTMP/posixgetuser.py
1614 [acl.allow]
1614 [acl.allow]
1615 ** = @group1
1615 ** = @group1
1616 [acl.deny]
1616 [acl.deny]
1617 foo/Bar/** = @group1
1617 foo/Bar/** = @group1
1618 """
1618 """
1619 pushing to ../b
1619 pushing to ../b
1620 query 1; heads
1620 query 1; heads
1621 searching for changes
1621 searching for changes
1622 all remote heads known locally
1622 all remote heads known locally
1623 listing keys for "phases"
1623 listing keys for "phases"
1624 checking for updated bookmarks
1624 checking for updated bookmarks
1625 listing keys for "bookmarks"
1625 listing keys for "bookmarks"
1626 invalid branch cache (served): tip differs
1626 invalid branch cache (served): tip differs
1627 listing keys for "bookmarks"
1627 listing keys for "bookmarks"
1628 3 changesets found
1628 3 changesets found
1629 list of changesets:
1629 list of changesets:
1630 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1630 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1631 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1631 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1632 911600dab2ae7a9baff75958b84fe606851ce955
1632 911600dab2ae7a9baff75958b84fe606851ce955
1633 bundle2-output-bundle: "HG20", 5 parts total
1633 bundle2-output-bundle: "HG20", 5 parts total
1634 bundle2-output-part: "replycaps" 207 bytes payload
1634 bundle2-output-part: "replycaps" 207 bytes payload
1635 bundle2-output-part: "check:phases" 24 bytes payload
1635 bundle2-output-part: "check:phases" 24 bytes payload
1636 bundle2-output-part: "check:updated-heads" streamed payload
1636 bundle2-output-part: "check:updated-heads" streamed payload
1637 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1637 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1638 bundle2-output-part: "phase-heads" 24 bytes payload
1638 bundle2-output-part: "phase-heads" 24 bytes payload
1639 bundle2-input-bundle: with-transaction
1639 bundle2-input-bundle: with-transaction
1640 bundle2-input-part: "replycaps" supported
1640 bundle2-input-part: "replycaps" supported
1641 bundle2-input-part: total payload size 207
1641 bundle2-input-part: total payload size 207
1642 bundle2-input-part: "check:phases" supported
1642 bundle2-input-part: "check:phases" supported
1643 bundle2-input-part: total payload size 24
1643 bundle2-input-part: total payload size 24
1644 bundle2-input-part: "check:updated-heads" supported
1644 bundle2-input-part: "check:updated-heads" supported
1645 bundle2-input-part: total payload size 20
1645 bundle2-input-part: total payload size 20
1646 invalid branch cache (served): tip differs
1646 invalid branch cache (served): tip differs
1647 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1647 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1648 adding changesets
1648 adding changesets
1649 add changeset ef1ea85a6374
1649 add changeset ef1ea85a6374
1650 add changeset f9cafe1212c8
1650 add changeset f9cafe1212c8
1651 add changeset 911600dab2ae
1651 add changeset 911600dab2ae
1652 adding manifests
1652 adding manifests
1653 adding file changes
1653 adding file changes
1654 adding foo/Bar/file.txt revisions
1654 adding foo/Bar/file.txt revisions
1655 adding foo/file.txt revisions
1655 adding foo/file.txt revisions
1656 adding quux/file.py revisions
1656 adding quux/file.py revisions
1657 calling hook pretxnchangegroup.acl: hgext.acl.hook
1657 calling hook pretxnchangegroup.acl: hgext.acl.hook
1658 acl: checking access for user "fred"
1658 acl: checking access for user "fred"
1659 acl: acl.allow.branches not enabled
1659 acl: acl.allow.branches not enabled
1660 acl: acl.deny.branches not enabled
1660 acl: acl.deny.branches not enabled
1661 acl: "group1" not defined in [acl.groups]
1661 acl: "group1" not defined in [acl.groups]
1662 acl: acl.allow enabled, 1 entries for user fred
1662 acl: acl.allow enabled, 1 entries for user fred
1663 acl: "group1" not defined in [acl.groups]
1663 acl: "group1" not defined in [acl.groups]
1664 acl: acl.deny enabled, 1 entries for user fred
1664 acl: acl.deny enabled, 1 entries for user fred
1665 acl: branch access granted: "ef1ea85a6374" on branch "default"
1665 acl: branch access granted: "ef1ea85a6374" on branch "default"
1666 acl: path access granted: "ef1ea85a6374"
1666 acl: path access granted: "ef1ea85a6374"
1667 acl: branch access granted: "f9cafe1212c8" on branch "default"
1667 acl: branch access granted: "f9cafe1212c8" on branch "default"
1668 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1668 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1669 bundle2-input-part: total payload size 1553
1669 bundle2-input-part: total payload size 1553
1670 bundle2-input-part: total payload size 24
1670 bundle2-input-part: total payload size 24
1671 bundle2-input-bundle: 5 parts total
1671 bundle2-input-bundle: 5 parts total
1672 transaction abort!
1672 transaction abort!
1673 rollback completed
1673 rollback completed
1674 truncating cache/rbc-revs-v1 to 8
1674 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1675 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1675 no rollback information available
1676 no rollback information available
1676 0:6675d58eff77
1677 0:6675d58eff77
1677
1678
1678
1679
1679 Invalid group
1680 Invalid group
1680
1681
1681 Disable the fakegroups trick to get real failures
1682 Disable the fakegroups trick to get real failures
1682
1683
1683 $ grep -v fakegroups $config > config.tmp
1684 $ grep -v fakegroups $config > config.tmp
1684 $ mv config.tmp $config
1685 $ mv config.tmp $config
1685 $ echo '[acl.allow]' >> $config
1686 $ echo '[acl.allow]' >> $config
1686 $ echo "** = @unlikelytoexist" >> $config
1687 $ echo "** = @unlikelytoexist" >> $config
1687 $ do_push fred 2>&1 | grep unlikelytoexist
1688 $ do_push fred 2>&1 | grep unlikelytoexist
1688 ** = @unlikelytoexist
1689 ** = @unlikelytoexist
1689 acl: "unlikelytoexist" not defined in [acl.groups]
1690 acl: "unlikelytoexist" not defined in [acl.groups]
1690 error: pretxnchangegroup.acl hook failed: group 'unlikelytoexist' is undefined
1691 error: pretxnchangegroup.acl hook failed: group 'unlikelytoexist' is undefined
1691 abort: group 'unlikelytoexist' is undefined
1692 abort: group 'unlikelytoexist' is undefined
1692
1693
1693
1694
1694 Branch acl tests setup
1695 Branch acl tests setup
1695
1696
1696 $ init_config
1697 $ init_config
1697 $ cd b
1698 $ cd b
1698 $ hg up
1699 $ hg up
1699 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1700 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1700 $ hg branch foobar
1701 $ hg branch foobar
1701 marked working directory as branch foobar
1702 marked working directory as branch foobar
1702 (branches are permanent and global, did you want a bookmark?)
1703 (branches are permanent and global, did you want a bookmark?)
1703 $ hg commit -m 'create foobar'
1704 $ hg commit -m 'create foobar'
1704 $ echo 'foo contents' > abc.txt
1705 $ echo 'foo contents' > abc.txt
1705 $ hg add abc.txt
1706 $ hg add abc.txt
1706 $ hg commit -m 'foobar contents'
1707 $ hg commit -m 'foobar contents'
1707 $ cd ..
1708 $ cd ..
1708 $ hg --cwd a pull ../b
1709 $ hg --cwd a pull ../b
1709 pulling from ../b
1710 pulling from ../b
1710 searching for changes
1711 searching for changes
1711 adding changesets
1712 adding changesets
1712 adding manifests
1713 adding manifests
1713 adding file changes
1714 adding file changes
1714 added 2 changesets with 1 changes to 1 files (+1 heads)
1715 added 2 changesets with 1 changes to 1 files (+1 heads)
1715 new changesets 81fbf4469322:fb35475503ef
1716 new changesets 81fbf4469322:fb35475503ef
1716 (run 'hg heads' to see heads)
1717 (run 'hg heads' to see heads)
1717
1718
1718 Create additional changeset on foobar branch
1719 Create additional changeset on foobar branch
1719
1720
1720 $ cd a
1721 $ cd a
1721 $ hg up -C foobar
1722 $ hg up -C foobar
1722 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1723 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1723 $ echo 'foo contents2' > abc.txt
1724 $ echo 'foo contents2' > abc.txt
1724 $ hg commit -m 'foobar contents2'
1725 $ hg commit -m 'foobar contents2'
1725 $ cd ..
1726 $ cd ..
1726
1727
1727
1728
1728 No branch acls specified
1729 No branch acls specified
1729
1730
1730 $ do_push astro
1731 $ do_push astro
1731 Pushing as user astro
1732 Pushing as user astro
1732 hgrc = """
1733 hgrc = """
1733 [hooks]
1734 [hooks]
1734 pretxnchangegroup.acl = python:hgext.acl.hook
1735 pretxnchangegroup.acl = python:hgext.acl.hook
1735 prepushkey.acl = python:hgext.acl.hook
1736 prepushkey.acl = python:hgext.acl.hook
1736 [acl]
1737 [acl]
1737 sources = push
1738 sources = push
1738 [extensions]
1739 [extensions]
1739 posixgetuser=$TESTTMP/posixgetuser.py
1740 posixgetuser=$TESTTMP/posixgetuser.py
1740 """
1741 """
1741 pushing to ../b
1742 pushing to ../b
1742 query 1; heads
1743 query 1; heads
1743 searching for changes
1744 searching for changes
1744 all remote heads known locally
1745 all remote heads known locally
1745 listing keys for "phases"
1746 listing keys for "phases"
1746 checking for updated bookmarks
1747 checking for updated bookmarks
1747 listing keys for "bookmarks"
1748 listing keys for "bookmarks"
1748 listing keys for "bookmarks"
1749 listing keys for "bookmarks"
1749 4 changesets found
1750 4 changesets found
1750 list of changesets:
1751 list of changesets:
1751 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1752 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1752 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1753 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1753 911600dab2ae7a9baff75958b84fe606851ce955
1754 911600dab2ae7a9baff75958b84fe606851ce955
1754 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1755 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1755 bundle2-output-bundle: "HG20", 5 parts total
1756 bundle2-output-bundle: "HG20", 5 parts total
1756 bundle2-output-part: "replycaps" 207 bytes payload
1757 bundle2-output-part: "replycaps" 207 bytes payload
1757 bundle2-output-part: "check:phases" 48 bytes payload
1758 bundle2-output-part: "check:phases" 48 bytes payload
1758 bundle2-output-part: "check:updated-heads" streamed payload
1759 bundle2-output-part: "check:updated-heads" streamed payload
1759 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1760 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1760 bundle2-output-part: "phase-heads" 48 bytes payload
1761 bundle2-output-part: "phase-heads" 48 bytes payload
1761 bundle2-input-bundle: with-transaction
1762 bundle2-input-bundle: with-transaction
1762 bundle2-input-part: "replycaps" supported
1763 bundle2-input-part: "replycaps" supported
1763 bundle2-input-part: total payload size 207
1764 bundle2-input-part: total payload size 207
1764 bundle2-input-part: "check:phases" supported
1765 bundle2-input-part: "check:phases" supported
1765 bundle2-input-part: total payload size 48
1766 bundle2-input-part: total payload size 48
1766 bundle2-input-part: "check:updated-heads" supported
1767 bundle2-input-part: "check:updated-heads" supported
1767 bundle2-input-part: total payload size 40
1768 bundle2-input-part: total payload size 40
1768 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1769 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1769 adding changesets
1770 adding changesets
1770 add changeset ef1ea85a6374
1771 add changeset ef1ea85a6374
1771 add changeset f9cafe1212c8
1772 add changeset f9cafe1212c8
1772 add changeset 911600dab2ae
1773 add changeset 911600dab2ae
1773 add changeset e8fc755d4d82
1774 add changeset e8fc755d4d82
1774 adding manifests
1775 adding manifests
1775 adding file changes
1776 adding file changes
1776 adding abc.txt revisions
1777 adding abc.txt revisions
1777 adding foo/Bar/file.txt revisions
1778 adding foo/Bar/file.txt revisions
1778 adding foo/file.txt revisions
1779 adding foo/file.txt revisions
1779 adding quux/file.py revisions
1780 adding quux/file.py revisions
1780 calling hook pretxnchangegroup.acl: hgext.acl.hook
1781 calling hook pretxnchangegroup.acl: hgext.acl.hook
1781 acl: checking access for user "astro"
1782 acl: checking access for user "astro"
1782 acl: acl.allow.branches not enabled
1783 acl: acl.allow.branches not enabled
1783 acl: acl.deny.branches not enabled
1784 acl: acl.deny.branches not enabled
1784 acl: acl.allow not enabled
1785 acl: acl.allow not enabled
1785 acl: acl.deny not enabled
1786 acl: acl.deny not enabled
1786 acl: branch access granted: "ef1ea85a6374" on branch "default"
1787 acl: branch access granted: "ef1ea85a6374" on branch "default"
1787 acl: path access granted: "ef1ea85a6374"
1788 acl: path access granted: "ef1ea85a6374"
1788 acl: branch access granted: "f9cafe1212c8" on branch "default"
1789 acl: branch access granted: "f9cafe1212c8" on branch "default"
1789 acl: path access granted: "f9cafe1212c8"
1790 acl: path access granted: "f9cafe1212c8"
1790 acl: branch access granted: "911600dab2ae" on branch "default"
1791 acl: branch access granted: "911600dab2ae" on branch "default"
1791 acl: path access granted: "911600dab2ae"
1792 acl: path access granted: "911600dab2ae"
1792 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1793 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1793 acl: path access granted: "e8fc755d4d82"
1794 acl: path access granted: "e8fc755d4d82"
1794 bundle2-input-part: total payload size 2068
1795 bundle2-input-part: total payload size 2068
1795 bundle2-input-part: "phase-heads" supported
1796 bundle2-input-part: "phase-heads" supported
1796 bundle2-input-part: total payload size 48
1797 bundle2-input-part: total payload size 48
1797 bundle2-input-bundle: 5 parts total
1798 bundle2-input-bundle: 5 parts total
1798 updating the branch cache
1799 updating the branch cache
1799 invalid branch cache (served.hidden): tip differs
1800 invalid branch cache (served.hidden): tip differs
1800 added 4 changesets with 4 changes to 4 files (+1 heads)
1801 added 4 changesets with 4 changes to 4 files (+1 heads)
1801 bundle2-output-bundle: "HG20", 1 parts total
1802 bundle2-output-bundle: "HG20", 1 parts total
1802 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1803 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1803 bundle2-input-bundle: no-transaction
1804 bundle2-input-bundle: no-transaction
1804 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1805 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1805 bundle2-input-bundle: 1 parts total
1806 bundle2-input-bundle: 1 parts total
1806 listing keys for "phases"
1807 listing keys for "phases"
1807 repository tip rolled back to revision 2 (undo push)
1808 repository tip rolled back to revision 2 (undo push)
1808 2:fb35475503ef
1809 2:fb35475503ef
1809
1810
1810
1811
1811 Branch acl deny test
1812 Branch acl deny test
1812
1813
1813 $ echo "[acl.deny.branches]" >> $config
1814 $ echo "[acl.deny.branches]" >> $config
1814 $ echo "foobar = *" >> $config
1815 $ echo "foobar = *" >> $config
1815 $ do_push astro
1816 $ do_push astro
1816 Pushing as user astro
1817 Pushing as user astro
1817 hgrc = """
1818 hgrc = """
1818 [hooks]
1819 [hooks]
1819 pretxnchangegroup.acl = python:hgext.acl.hook
1820 pretxnchangegroup.acl = python:hgext.acl.hook
1820 prepushkey.acl = python:hgext.acl.hook
1821 prepushkey.acl = python:hgext.acl.hook
1821 [acl]
1822 [acl]
1822 sources = push
1823 sources = push
1823 [extensions]
1824 [extensions]
1824 posixgetuser=$TESTTMP/posixgetuser.py
1825 posixgetuser=$TESTTMP/posixgetuser.py
1825 [acl.deny.branches]
1826 [acl.deny.branches]
1826 foobar = *
1827 foobar = *
1827 """
1828 """
1828 pushing to ../b
1829 pushing to ../b
1829 query 1; heads
1830 query 1; heads
1830 searching for changes
1831 searching for changes
1831 all remote heads known locally
1832 all remote heads known locally
1832 listing keys for "phases"
1833 listing keys for "phases"
1833 checking for updated bookmarks
1834 checking for updated bookmarks
1834 listing keys for "bookmarks"
1835 listing keys for "bookmarks"
1835 listing keys for "bookmarks"
1836 listing keys for "bookmarks"
1836 4 changesets found
1837 4 changesets found
1837 list of changesets:
1838 list of changesets:
1838 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1839 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1839 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1840 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1840 911600dab2ae7a9baff75958b84fe606851ce955
1841 911600dab2ae7a9baff75958b84fe606851ce955
1841 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1842 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1842 bundle2-output-bundle: "HG20", 5 parts total
1843 bundle2-output-bundle: "HG20", 5 parts total
1843 bundle2-output-part: "replycaps" 207 bytes payload
1844 bundle2-output-part: "replycaps" 207 bytes payload
1844 bundle2-output-part: "check:phases" 48 bytes payload
1845 bundle2-output-part: "check:phases" 48 bytes payload
1845 bundle2-output-part: "check:updated-heads" streamed payload
1846 bundle2-output-part: "check:updated-heads" streamed payload
1846 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1847 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1847 bundle2-output-part: "phase-heads" 48 bytes payload
1848 bundle2-output-part: "phase-heads" 48 bytes payload
1848 bundle2-input-bundle: with-transaction
1849 bundle2-input-bundle: with-transaction
1849 bundle2-input-part: "replycaps" supported
1850 bundle2-input-part: "replycaps" supported
1850 bundle2-input-part: total payload size 207
1851 bundle2-input-part: total payload size 207
1851 bundle2-input-part: "check:phases" supported
1852 bundle2-input-part: "check:phases" supported
1852 bundle2-input-part: total payload size 48
1853 bundle2-input-part: total payload size 48
1853 bundle2-input-part: "check:updated-heads" supported
1854 bundle2-input-part: "check:updated-heads" supported
1854 bundle2-input-part: total payload size 40
1855 bundle2-input-part: total payload size 40
1855 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1856 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1856 adding changesets
1857 adding changesets
1857 add changeset ef1ea85a6374
1858 add changeset ef1ea85a6374
1858 add changeset f9cafe1212c8
1859 add changeset f9cafe1212c8
1859 add changeset 911600dab2ae
1860 add changeset 911600dab2ae
1860 add changeset e8fc755d4d82
1861 add changeset e8fc755d4d82
1861 adding manifests
1862 adding manifests
1862 adding file changes
1863 adding file changes
1863 adding abc.txt revisions
1864 adding abc.txt revisions
1864 adding foo/Bar/file.txt revisions
1865 adding foo/Bar/file.txt revisions
1865 adding foo/file.txt revisions
1866 adding foo/file.txt revisions
1866 adding quux/file.py revisions
1867 adding quux/file.py revisions
1867 calling hook pretxnchangegroup.acl: hgext.acl.hook
1868 calling hook pretxnchangegroup.acl: hgext.acl.hook
1868 acl: checking access for user "astro"
1869 acl: checking access for user "astro"
1869 acl: acl.allow.branches not enabled
1870 acl: acl.allow.branches not enabled
1870 acl: acl.deny.branches enabled, 1 entries for user astro
1871 acl: acl.deny.branches enabled, 1 entries for user astro
1871 acl: acl.allow not enabled
1872 acl: acl.allow not enabled
1872 acl: acl.deny not enabled
1873 acl: acl.deny not enabled
1873 acl: branch access granted: "ef1ea85a6374" on branch "default"
1874 acl: branch access granted: "ef1ea85a6374" on branch "default"
1874 acl: path access granted: "ef1ea85a6374"
1875 acl: path access granted: "ef1ea85a6374"
1875 acl: branch access granted: "f9cafe1212c8" on branch "default"
1876 acl: branch access granted: "f9cafe1212c8" on branch "default"
1876 acl: path access granted: "f9cafe1212c8"
1877 acl: path access granted: "f9cafe1212c8"
1877 acl: branch access granted: "911600dab2ae" on branch "default"
1878 acl: branch access granted: "911600dab2ae" on branch "default"
1878 acl: path access granted: "911600dab2ae"
1879 acl: path access granted: "911600dab2ae"
1879 error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1880 error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1880 bundle2-input-part: total payload size 2068
1881 bundle2-input-part: total payload size 2068
1881 bundle2-input-part: total payload size 48
1882 bundle2-input-part: total payload size 48
1882 bundle2-input-bundle: 5 parts total
1883 bundle2-input-bundle: 5 parts total
1883 transaction abort!
1884 transaction abort!
1884 rollback completed
1885 rollback completed
1885 abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1886 abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1886 no rollback information available
1887 no rollback information available
1887 2:fb35475503ef
1888 2:fb35475503ef
1888
1889
1889
1890
1890 Branch acl empty allow test
1891 Branch acl empty allow test
1891
1892
1892 $ init_config
1893 $ init_config
1893 $ echo "[acl.allow.branches]" >> $config
1894 $ echo "[acl.allow.branches]" >> $config
1894 $ do_push astro
1895 $ do_push astro
1895 Pushing as user astro
1896 Pushing as user astro
1896 hgrc = """
1897 hgrc = """
1897 [hooks]
1898 [hooks]
1898 pretxnchangegroup.acl = python:hgext.acl.hook
1899 pretxnchangegroup.acl = python:hgext.acl.hook
1899 prepushkey.acl = python:hgext.acl.hook
1900 prepushkey.acl = python:hgext.acl.hook
1900 [acl]
1901 [acl]
1901 sources = push
1902 sources = push
1902 [extensions]
1903 [extensions]
1903 posixgetuser=$TESTTMP/posixgetuser.py
1904 posixgetuser=$TESTTMP/posixgetuser.py
1904 [acl.allow.branches]
1905 [acl.allow.branches]
1905 """
1906 """
1906 pushing to ../b
1907 pushing to ../b
1907 query 1; heads
1908 query 1; heads
1908 searching for changes
1909 searching for changes
1909 all remote heads known locally
1910 all remote heads known locally
1910 listing keys for "phases"
1911 listing keys for "phases"
1911 checking for updated bookmarks
1912 checking for updated bookmarks
1912 listing keys for "bookmarks"
1913 listing keys for "bookmarks"
1913 listing keys for "bookmarks"
1914 listing keys for "bookmarks"
1914 4 changesets found
1915 4 changesets found
1915 list of changesets:
1916 list of changesets:
1916 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1917 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1917 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1918 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1918 911600dab2ae7a9baff75958b84fe606851ce955
1919 911600dab2ae7a9baff75958b84fe606851ce955
1919 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1920 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1920 bundle2-output-bundle: "HG20", 5 parts total
1921 bundle2-output-bundle: "HG20", 5 parts total
1921 bundle2-output-part: "replycaps" 207 bytes payload
1922 bundle2-output-part: "replycaps" 207 bytes payload
1922 bundle2-output-part: "check:phases" 48 bytes payload
1923 bundle2-output-part: "check:phases" 48 bytes payload
1923 bundle2-output-part: "check:updated-heads" streamed payload
1924 bundle2-output-part: "check:updated-heads" streamed payload
1924 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1925 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1925 bundle2-output-part: "phase-heads" 48 bytes payload
1926 bundle2-output-part: "phase-heads" 48 bytes payload
1926 bundle2-input-bundle: with-transaction
1927 bundle2-input-bundle: with-transaction
1927 bundle2-input-part: "replycaps" supported
1928 bundle2-input-part: "replycaps" supported
1928 bundle2-input-part: total payload size 207
1929 bundle2-input-part: total payload size 207
1929 bundle2-input-part: "check:phases" supported
1930 bundle2-input-part: "check:phases" supported
1930 bundle2-input-part: total payload size 48
1931 bundle2-input-part: total payload size 48
1931 bundle2-input-part: "check:updated-heads" supported
1932 bundle2-input-part: "check:updated-heads" supported
1932 bundle2-input-part: total payload size 40
1933 bundle2-input-part: total payload size 40
1933 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1934 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1934 adding changesets
1935 adding changesets
1935 add changeset ef1ea85a6374
1936 add changeset ef1ea85a6374
1936 add changeset f9cafe1212c8
1937 add changeset f9cafe1212c8
1937 add changeset 911600dab2ae
1938 add changeset 911600dab2ae
1938 add changeset e8fc755d4d82
1939 add changeset e8fc755d4d82
1939 adding manifests
1940 adding manifests
1940 adding file changes
1941 adding file changes
1941 adding abc.txt revisions
1942 adding abc.txt revisions
1942 adding foo/Bar/file.txt revisions
1943 adding foo/Bar/file.txt revisions
1943 adding foo/file.txt revisions
1944 adding foo/file.txt revisions
1944 adding quux/file.py revisions
1945 adding quux/file.py revisions
1945 calling hook pretxnchangegroup.acl: hgext.acl.hook
1946 calling hook pretxnchangegroup.acl: hgext.acl.hook
1946 acl: checking access for user "astro"
1947 acl: checking access for user "astro"
1947 acl: acl.allow.branches enabled, 0 entries for user astro
1948 acl: acl.allow.branches enabled, 0 entries for user astro
1948 acl: acl.deny.branches not enabled
1949 acl: acl.deny.branches not enabled
1949 acl: acl.allow not enabled
1950 acl: acl.allow not enabled
1950 acl: acl.deny not enabled
1951 acl: acl.deny not enabled
1951 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1952 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1952 bundle2-input-part: total payload size 2068
1953 bundle2-input-part: total payload size 2068
1953 bundle2-input-part: total payload size 48
1954 bundle2-input-part: total payload size 48
1954 bundle2-input-bundle: 5 parts total
1955 bundle2-input-bundle: 5 parts total
1955 transaction abort!
1956 transaction abort!
1956 rollback completed
1957 rollback completed
1957 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1958 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1958 no rollback information available
1959 no rollback information available
1959 2:fb35475503ef
1960 2:fb35475503ef
1960
1961
1961
1962
1962 Branch acl allow other
1963 Branch acl allow other
1963
1964
1964 $ init_config
1965 $ init_config
1965 $ echo "[acl.allow.branches]" >> $config
1966 $ echo "[acl.allow.branches]" >> $config
1966 $ echo "* = george" >> $config
1967 $ echo "* = george" >> $config
1967 $ do_push astro
1968 $ do_push astro
1968 Pushing as user astro
1969 Pushing as user astro
1969 hgrc = """
1970 hgrc = """
1970 [hooks]
1971 [hooks]
1971 pretxnchangegroup.acl = python:hgext.acl.hook
1972 pretxnchangegroup.acl = python:hgext.acl.hook
1972 prepushkey.acl = python:hgext.acl.hook
1973 prepushkey.acl = python:hgext.acl.hook
1973 [acl]
1974 [acl]
1974 sources = push
1975 sources = push
1975 [extensions]
1976 [extensions]
1976 posixgetuser=$TESTTMP/posixgetuser.py
1977 posixgetuser=$TESTTMP/posixgetuser.py
1977 [acl.allow.branches]
1978 [acl.allow.branches]
1978 * = george
1979 * = george
1979 """
1980 """
1980 pushing to ../b
1981 pushing to ../b
1981 query 1; heads
1982 query 1; heads
1982 searching for changes
1983 searching for changes
1983 all remote heads known locally
1984 all remote heads known locally
1984 listing keys for "phases"
1985 listing keys for "phases"
1985 checking for updated bookmarks
1986 checking for updated bookmarks
1986 listing keys for "bookmarks"
1987 listing keys for "bookmarks"
1987 listing keys for "bookmarks"
1988 listing keys for "bookmarks"
1988 4 changesets found
1989 4 changesets found
1989 list of changesets:
1990 list of changesets:
1990 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1991 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1991 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1992 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1992 911600dab2ae7a9baff75958b84fe606851ce955
1993 911600dab2ae7a9baff75958b84fe606851ce955
1993 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1994 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1994 bundle2-output-bundle: "HG20", 5 parts total
1995 bundle2-output-bundle: "HG20", 5 parts total
1995 bundle2-output-part: "replycaps" 207 bytes payload
1996 bundle2-output-part: "replycaps" 207 bytes payload
1996 bundle2-output-part: "check:phases" 48 bytes payload
1997 bundle2-output-part: "check:phases" 48 bytes payload
1997 bundle2-output-part: "check:updated-heads" streamed payload
1998 bundle2-output-part: "check:updated-heads" streamed payload
1998 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1999 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1999 bundle2-output-part: "phase-heads" 48 bytes payload
2000 bundle2-output-part: "phase-heads" 48 bytes payload
2000 bundle2-input-bundle: with-transaction
2001 bundle2-input-bundle: with-transaction
2001 bundle2-input-part: "replycaps" supported
2002 bundle2-input-part: "replycaps" supported
2002 bundle2-input-part: total payload size 207
2003 bundle2-input-part: total payload size 207
2003 bundle2-input-part: "check:phases" supported
2004 bundle2-input-part: "check:phases" supported
2004 bundle2-input-part: total payload size 48
2005 bundle2-input-part: total payload size 48
2005 bundle2-input-part: "check:updated-heads" supported
2006 bundle2-input-part: "check:updated-heads" supported
2006 bundle2-input-part: total payload size 40
2007 bundle2-input-part: total payload size 40
2007 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2008 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2008 adding changesets
2009 adding changesets
2009 add changeset ef1ea85a6374
2010 add changeset ef1ea85a6374
2010 add changeset f9cafe1212c8
2011 add changeset f9cafe1212c8
2011 add changeset 911600dab2ae
2012 add changeset 911600dab2ae
2012 add changeset e8fc755d4d82
2013 add changeset e8fc755d4d82
2013 adding manifests
2014 adding manifests
2014 adding file changes
2015 adding file changes
2015 adding abc.txt revisions
2016 adding abc.txt revisions
2016 adding foo/Bar/file.txt revisions
2017 adding foo/Bar/file.txt revisions
2017 adding foo/file.txt revisions
2018 adding foo/file.txt revisions
2018 adding quux/file.py revisions
2019 adding quux/file.py revisions
2019 calling hook pretxnchangegroup.acl: hgext.acl.hook
2020 calling hook pretxnchangegroup.acl: hgext.acl.hook
2020 acl: checking access for user "astro"
2021 acl: checking access for user "astro"
2021 acl: acl.allow.branches enabled, 0 entries for user astro
2022 acl: acl.allow.branches enabled, 0 entries for user astro
2022 acl: acl.deny.branches not enabled
2023 acl: acl.deny.branches not enabled
2023 acl: acl.allow not enabled
2024 acl: acl.allow not enabled
2024 acl: acl.deny not enabled
2025 acl: acl.deny not enabled
2025 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
2026 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
2026 bundle2-input-part: total payload size 2068
2027 bundle2-input-part: total payload size 2068
2027 bundle2-input-part: total payload size 48
2028 bundle2-input-part: total payload size 48
2028 bundle2-input-bundle: 5 parts total
2029 bundle2-input-bundle: 5 parts total
2029 transaction abort!
2030 transaction abort!
2030 rollback completed
2031 rollback completed
2031 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
2032 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
2032 no rollback information available
2033 no rollback information available
2033 2:fb35475503ef
2034 2:fb35475503ef
2034
2035
2035 $ do_push george
2036 $ do_push george
2036 Pushing as user george
2037 Pushing as user george
2037 hgrc = """
2038 hgrc = """
2038 [hooks]
2039 [hooks]
2039 pretxnchangegroup.acl = python:hgext.acl.hook
2040 pretxnchangegroup.acl = python:hgext.acl.hook
2040 prepushkey.acl = python:hgext.acl.hook
2041 prepushkey.acl = python:hgext.acl.hook
2041 [acl]
2042 [acl]
2042 sources = push
2043 sources = push
2043 [extensions]
2044 [extensions]
2044 posixgetuser=$TESTTMP/posixgetuser.py
2045 posixgetuser=$TESTTMP/posixgetuser.py
2045 [acl.allow.branches]
2046 [acl.allow.branches]
2046 * = george
2047 * = george
2047 """
2048 """
2048 pushing to ../b
2049 pushing to ../b
2049 query 1; heads
2050 query 1; heads
2050 searching for changes
2051 searching for changes
2051 all remote heads known locally
2052 all remote heads known locally
2052 listing keys for "phases"
2053 listing keys for "phases"
2053 checking for updated bookmarks
2054 checking for updated bookmarks
2054 listing keys for "bookmarks"
2055 listing keys for "bookmarks"
2055 listing keys for "bookmarks"
2056 listing keys for "bookmarks"
2056 4 changesets found
2057 4 changesets found
2057 list of changesets:
2058 list of changesets:
2058 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2059 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2059 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2060 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2060 911600dab2ae7a9baff75958b84fe606851ce955
2061 911600dab2ae7a9baff75958b84fe606851ce955
2061 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2062 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2062 bundle2-output-bundle: "HG20", 5 parts total
2063 bundle2-output-bundle: "HG20", 5 parts total
2063 bundle2-output-part: "replycaps" 207 bytes payload
2064 bundle2-output-part: "replycaps" 207 bytes payload
2064 bundle2-output-part: "check:phases" 48 bytes payload
2065 bundle2-output-part: "check:phases" 48 bytes payload
2065 bundle2-output-part: "check:updated-heads" streamed payload
2066 bundle2-output-part: "check:updated-heads" streamed payload
2066 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2067 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2067 bundle2-output-part: "phase-heads" 48 bytes payload
2068 bundle2-output-part: "phase-heads" 48 bytes payload
2068 bundle2-input-bundle: with-transaction
2069 bundle2-input-bundle: with-transaction
2069 bundle2-input-part: "replycaps" supported
2070 bundle2-input-part: "replycaps" supported
2070 bundle2-input-part: total payload size 207
2071 bundle2-input-part: total payload size 207
2071 bundle2-input-part: "check:phases" supported
2072 bundle2-input-part: "check:phases" supported
2072 bundle2-input-part: total payload size 48
2073 bundle2-input-part: total payload size 48
2073 bundle2-input-part: "check:updated-heads" supported
2074 bundle2-input-part: "check:updated-heads" supported
2074 bundle2-input-part: total payload size 40
2075 bundle2-input-part: total payload size 40
2075 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2076 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2076 adding changesets
2077 adding changesets
2077 add changeset ef1ea85a6374
2078 add changeset ef1ea85a6374
2078 add changeset f9cafe1212c8
2079 add changeset f9cafe1212c8
2079 add changeset 911600dab2ae
2080 add changeset 911600dab2ae
2080 add changeset e8fc755d4d82
2081 add changeset e8fc755d4d82
2081 adding manifests
2082 adding manifests
2082 adding file changes
2083 adding file changes
2083 adding abc.txt revisions
2084 adding abc.txt revisions
2084 adding foo/Bar/file.txt revisions
2085 adding foo/Bar/file.txt revisions
2085 adding foo/file.txt revisions
2086 adding foo/file.txt revisions
2086 adding quux/file.py revisions
2087 adding quux/file.py revisions
2087 calling hook pretxnchangegroup.acl: hgext.acl.hook
2088 calling hook pretxnchangegroup.acl: hgext.acl.hook
2088 acl: checking access for user "george"
2089 acl: checking access for user "george"
2089 acl: acl.allow.branches enabled, 1 entries for user george
2090 acl: acl.allow.branches enabled, 1 entries for user george
2090 acl: acl.deny.branches not enabled
2091 acl: acl.deny.branches not enabled
2091 acl: acl.allow not enabled
2092 acl: acl.allow not enabled
2092 acl: acl.deny not enabled
2093 acl: acl.deny not enabled
2093 acl: branch access granted: "ef1ea85a6374" on branch "default"
2094 acl: branch access granted: "ef1ea85a6374" on branch "default"
2094 acl: path access granted: "ef1ea85a6374"
2095 acl: path access granted: "ef1ea85a6374"
2095 acl: branch access granted: "f9cafe1212c8" on branch "default"
2096 acl: branch access granted: "f9cafe1212c8" on branch "default"
2096 acl: path access granted: "f9cafe1212c8"
2097 acl: path access granted: "f9cafe1212c8"
2097 acl: branch access granted: "911600dab2ae" on branch "default"
2098 acl: branch access granted: "911600dab2ae" on branch "default"
2098 acl: path access granted: "911600dab2ae"
2099 acl: path access granted: "911600dab2ae"
2099 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2100 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2100 acl: path access granted: "e8fc755d4d82"
2101 acl: path access granted: "e8fc755d4d82"
2101 bundle2-input-part: total payload size 2068
2102 bundle2-input-part: total payload size 2068
2102 bundle2-input-part: "phase-heads" supported
2103 bundle2-input-part: "phase-heads" supported
2103 bundle2-input-part: total payload size 48
2104 bundle2-input-part: total payload size 48
2104 bundle2-input-bundle: 5 parts total
2105 bundle2-input-bundle: 5 parts total
2105 updating the branch cache
2106 updating the branch cache
2106 invalid branch cache (served.hidden): tip differs
2107 invalid branch cache (served.hidden): tip differs
2107 added 4 changesets with 4 changes to 4 files (+1 heads)
2108 added 4 changesets with 4 changes to 4 files (+1 heads)
2108 bundle2-output-bundle: "HG20", 1 parts total
2109 bundle2-output-bundle: "HG20", 1 parts total
2109 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2110 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2110 bundle2-input-bundle: no-transaction
2111 bundle2-input-bundle: no-transaction
2111 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2112 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2112 bundle2-input-bundle: 1 parts total
2113 bundle2-input-bundle: 1 parts total
2113 listing keys for "phases"
2114 listing keys for "phases"
2114 repository tip rolled back to revision 2 (undo push)
2115 repository tip rolled back to revision 2 (undo push)
2115 2:fb35475503ef
2116 2:fb35475503ef
2116
2117
2117
2118
2118 Branch acl conflicting allow
2119 Branch acl conflicting allow
2119 asterisk ends up applying to all branches and allowing george to
2120 asterisk ends up applying to all branches and allowing george to
2120 push foobar into the remote
2121 push foobar into the remote
2121
2122
2122 $ init_config
2123 $ init_config
2123 $ echo "[acl.allow.branches]" >> $config
2124 $ echo "[acl.allow.branches]" >> $config
2124 $ echo "foobar = astro" >> $config
2125 $ echo "foobar = astro" >> $config
2125 $ echo "* = george" >> $config
2126 $ echo "* = george" >> $config
2126 $ do_push george
2127 $ do_push george
2127 Pushing as user george
2128 Pushing as user george
2128 hgrc = """
2129 hgrc = """
2129 [hooks]
2130 [hooks]
2130 pretxnchangegroup.acl = python:hgext.acl.hook
2131 pretxnchangegroup.acl = python:hgext.acl.hook
2131 prepushkey.acl = python:hgext.acl.hook
2132 prepushkey.acl = python:hgext.acl.hook
2132 [acl]
2133 [acl]
2133 sources = push
2134 sources = push
2134 [extensions]
2135 [extensions]
2135 posixgetuser=$TESTTMP/posixgetuser.py
2136 posixgetuser=$TESTTMP/posixgetuser.py
2136 [acl.allow.branches]
2137 [acl.allow.branches]
2137 foobar = astro
2138 foobar = astro
2138 * = george
2139 * = george
2139 """
2140 """
2140 pushing to ../b
2141 pushing to ../b
2141 query 1; heads
2142 query 1; heads
2142 searching for changes
2143 searching for changes
2143 all remote heads known locally
2144 all remote heads known locally
2144 listing keys for "phases"
2145 listing keys for "phases"
2145 checking for updated bookmarks
2146 checking for updated bookmarks
2146 listing keys for "bookmarks"
2147 listing keys for "bookmarks"
2147 listing keys for "bookmarks"
2148 listing keys for "bookmarks"
2148 4 changesets found
2149 4 changesets found
2149 list of changesets:
2150 list of changesets:
2150 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2151 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2151 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2152 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2152 911600dab2ae7a9baff75958b84fe606851ce955
2153 911600dab2ae7a9baff75958b84fe606851ce955
2153 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2154 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2154 bundle2-output-bundle: "HG20", 5 parts total
2155 bundle2-output-bundle: "HG20", 5 parts total
2155 bundle2-output-part: "replycaps" 207 bytes payload
2156 bundle2-output-part: "replycaps" 207 bytes payload
2156 bundle2-output-part: "check:phases" 48 bytes payload
2157 bundle2-output-part: "check:phases" 48 bytes payload
2157 bundle2-output-part: "check:updated-heads" streamed payload
2158 bundle2-output-part: "check:updated-heads" streamed payload
2158 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2159 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2159 bundle2-output-part: "phase-heads" 48 bytes payload
2160 bundle2-output-part: "phase-heads" 48 bytes payload
2160 bundle2-input-bundle: with-transaction
2161 bundle2-input-bundle: with-transaction
2161 bundle2-input-part: "replycaps" supported
2162 bundle2-input-part: "replycaps" supported
2162 bundle2-input-part: total payload size 207
2163 bundle2-input-part: total payload size 207
2163 bundle2-input-part: "check:phases" supported
2164 bundle2-input-part: "check:phases" supported
2164 bundle2-input-part: total payload size 48
2165 bundle2-input-part: total payload size 48
2165 bundle2-input-part: "check:updated-heads" supported
2166 bundle2-input-part: "check:updated-heads" supported
2166 bundle2-input-part: total payload size 40
2167 bundle2-input-part: total payload size 40
2167 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2168 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2168 adding changesets
2169 adding changesets
2169 add changeset ef1ea85a6374
2170 add changeset ef1ea85a6374
2170 add changeset f9cafe1212c8
2171 add changeset f9cafe1212c8
2171 add changeset 911600dab2ae
2172 add changeset 911600dab2ae
2172 add changeset e8fc755d4d82
2173 add changeset e8fc755d4d82
2173 adding manifests
2174 adding manifests
2174 adding file changes
2175 adding file changes
2175 adding abc.txt revisions
2176 adding abc.txt revisions
2176 adding foo/Bar/file.txt revisions
2177 adding foo/Bar/file.txt revisions
2177 adding foo/file.txt revisions
2178 adding foo/file.txt revisions
2178 adding quux/file.py revisions
2179 adding quux/file.py revisions
2179 calling hook pretxnchangegroup.acl: hgext.acl.hook
2180 calling hook pretxnchangegroup.acl: hgext.acl.hook
2180 acl: checking access for user "george"
2181 acl: checking access for user "george"
2181 acl: acl.allow.branches enabled, 1 entries for user george
2182 acl: acl.allow.branches enabled, 1 entries for user george
2182 acl: acl.deny.branches not enabled
2183 acl: acl.deny.branches not enabled
2183 acl: acl.allow not enabled
2184 acl: acl.allow not enabled
2184 acl: acl.deny not enabled
2185 acl: acl.deny not enabled
2185 acl: branch access granted: "ef1ea85a6374" on branch "default"
2186 acl: branch access granted: "ef1ea85a6374" on branch "default"
2186 acl: path access granted: "ef1ea85a6374"
2187 acl: path access granted: "ef1ea85a6374"
2187 acl: branch access granted: "f9cafe1212c8" on branch "default"
2188 acl: branch access granted: "f9cafe1212c8" on branch "default"
2188 acl: path access granted: "f9cafe1212c8"
2189 acl: path access granted: "f9cafe1212c8"
2189 acl: branch access granted: "911600dab2ae" on branch "default"
2190 acl: branch access granted: "911600dab2ae" on branch "default"
2190 acl: path access granted: "911600dab2ae"
2191 acl: path access granted: "911600dab2ae"
2191 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2192 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2192 acl: path access granted: "e8fc755d4d82"
2193 acl: path access granted: "e8fc755d4d82"
2193 bundle2-input-part: total payload size 2068
2194 bundle2-input-part: total payload size 2068
2194 bundle2-input-part: "phase-heads" supported
2195 bundle2-input-part: "phase-heads" supported
2195 bundle2-input-part: total payload size 48
2196 bundle2-input-part: total payload size 48
2196 bundle2-input-bundle: 5 parts total
2197 bundle2-input-bundle: 5 parts total
2197 updating the branch cache
2198 updating the branch cache
2198 invalid branch cache (served.hidden): tip differs
2199 invalid branch cache (served.hidden): tip differs
2199 added 4 changesets with 4 changes to 4 files (+1 heads)
2200 added 4 changesets with 4 changes to 4 files (+1 heads)
2200 bundle2-output-bundle: "HG20", 1 parts total
2201 bundle2-output-bundle: "HG20", 1 parts total
2201 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2202 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2202 bundle2-input-bundle: no-transaction
2203 bundle2-input-bundle: no-transaction
2203 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2204 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2204 bundle2-input-bundle: 1 parts total
2205 bundle2-input-bundle: 1 parts total
2205 listing keys for "phases"
2206 listing keys for "phases"
2206 repository tip rolled back to revision 2 (undo push)
2207 repository tip rolled back to revision 2 (undo push)
2207 2:fb35475503ef
2208 2:fb35475503ef
2208
2209
2209 Branch acl conflicting deny
2210 Branch acl conflicting deny
2210
2211
2211 $ init_config
2212 $ init_config
2212 $ echo "[acl.deny.branches]" >> $config
2213 $ echo "[acl.deny.branches]" >> $config
2213 $ echo "foobar = astro" >> $config
2214 $ echo "foobar = astro" >> $config
2214 $ echo "default = astro" >> $config
2215 $ echo "default = astro" >> $config
2215 $ echo "* = george" >> $config
2216 $ echo "* = george" >> $config
2216 $ do_push george
2217 $ do_push george
2217 Pushing as user george
2218 Pushing as user george
2218 hgrc = """
2219 hgrc = """
2219 [hooks]
2220 [hooks]
2220 pretxnchangegroup.acl = python:hgext.acl.hook
2221 pretxnchangegroup.acl = python:hgext.acl.hook
2221 prepushkey.acl = python:hgext.acl.hook
2222 prepushkey.acl = python:hgext.acl.hook
2222 [acl]
2223 [acl]
2223 sources = push
2224 sources = push
2224 [extensions]
2225 [extensions]
2225 posixgetuser=$TESTTMP/posixgetuser.py
2226 posixgetuser=$TESTTMP/posixgetuser.py
2226 [acl.deny.branches]
2227 [acl.deny.branches]
2227 foobar = astro
2228 foobar = astro
2228 default = astro
2229 default = astro
2229 * = george
2230 * = george
2230 """
2231 """
2231 pushing to ../b
2232 pushing to ../b
2232 query 1; heads
2233 query 1; heads
2233 searching for changes
2234 searching for changes
2234 all remote heads known locally
2235 all remote heads known locally
2235 listing keys for "phases"
2236 listing keys for "phases"
2236 checking for updated bookmarks
2237 checking for updated bookmarks
2237 listing keys for "bookmarks"
2238 listing keys for "bookmarks"
2238 listing keys for "bookmarks"
2239 listing keys for "bookmarks"
2239 4 changesets found
2240 4 changesets found
2240 list of changesets:
2241 list of changesets:
2241 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2242 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2242 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2243 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2243 911600dab2ae7a9baff75958b84fe606851ce955
2244 911600dab2ae7a9baff75958b84fe606851ce955
2244 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2245 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2245 bundle2-output-bundle: "HG20", 5 parts total
2246 bundle2-output-bundle: "HG20", 5 parts total
2246 bundle2-output-part: "replycaps" 207 bytes payload
2247 bundle2-output-part: "replycaps" 207 bytes payload
2247 bundle2-output-part: "check:phases" 48 bytes payload
2248 bundle2-output-part: "check:phases" 48 bytes payload
2248 bundle2-output-part: "check:updated-heads" streamed payload
2249 bundle2-output-part: "check:updated-heads" streamed payload
2249 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2250 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2250 bundle2-output-part: "phase-heads" 48 bytes payload
2251 bundle2-output-part: "phase-heads" 48 bytes payload
2251 bundle2-input-bundle: with-transaction
2252 bundle2-input-bundle: with-transaction
2252 bundle2-input-part: "replycaps" supported
2253 bundle2-input-part: "replycaps" supported
2253 bundle2-input-part: total payload size 207
2254 bundle2-input-part: total payload size 207
2254 bundle2-input-part: "check:phases" supported
2255 bundle2-input-part: "check:phases" supported
2255 bundle2-input-part: total payload size 48
2256 bundle2-input-part: total payload size 48
2256 bundle2-input-part: "check:updated-heads" supported
2257 bundle2-input-part: "check:updated-heads" supported
2257 bundle2-input-part: total payload size 40
2258 bundle2-input-part: total payload size 40
2258 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2259 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2259 adding changesets
2260 adding changesets
2260 add changeset ef1ea85a6374
2261 add changeset ef1ea85a6374
2261 add changeset f9cafe1212c8
2262 add changeset f9cafe1212c8
2262 add changeset 911600dab2ae
2263 add changeset 911600dab2ae
2263 add changeset e8fc755d4d82
2264 add changeset e8fc755d4d82
2264 adding manifests
2265 adding manifests
2265 adding file changes
2266 adding file changes
2266 adding abc.txt revisions
2267 adding abc.txt revisions
2267 adding foo/Bar/file.txt revisions
2268 adding foo/Bar/file.txt revisions
2268 adding foo/file.txt revisions
2269 adding foo/file.txt revisions
2269 adding quux/file.py revisions
2270 adding quux/file.py revisions
2270 calling hook pretxnchangegroup.acl: hgext.acl.hook
2271 calling hook pretxnchangegroup.acl: hgext.acl.hook
2271 acl: checking access for user "george"
2272 acl: checking access for user "george"
2272 acl: acl.allow.branches not enabled
2273 acl: acl.allow.branches not enabled
2273 acl: acl.deny.branches enabled, 1 entries for user george
2274 acl: acl.deny.branches enabled, 1 entries for user george
2274 acl: acl.allow not enabled
2275 acl: acl.allow not enabled
2275 acl: acl.deny not enabled
2276 acl: acl.deny not enabled
2276 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2277 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2277 bundle2-input-part: total payload size 2068
2278 bundle2-input-part: total payload size 2068
2278 bundle2-input-part: total payload size 48
2279 bundle2-input-part: total payload size 48
2279 bundle2-input-bundle: 5 parts total
2280 bundle2-input-bundle: 5 parts total
2280 transaction abort!
2281 transaction abort!
2281 rollback completed
2282 rollback completed
2282 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2283 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2283 no rollback information available
2284 no rollback information available
2284 2:fb35475503ef
2285 2:fb35475503ef
2285
2286
2286 User 'astro' must not be denied
2287 User 'astro' must not be denied
2287
2288
2288 $ init_config
2289 $ init_config
2289 $ echo "[acl.deny.branches]" >> $config
2290 $ echo "[acl.deny.branches]" >> $config
2290 $ echo "default = !astro" >> $config
2291 $ echo "default = !astro" >> $config
2291 $ do_push astro
2292 $ do_push astro
2292 Pushing as user astro
2293 Pushing as user astro
2293 hgrc = """
2294 hgrc = """
2294 [hooks]
2295 [hooks]
2295 pretxnchangegroup.acl = python:hgext.acl.hook
2296 pretxnchangegroup.acl = python:hgext.acl.hook
2296 prepushkey.acl = python:hgext.acl.hook
2297 prepushkey.acl = python:hgext.acl.hook
2297 [acl]
2298 [acl]
2298 sources = push
2299 sources = push
2299 [extensions]
2300 [extensions]
2300 posixgetuser=$TESTTMP/posixgetuser.py
2301 posixgetuser=$TESTTMP/posixgetuser.py
2301 [acl.deny.branches]
2302 [acl.deny.branches]
2302 default = !astro
2303 default = !astro
2303 """
2304 """
2304 pushing to ../b
2305 pushing to ../b
2305 query 1; heads
2306 query 1; heads
2306 searching for changes
2307 searching for changes
2307 all remote heads known locally
2308 all remote heads known locally
2308 listing keys for "phases"
2309 listing keys for "phases"
2309 checking for updated bookmarks
2310 checking for updated bookmarks
2310 listing keys for "bookmarks"
2311 listing keys for "bookmarks"
2311 listing keys for "bookmarks"
2312 listing keys for "bookmarks"
2312 4 changesets found
2313 4 changesets found
2313 list of changesets:
2314 list of changesets:
2314 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2315 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2315 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2316 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2316 911600dab2ae7a9baff75958b84fe606851ce955
2317 911600dab2ae7a9baff75958b84fe606851ce955
2317 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2318 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2318 bundle2-output-bundle: "HG20", 5 parts total
2319 bundle2-output-bundle: "HG20", 5 parts total
2319 bundle2-output-part: "replycaps" 207 bytes payload
2320 bundle2-output-part: "replycaps" 207 bytes payload
2320 bundle2-output-part: "check:phases" 48 bytes payload
2321 bundle2-output-part: "check:phases" 48 bytes payload
2321 bundle2-output-part: "check:updated-heads" streamed payload
2322 bundle2-output-part: "check:updated-heads" streamed payload
2322 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2323 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2323 bundle2-output-part: "phase-heads" 48 bytes payload
2324 bundle2-output-part: "phase-heads" 48 bytes payload
2324 bundle2-input-bundle: with-transaction
2325 bundle2-input-bundle: with-transaction
2325 bundle2-input-part: "replycaps" supported
2326 bundle2-input-part: "replycaps" supported
2326 bundle2-input-part: total payload size 207
2327 bundle2-input-part: total payload size 207
2327 bundle2-input-part: "check:phases" supported
2328 bundle2-input-part: "check:phases" supported
2328 bundle2-input-part: total payload size 48
2329 bundle2-input-part: total payload size 48
2329 bundle2-input-part: "check:updated-heads" supported
2330 bundle2-input-part: "check:updated-heads" supported
2330 bundle2-input-part: total payload size 40
2331 bundle2-input-part: total payload size 40
2331 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2332 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2332 adding changesets
2333 adding changesets
2333 add changeset ef1ea85a6374
2334 add changeset ef1ea85a6374
2334 add changeset f9cafe1212c8
2335 add changeset f9cafe1212c8
2335 add changeset 911600dab2ae
2336 add changeset 911600dab2ae
2336 add changeset e8fc755d4d82
2337 add changeset e8fc755d4d82
2337 adding manifests
2338 adding manifests
2338 adding file changes
2339 adding file changes
2339 adding abc.txt revisions
2340 adding abc.txt revisions
2340 adding foo/Bar/file.txt revisions
2341 adding foo/Bar/file.txt revisions
2341 adding foo/file.txt revisions
2342 adding foo/file.txt revisions
2342 adding quux/file.py revisions
2343 adding quux/file.py revisions
2343 calling hook pretxnchangegroup.acl: hgext.acl.hook
2344 calling hook pretxnchangegroup.acl: hgext.acl.hook
2344 acl: checking access for user "astro"
2345 acl: checking access for user "astro"
2345 acl: acl.allow.branches not enabled
2346 acl: acl.allow.branches not enabled
2346 acl: acl.deny.branches enabled, 0 entries for user astro
2347 acl: acl.deny.branches enabled, 0 entries for user astro
2347 acl: acl.allow not enabled
2348 acl: acl.allow not enabled
2348 acl: acl.deny not enabled
2349 acl: acl.deny not enabled
2349 acl: branch access granted: "ef1ea85a6374" on branch "default"
2350 acl: branch access granted: "ef1ea85a6374" on branch "default"
2350 acl: path access granted: "ef1ea85a6374"
2351 acl: path access granted: "ef1ea85a6374"
2351 acl: branch access granted: "f9cafe1212c8" on branch "default"
2352 acl: branch access granted: "f9cafe1212c8" on branch "default"
2352 acl: path access granted: "f9cafe1212c8"
2353 acl: path access granted: "f9cafe1212c8"
2353 acl: branch access granted: "911600dab2ae" on branch "default"
2354 acl: branch access granted: "911600dab2ae" on branch "default"
2354 acl: path access granted: "911600dab2ae"
2355 acl: path access granted: "911600dab2ae"
2355 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2356 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2356 acl: path access granted: "e8fc755d4d82"
2357 acl: path access granted: "e8fc755d4d82"
2357 bundle2-input-part: total payload size 2068
2358 bundle2-input-part: total payload size 2068
2358 bundle2-input-part: "phase-heads" supported
2359 bundle2-input-part: "phase-heads" supported
2359 bundle2-input-part: total payload size 48
2360 bundle2-input-part: total payload size 48
2360 bundle2-input-bundle: 5 parts total
2361 bundle2-input-bundle: 5 parts total
2361 updating the branch cache
2362 updating the branch cache
2362 invalid branch cache (served.hidden): tip differs
2363 invalid branch cache (served.hidden): tip differs
2363 added 4 changesets with 4 changes to 4 files (+1 heads)
2364 added 4 changesets with 4 changes to 4 files (+1 heads)
2364 bundle2-output-bundle: "HG20", 1 parts total
2365 bundle2-output-bundle: "HG20", 1 parts total
2365 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2366 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2366 bundle2-input-bundle: no-transaction
2367 bundle2-input-bundle: no-transaction
2367 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2368 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2368 bundle2-input-bundle: 1 parts total
2369 bundle2-input-bundle: 1 parts total
2369 listing keys for "phases"
2370 listing keys for "phases"
2370 repository tip rolled back to revision 2 (undo push)
2371 repository tip rolled back to revision 2 (undo push)
2371 2:fb35475503ef
2372 2:fb35475503ef
2372
2373
2373
2374
2374 Non-astro users must be denied
2375 Non-astro users must be denied
2375
2376
2376 $ do_push george
2377 $ do_push george
2377 Pushing as user george
2378 Pushing as user george
2378 hgrc = """
2379 hgrc = """
2379 [hooks]
2380 [hooks]
2380 pretxnchangegroup.acl = python:hgext.acl.hook
2381 pretxnchangegroup.acl = python:hgext.acl.hook
2381 prepushkey.acl = python:hgext.acl.hook
2382 prepushkey.acl = python:hgext.acl.hook
2382 [acl]
2383 [acl]
2383 sources = push
2384 sources = push
2384 [extensions]
2385 [extensions]
2385 posixgetuser=$TESTTMP/posixgetuser.py
2386 posixgetuser=$TESTTMP/posixgetuser.py
2386 [acl.deny.branches]
2387 [acl.deny.branches]
2387 default = !astro
2388 default = !astro
2388 """
2389 """
2389 pushing to ../b
2390 pushing to ../b
2390 query 1; heads
2391 query 1; heads
2391 searching for changes
2392 searching for changes
2392 all remote heads known locally
2393 all remote heads known locally
2393 listing keys for "phases"
2394 listing keys for "phases"
2394 checking for updated bookmarks
2395 checking for updated bookmarks
2395 listing keys for "bookmarks"
2396 listing keys for "bookmarks"
2396 listing keys for "bookmarks"
2397 listing keys for "bookmarks"
2397 4 changesets found
2398 4 changesets found
2398 list of changesets:
2399 list of changesets:
2399 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2400 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2400 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2401 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2401 911600dab2ae7a9baff75958b84fe606851ce955
2402 911600dab2ae7a9baff75958b84fe606851ce955
2402 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2403 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2403 bundle2-output-bundle: "HG20", 5 parts total
2404 bundle2-output-bundle: "HG20", 5 parts total
2404 bundle2-output-part: "replycaps" 207 bytes payload
2405 bundle2-output-part: "replycaps" 207 bytes payload
2405 bundle2-output-part: "check:phases" 48 bytes payload
2406 bundle2-output-part: "check:phases" 48 bytes payload
2406 bundle2-output-part: "check:updated-heads" streamed payload
2407 bundle2-output-part: "check:updated-heads" streamed payload
2407 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2408 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2408 bundle2-output-part: "phase-heads" 48 bytes payload
2409 bundle2-output-part: "phase-heads" 48 bytes payload
2409 bundle2-input-bundle: with-transaction
2410 bundle2-input-bundle: with-transaction
2410 bundle2-input-part: "replycaps" supported
2411 bundle2-input-part: "replycaps" supported
2411 bundle2-input-part: total payload size 207
2412 bundle2-input-part: total payload size 207
2412 bundle2-input-part: "check:phases" supported
2413 bundle2-input-part: "check:phases" supported
2413 bundle2-input-part: total payload size 48
2414 bundle2-input-part: total payload size 48
2414 bundle2-input-part: "check:updated-heads" supported
2415 bundle2-input-part: "check:updated-heads" supported
2415 bundle2-input-part: total payload size 40
2416 bundle2-input-part: total payload size 40
2416 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2417 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2417 adding changesets
2418 adding changesets
2418 add changeset ef1ea85a6374
2419 add changeset ef1ea85a6374
2419 add changeset f9cafe1212c8
2420 add changeset f9cafe1212c8
2420 add changeset 911600dab2ae
2421 add changeset 911600dab2ae
2421 add changeset e8fc755d4d82
2422 add changeset e8fc755d4d82
2422 adding manifests
2423 adding manifests
2423 adding file changes
2424 adding file changes
2424 adding abc.txt revisions
2425 adding abc.txt revisions
2425 adding foo/Bar/file.txt revisions
2426 adding foo/Bar/file.txt revisions
2426 adding foo/file.txt revisions
2427 adding foo/file.txt revisions
2427 adding quux/file.py revisions
2428 adding quux/file.py revisions
2428 calling hook pretxnchangegroup.acl: hgext.acl.hook
2429 calling hook pretxnchangegroup.acl: hgext.acl.hook
2429 acl: checking access for user "george"
2430 acl: checking access for user "george"
2430 acl: acl.allow.branches not enabled
2431 acl: acl.allow.branches not enabled
2431 acl: acl.deny.branches enabled, 1 entries for user george
2432 acl: acl.deny.branches enabled, 1 entries for user george
2432 acl: acl.allow not enabled
2433 acl: acl.allow not enabled
2433 acl: acl.deny not enabled
2434 acl: acl.deny not enabled
2434 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2435 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2435 bundle2-input-part: total payload size 2068
2436 bundle2-input-part: total payload size 2068
2436 bundle2-input-part: total payload size 48
2437 bundle2-input-part: total payload size 48
2437 bundle2-input-bundle: 5 parts total
2438 bundle2-input-bundle: 5 parts total
2438 transaction abort!
2439 transaction abort!
2439 rollback completed
2440 rollback completed
2440 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2441 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2441 no rollback information available
2442 no rollback information available
2442 2:fb35475503ef
2443 2:fb35475503ef
2443
2444
2444
2445
@@ -1,615 +1,616 b''
1 #require serve
1 #require serve
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo foo>foo
5 $ echo foo>foo
6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
7 $ echo foo>foo.d/foo
7 $ echo foo>foo.d/foo
8 $ echo bar>foo.d/bAr.hg.d/BaR
8 $ echo bar>foo.d/bAr.hg.d/BaR
9 $ echo bar>foo.d/baR.d.hg/bAR
9 $ echo bar>foo.d/baR.d.hg/bAR
10 $ hg commit -A -m 1
10 $ hg commit -A -m 1
11 adding foo
11 adding foo
12 adding foo.d/bAr.hg.d/BaR
12 adding foo.d/bAr.hg.d/BaR
13 adding foo.d/baR.d.hg/bAR
13 adding foo.d/baR.d.hg/bAR
14 adding foo.d/foo
14 adding foo.d/foo
15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
17
17
18 Test server address cannot be reused
18 Test server address cannot be reused
19
19
20 $ hg serve -p $HGPORT1 2>&1
20 $ hg serve -p $HGPORT1 2>&1
21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
22 [255]
22 [255]
23
23
24 $ cd ..
24 $ cd ..
25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
26
26
27 clone via stream
27 clone via stream
28
28
29 #if no-reposimplestore
29 #if no-reposimplestore
30 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
30 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
31 streaming all changes
31 streaming all changes
32 9 files to transfer, 715 bytes of data
32 9 files to transfer, 715 bytes of data
33 transferred * bytes in * seconds (*/sec) (glob)
33 transferred * bytes in * seconds (*/sec) (glob)
34 updating to branch default
34 updating to branch default
35 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 $ hg verify -R copy
36 $ hg verify -R copy
37 checking changesets
37 checking changesets
38 checking manifests
38 checking manifests
39 crosschecking files in changesets and manifests
39 crosschecking files in changesets and manifests
40 checking files
40 checking files
41 checked 1 changesets with 4 changes to 4 files
41 checked 1 changesets with 4 changes to 4 files
42 #endif
42 #endif
43
43
44 try to clone via stream, should use pull instead
44 try to clone via stream, should use pull instead
45
45
46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
47 warning: stream clone requested but server has them disabled
47 warning: stream clone requested but server has them disabled
48 requesting all changes
48 requesting all changes
49 adding changesets
49 adding changesets
50 adding manifests
50 adding manifests
51 adding file changes
51 adding file changes
52 added 1 changesets with 4 changes to 4 files
52 added 1 changesets with 4 changes to 4 files
53 new changesets 8b6053c928fe
53 new changesets 8b6053c928fe
54 updating to branch default
54 updating to branch default
55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
56
56
57 try to clone via stream but missing requirements, so should use pull instead
57 try to clone via stream but missing requirements, so should use pull instead
58
58
59 $ cat > $TESTTMP/removesupportedformat.py << EOF
59 $ cat > $TESTTMP/removesupportedformat.py << EOF
60 > from mercurial import localrepo
60 > from mercurial import localrepo
61 > def extsetup(ui):
61 > def extsetup(ui):
62 > localrepo.localrepository.supportedformats.remove(b'generaldelta')
62 > localrepo.localrepository.supportedformats.remove(b'generaldelta')
63 > EOF
63 > EOF
64
64
65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
66 warning: stream clone requested but client is missing requirements: generaldelta
66 warning: stream clone requested but client is missing requirements: generaldelta
67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
68 requesting all changes
68 requesting all changes
69 adding changesets
69 adding changesets
70 adding manifests
70 adding manifests
71 adding file changes
71 adding file changes
72 added 1 changesets with 4 changes to 4 files
72 added 1 changesets with 4 changes to 4 files
73 new changesets 8b6053c928fe
73 new changesets 8b6053c928fe
74 updating to branch default
74 updating to branch default
75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
76
76
77 clone via pull
77 clone via pull
78
78
79 $ hg clone http://localhost:$HGPORT1/ copy-pull
79 $ hg clone http://localhost:$HGPORT1/ copy-pull
80 requesting all changes
80 requesting all changes
81 adding changesets
81 adding changesets
82 adding manifests
82 adding manifests
83 adding file changes
83 adding file changes
84 added 1 changesets with 4 changes to 4 files
84 added 1 changesets with 4 changes to 4 files
85 new changesets 8b6053c928fe
85 new changesets 8b6053c928fe
86 updating to branch default
86 updating to branch default
87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 $ hg verify -R copy-pull
88 $ hg verify -R copy-pull
89 checking changesets
89 checking changesets
90 checking manifests
90 checking manifests
91 crosschecking files in changesets and manifests
91 crosschecking files in changesets and manifests
92 checking files
92 checking files
93 checked 1 changesets with 4 changes to 4 files
93 checked 1 changesets with 4 changes to 4 files
94 $ cd test
94 $ cd test
95 $ echo bar > bar
95 $ echo bar > bar
96 $ hg commit -A -d '1 0' -m 2
96 $ hg commit -A -d '1 0' -m 2
97 adding bar
97 adding bar
98 $ cd ..
98 $ cd ..
99
99
100 clone over http with --update
100 clone over http with --update
101
101
102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
103 requesting all changes
103 requesting all changes
104 adding changesets
104 adding changesets
105 adding manifests
105 adding manifests
106 adding file changes
106 adding file changes
107 added 2 changesets with 5 changes to 5 files
107 added 2 changesets with 5 changes to 5 files
108 new changesets 8b6053c928fe:5fed3813f7f5
108 new changesets 8b6053c928fe:5fed3813f7f5
109 updating to branch default
109 updating to branch default
110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 $ hg log -r . -R updated
111 $ hg log -r . -R updated
112 changeset: 0:8b6053c928fe
112 changeset: 0:8b6053c928fe
113 user: test
113 user: test
114 date: Thu Jan 01 00:00:00 1970 +0000
114 date: Thu Jan 01 00:00:00 1970 +0000
115 summary: 1
115 summary: 1
116
116
117 $ rm -rf updated
117 $ rm -rf updated
118
118
119 incoming via HTTP
119 incoming via HTTP
120
120
121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
122 adding changesets
122 adding changesets
123 adding manifests
123 adding manifests
124 adding file changes
124 adding file changes
125 added 1 changesets with 4 changes to 4 files
125 added 1 changesets with 4 changes to 4 files
126 new changesets 8b6053c928fe
126 new changesets 8b6053c928fe
127 updating to branch default
127 updating to branch default
128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 $ cd partial
129 $ cd partial
130 $ touch LOCAL
130 $ touch LOCAL
131 $ hg ci -qAm LOCAL
131 $ hg ci -qAm LOCAL
132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
133 comparing with http://localhost:$HGPORT1/
133 comparing with http://localhost:$HGPORT1/
134 searching for changes
134 searching for changes
135 2
135 2
136 $ cd ..
136 $ cd ..
137
137
138 pull
138 pull
139
139
140 $ cd copy-pull
140 $ cd copy-pull
141 $ cat >> .hg/hgrc <<EOF
141 $ cat >> .hg/hgrc <<EOF
142 > [hooks]
142 > [hooks]
143 > changegroup = sh -c "printenv.py --line changegroup"
143 > changegroup = sh -c "printenv.py --line changegroup"
144 > EOF
144 > EOF
145 $ hg pull
145 $ hg pull
146 pulling from http://localhost:$HGPORT1/
146 pulling from http://localhost:$HGPORT1/
147 searching for changes
147 searching for changes
148 adding changesets
148 adding changesets
149 adding manifests
149 adding manifests
150 adding file changes
150 adding file changes
151 added 1 changesets with 1 changes to 1 files
151 added 1 changesets with 1 changes to 1 files
152 new changesets 5fed3813f7f5
152 new changesets 5fed3813f7f5
153 changegroup hook: HG_HOOKNAME=changegroup
153 changegroup hook: HG_HOOKNAME=changegroup
154 HG_HOOKTYPE=changegroup
154 HG_HOOKTYPE=changegroup
155 HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
155 HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
156 HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
156 HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
157 HG_SOURCE=pull
157 HG_SOURCE=pull
158 HG_TXNID=TXN:$ID$
158 HG_TXNID=TXN:$ID$
159 HG_TXNNAME=pull
159 HG_TXNNAME=pull
160 http://localhost:$HGPORT1/
160 http://localhost:$HGPORT1/
161 HG_URL=http://localhost:$HGPORT1/
161 HG_URL=http://localhost:$HGPORT1/
162
162
163 (run 'hg update' to get a working copy)
163 (run 'hg update' to get a working copy)
164 $ cd ..
164 $ cd ..
165
165
166 clone from invalid URL
166 clone from invalid URL
167
167
168 $ hg clone http://localhost:$HGPORT/bad
168 $ hg clone http://localhost:$HGPORT/bad
169 abort: HTTP Error 404: Not Found
169 abort: HTTP Error 404: Not Found
170 [100]
170 [100]
171
171
172 test http authentication
172 test http authentication
173 + use the same server to test server side streaming preference
173 + use the same server to test server side streaming preference
174
174
175 $ cd test
175 $ cd test
176
176
177 $ hg serve --config extensions.x=$TESTDIR/httpserverauth.py -p $HGPORT2 -d \
177 $ hg serve --config extensions.x=$TESTDIR/httpserverauth.py -p $HGPORT2 -d \
178 > --pid-file=pid --config server.preferuncompressed=True -E ../errors2.log \
178 > --pid-file=pid --config server.preferuncompressed=True -E ../errors2.log \
179 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
179 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
180 $ cat pid >> $DAEMON_PIDS
180 $ cat pid >> $DAEMON_PIDS
181
181
182 $ cat << EOF > get_pass.py
182 $ cat << EOF > get_pass.py
183 > import getpass
183 > import getpass
184 > def newgetpass(arg):
184 > def newgetpass(arg):
185 > return "pass"
185 > return "pass"
186 > getpass.getpass = newgetpass
186 > getpass.getpass = newgetpass
187 > EOF
187 > EOF
188
188
189 $ hg id http://localhost:$HGPORT2/
189 $ hg id http://localhost:$HGPORT2/
190 abort: http authorization required for http://localhost:$HGPORT2/
190 abort: http authorization required for http://localhost:$HGPORT2/
191 [255]
191 [255]
192 $ hg id http://localhost:$HGPORT2/
192 $ hg id http://localhost:$HGPORT2/
193 abort: http authorization required for http://localhost:$HGPORT2/
193 abort: http authorization required for http://localhost:$HGPORT2/
194 [255]
194 [255]
195 $ hg id --config ui.interactive=true --debug http://localhost:$HGPORT2/
195 $ hg id --config ui.interactive=true --debug http://localhost:$HGPORT2/
196 using http://localhost:$HGPORT2/
196 using http://localhost:$HGPORT2/
197 sending capabilities command
197 sending capabilities command
198 http authorization required for http://localhost:$HGPORT2/
198 http authorization required for http://localhost:$HGPORT2/
199 realm: mercurial
199 realm: mercurial
200 user: abort: response expected
200 user: abort: response expected
201 [255]
201 [255]
202 $ cat <<'EOF' | hg id --config ui.interactive=true --config ui.nontty=true --debug http://localhost:$HGPORT2/
202 $ cat <<'EOF' | hg id --config ui.interactive=true --config ui.nontty=true --debug http://localhost:$HGPORT2/
203 >
203 >
204 > EOF
204 > EOF
205 using http://localhost:$HGPORT2/
205 using http://localhost:$HGPORT2/
206 sending capabilities command
206 sending capabilities command
207 http authorization required for http://localhost:$HGPORT2/
207 http authorization required for http://localhost:$HGPORT2/
208 realm: mercurial
208 realm: mercurial
209 user:
209 user:
210 password: abort: response expected
210 password: abort: response expected
211 [255]
211 [255]
212 $ cat <<'EOF' | hg id --config ui.interactive=true --config ui.nontty=true --debug http://localhost:$HGPORT2/
212 $ cat <<'EOF' | hg id --config ui.interactive=true --config ui.nontty=true --debug http://localhost:$HGPORT2/
213 >
213 >
214 >
214 >
215 > EOF
215 > EOF
216 using http://localhost:$HGPORT2/
216 using http://localhost:$HGPORT2/
217 sending capabilities command
217 sending capabilities command
218 http authorization required for http://localhost:$HGPORT2/
218 http authorization required for http://localhost:$HGPORT2/
219 realm: mercurial
219 realm: mercurial
220 user:
220 user:
221 password: abort: authorization failed
221 password: abort: authorization failed
222 [255]
222 [255]
223 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
223 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
224 http authorization required for http://localhost:$HGPORT2/
224 http authorization required for http://localhost:$HGPORT2/
225 realm: mercurial
225 realm: mercurial
226 user: user
226 user: user
227 password: 5fed3813f7f5
227 password: 5fed3813f7f5
228 $ hg id http://user:pass@localhost:$HGPORT2/
228 $ hg id http://user:pass@localhost:$HGPORT2/
229 5fed3813f7f5
229 5fed3813f7f5
230 $ echo '[auth]' >> .hg/hgrc
230 $ echo '[auth]' >> .hg/hgrc
231 $ echo 'l.schemes=http' >> .hg/hgrc
231 $ echo 'l.schemes=http' >> .hg/hgrc
232 $ echo 'l.prefix=lo' >> .hg/hgrc
232 $ echo 'l.prefix=lo' >> .hg/hgrc
233 $ echo 'l.username=user' >> .hg/hgrc
233 $ echo 'l.username=user' >> .hg/hgrc
234 $ echo 'l.password=pass' >> .hg/hgrc
234 $ echo 'l.password=pass' >> .hg/hgrc
235 $ hg id http://localhost:$HGPORT2/
235 $ hg id http://localhost:$HGPORT2/
236 5fed3813f7f5
236 5fed3813f7f5
237 $ hg id http://localhost:$HGPORT2/
237 $ hg id http://localhost:$HGPORT2/
238 5fed3813f7f5
238 5fed3813f7f5
239 $ hg id http://user@localhost:$HGPORT2/
239 $ hg id http://user@localhost:$HGPORT2/
240 5fed3813f7f5
240 5fed3813f7f5
241
241
242 $ cat > use_digests.py << EOF
242 $ cat > use_digests.py << EOF
243 > from mercurial import (
243 > from mercurial import (
244 > exthelper,
244 > exthelper,
245 > url,
245 > url,
246 > )
246 > )
247 >
247 >
248 > eh = exthelper.exthelper()
248 > eh = exthelper.exthelper()
249 > uisetup = eh.finaluisetup
249 > uisetup = eh.finaluisetup
250 >
250 >
251 > @eh.wrapfunction(url, 'opener')
251 > @eh.wrapfunction(url, 'opener')
252 > def urlopener(orig, *args, **kwargs):
252 > def urlopener(orig, *args, **kwargs):
253 > opener = orig(*args, **kwargs)
253 > opener = orig(*args, **kwargs)
254 > opener.addheaders.append((r'X-HgTest-AuthType', r'Digest'))
254 > opener.addheaders.append((r'X-HgTest-AuthType', r'Digest'))
255 > return opener
255 > return opener
256 > EOF
256 > EOF
257
257
258 $ hg id http://localhost:$HGPORT2/ --config extensions.x=use_digests.py
258 $ hg id http://localhost:$HGPORT2/ --config extensions.x=use_digests.py
259 5fed3813f7f5
259 5fed3813f7f5
260
260
261 #if no-reposimplestore
261 #if no-reposimplestore
262 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
262 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
263 streaming all changes
263 streaming all changes
264 10 files to transfer, 1.01 KB of data
264 10 files to transfer, 1.01 KB of data
265 transferred * KB in * seconds (*/sec) (glob)
265 transferred * KB in * seconds (*/sec) (glob)
266 updating to branch default
266 updating to branch default
267 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
268 #endif
268 #endif
269
269
270 --pull should override server's preferuncompressed
270 --pull should override server's preferuncompressed
271 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
271 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
272 requesting all changes
272 requesting all changes
273 adding changesets
273 adding changesets
274 adding manifests
274 adding manifests
275 adding file changes
275 adding file changes
276 added 2 changesets with 5 changes to 5 files
276 added 2 changesets with 5 changes to 5 files
277 new changesets 8b6053c928fe:5fed3813f7f5
277 new changesets 8b6053c928fe:5fed3813f7f5
278 updating to branch default
278 updating to branch default
279 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
279 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
280
280
281 $ hg id http://user2@localhost:$HGPORT2/
281 $ hg id http://user2@localhost:$HGPORT2/
282 abort: http authorization required for http://localhost:$HGPORT2/
282 abort: http authorization required for http://localhost:$HGPORT2/
283 [255]
283 [255]
284 $ hg id http://user:pass2@localhost:$HGPORT2/
284 $ hg id http://user:pass2@localhost:$HGPORT2/
285 abort: HTTP Error 403: no
285 abort: HTTP Error 403: no
286 [100]
286 [100]
287
287
288 $ hg -R dest-pull tag -r tip top
288 $ hg -R dest-pull tag -r tip top
289 $ hg -R dest-pull push http://user:pass@localhost:$HGPORT2/
289 $ hg -R dest-pull push http://user:pass@localhost:$HGPORT2/
290 pushing to http://user:***@localhost:$HGPORT2/
290 pushing to http://user:***@localhost:$HGPORT2/
291 searching for changes
291 searching for changes
292 remote: adding changesets
292 remote: adding changesets
293 remote: adding manifests
293 remote: adding manifests
294 remote: adding file changes
294 remote: adding file changes
295 remote: added 1 changesets with 1 changes to 1 files
295 remote: added 1 changesets with 1 changes to 1 files
296 $ hg rollback -q
296 $ hg rollback -q
297 $ hg -R dest-pull push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
297 $ hg -R dest-pull push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
298 pushing to http://user:***@localhost:$HGPORT2/
298 pushing to http://user:***@localhost:$HGPORT2/
299 using http://localhost:$HGPORT2/
299 using http://localhost:$HGPORT2/
300 http auth: user user, password ****
300 http auth: user user, password ****
301 sending capabilities command
301 sending capabilities command
302 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
302 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
303 http auth: user user, password ****
303 http auth: user user, password ****
304 devel-peer-request: finished in *.???? seconds (200) (glob)
304 devel-peer-request: finished in *.???? seconds (200) (glob)
305 query 1; heads
305 query 1; heads
306 devel-peer-request: batched-content
306 devel-peer-request: batched-content
307 devel-peer-request: - heads (0 arguments)
307 devel-peer-request: - heads (0 arguments)
308 devel-peer-request: - known (1 arguments)
308 devel-peer-request: - known (1 arguments)
309 sending batch command
309 sending batch command
310 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
310 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
311 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
311 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
312 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
312 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
313 devel-peer-request: 68 bytes of commands arguments in headers
313 devel-peer-request: 68 bytes of commands arguments in headers
314 devel-peer-request: finished in *.???? seconds (200) (glob)
314 devel-peer-request: finished in *.???? seconds (200) (glob)
315 searching for changes
315 searching for changes
316 all remote heads known locally
316 all remote heads known locally
317 preparing listkeys for "phases"
317 preparing listkeys for "phases"
318 sending listkeys command
318 sending listkeys command
319 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
319 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
320 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
320 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
321 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
321 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
322 devel-peer-request: 16 bytes of commands arguments in headers
322 devel-peer-request: 16 bytes of commands arguments in headers
323 devel-peer-request: finished in *.???? seconds (200) (glob)
323 devel-peer-request: finished in *.???? seconds (200) (glob)
324 received listkey for "phases": 58 bytes
324 received listkey for "phases": 58 bytes
325 checking for updated bookmarks
325 checking for updated bookmarks
326 preparing listkeys for "bookmarks"
326 preparing listkeys for "bookmarks"
327 sending listkeys command
327 sending listkeys command
328 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
328 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
329 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
329 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
330 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
330 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
331 devel-peer-request: 19 bytes of commands arguments in headers
331 devel-peer-request: 19 bytes of commands arguments in headers
332 devel-peer-request: finished in *.???? seconds (200) (glob)
332 devel-peer-request: finished in *.???? seconds (200) (glob)
333 received listkey for "bookmarks": 0 bytes
333 received listkey for "bookmarks": 0 bytes
334 sending branchmap command
334 sending branchmap command
335 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
335 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
336 devel-peer-request: Vary X-HgProto-1
336 devel-peer-request: Vary X-HgProto-1
337 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
337 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
338 devel-peer-request: finished in *.???? seconds (200) (glob)
338 devel-peer-request: finished in *.???? seconds (200) (glob)
339 preparing listkeys for "bookmarks"
339 preparing listkeys for "bookmarks"
340 sending listkeys command
340 sending listkeys command
341 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
341 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
342 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
342 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
343 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
343 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
344 devel-peer-request: 19 bytes of commands arguments in headers
344 devel-peer-request: 19 bytes of commands arguments in headers
345 devel-peer-request: finished in *.???? seconds (200) (glob)
345 devel-peer-request: finished in *.???? seconds (200) (glob)
346 received listkey for "bookmarks": 0 bytes
346 received listkey for "bookmarks": 0 bytes
347 1 changesets found
347 1 changesets found
348 list of changesets:
348 list of changesets:
349 7f4e523d01f2cc3765ac8934da3d14db775ff872
349 7f4e523d01f2cc3765ac8934da3d14db775ff872
350 bundle2-output-bundle: "HG20", 5 parts total
350 bundle2-output-bundle: "HG20", 5 parts total
351 bundle2-output-part: "replycaps" 207 bytes payload
351 bundle2-output-part: "replycaps" 207 bytes payload
352 bundle2-output-part: "check:phases" 24 bytes payload
352 bundle2-output-part: "check:phases" 24 bytes payload
353 bundle2-output-part: "check:updated-heads" streamed payload
353 bundle2-output-part: "check:updated-heads" streamed payload
354 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
354 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
355 bundle2-output-part: "phase-heads" 24 bytes payload
355 bundle2-output-part: "phase-heads" 24 bytes payload
356 sending unbundle command
356 sending unbundle command
357 sending 1023 bytes
357 sending 1023 bytes
358 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
358 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
359 devel-peer-request: Content-length 1023
359 devel-peer-request: Content-length 1023
360 devel-peer-request: Content-type application/mercurial-0.1
360 devel-peer-request: Content-type application/mercurial-0.1
361 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
361 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
362 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
362 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
363 devel-peer-request: 16 bytes of commands arguments in headers
363 devel-peer-request: 16 bytes of commands arguments in headers
364 devel-peer-request: 1023 bytes of data
364 devel-peer-request: 1023 bytes of data
365 devel-peer-request: finished in *.???? seconds (200) (glob)
365 devel-peer-request: finished in *.???? seconds (200) (glob)
366 bundle2-input-bundle: no-transaction
366 bundle2-input-bundle: no-transaction
367 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
367 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
368 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
368 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
369 bundle2-input-part: total payload size 55
369 bundle2-input-part: total payload size 55
370 remote: adding changesets
370 remote: adding changesets
371 remote: adding manifests
371 remote: adding manifests
372 remote: adding file changes
372 remote: adding file changes
373 bundle2-input-part: "output" (advisory) supported
373 bundle2-input-part: "output" (advisory) supported
374 bundle2-input-part: total payload size 45
374 bundle2-input-part: total payload size 45
375 remote: added 1 changesets with 1 changes to 1 files
375 remote: added 1 changesets with 1 changes to 1 files
376 bundle2-input-bundle: 3 parts total
376 bundle2-input-bundle: 3 parts total
377 preparing listkeys for "phases"
377 preparing listkeys for "phases"
378 sending listkeys command
378 sending listkeys command
379 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
379 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
380 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
380 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
381 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
381 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
382 devel-peer-request: 16 bytes of commands arguments in headers
382 devel-peer-request: 16 bytes of commands arguments in headers
383 devel-peer-request: finished in *.???? seconds (200) (glob)
383 devel-peer-request: finished in *.???? seconds (200) (glob)
384 received listkey for "phases": 15 bytes
384 received listkey for "phases": 15 bytes
385 (sent 9 HTTP requests and 3898 bytes; received 920 bytes in responses)
385 $ hg rollback -q
386 $ hg rollback -q
386
387
387 $ sed 's/.*] "/"/' < ../access.log
388 $ sed 's/.*] "/"/' < ../access.log
388 "GET /?cmd=capabilities HTTP/1.1" 401 -
389 "GET /?cmd=capabilities HTTP/1.1" 401 -
389 "GET /?cmd=capabilities HTTP/1.1" 401 -
390 "GET /?cmd=capabilities HTTP/1.1" 401 -
390 "GET /?cmd=capabilities HTTP/1.1" 401 -
391 "GET /?cmd=capabilities HTTP/1.1" 401 -
391 "GET /?cmd=capabilities HTTP/1.1" 401 -
392 "GET /?cmd=capabilities HTTP/1.1" 401 -
392 "GET /?cmd=capabilities HTTP/1.1" 401 -
393 "GET /?cmd=capabilities HTTP/1.1" 401 -
393 "GET /?cmd=capabilities HTTP/1.1" 401 -
394 "GET /?cmd=capabilities HTTP/1.1" 401 -
394 "GET /?cmd=capabilities HTTP/1.1" 200 -
395 "GET /?cmd=capabilities HTTP/1.1" 200 -
395 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
396 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
396 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
397 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
397 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
398 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
398 "GET /?cmd=capabilities HTTP/1.1" 401 -
399 "GET /?cmd=capabilities HTTP/1.1" 401 -
399 "GET /?cmd=capabilities HTTP/1.1" 200 -
400 "GET /?cmd=capabilities HTTP/1.1" 200 -
400 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
401 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
401 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
402 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
402 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
403 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
403 "GET /?cmd=capabilities HTTP/1.1" 401 -
404 "GET /?cmd=capabilities HTTP/1.1" 401 -
404 "GET /?cmd=capabilities HTTP/1.1" 200 -
405 "GET /?cmd=capabilities HTTP/1.1" 200 -
405 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
406 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
406 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
407 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
407 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
408 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
408 "GET /?cmd=capabilities HTTP/1.1" 401 -
409 "GET /?cmd=capabilities HTTP/1.1" 401 -
409 "GET /?cmd=capabilities HTTP/1.1" 200 -
410 "GET /?cmd=capabilities HTTP/1.1" 200 -
410 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
411 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
411 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
412 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
412 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
413 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
413 "GET /?cmd=capabilities HTTP/1.1" 401 -
414 "GET /?cmd=capabilities HTTP/1.1" 401 -
414 "GET /?cmd=capabilities HTTP/1.1" 200 -
415 "GET /?cmd=capabilities HTTP/1.1" 200 -
415 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
416 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
416 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
417 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
417 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
418 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
418 "GET /?cmd=capabilities HTTP/1.1" 401 - x-hgtest-authtype:Digest
419 "GET /?cmd=capabilities HTTP/1.1" 401 - x-hgtest-authtype:Digest
419 "GET /?cmd=capabilities HTTP/1.1" 200 - x-hgtest-authtype:Digest
420 "GET /?cmd=capabilities HTTP/1.1" 200 - x-hgtest-authtype:Digest
420 "GET /?cmd=lookup HTTP/1.1" 401 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
421 "GET /?cmd=lookup HTTP/1.1" 401 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
421 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
422 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
422 "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
423 "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
423 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
424 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
424 "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
425 "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
425 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
426 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
426 "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
427 "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
427 "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
428 "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
428 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
429 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
429 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&stream=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
430 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&stream=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
430 "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
431 "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
431 "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
432 "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
432 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
433 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
433 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
434 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
434 "GET /?cmd=capabilities HTTP/1.1" 401 -
435 "GET /?cmd=capabilities HTTP/1.1" 401 -
435 "GET /?cmd=capabilities HTTP/1.1" 401 -
436 "GET /?cmd=capabilities HTTP/1.1" 401 -
436 "GET /?cmd=capabilities HTTP/1.1" 403 -
437 "GET /?cmd=capabilities HTTP/1.1" 403 -
437 "GET /?cmd=capabilities HTTP/1.1" 401 -
438 "GET /?cmd=capabilities HTTP/1.1" 401 -
438 "GET /?cmd=capabilities HTTP/1.1" 200 -
439 "GET /?cmd=capabilities HTTP/1.1" 200 -
439 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
440 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
440 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
441 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
441 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
442 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
442 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
443 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
443 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
444 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
444 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
445 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
445 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
446 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
446 "GET /?cmd=capabilities HTTP/1.1" 401 -
447 "GET /?cmd=capabilities HTTP/1.1" 401 -
447 "GET /?cmd=capabilities HTTP/1.1" 200 -
448 "GET /?cmd=capabilities HTTP/1.1" 200 -
448 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
449 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
449 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
450 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
450 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
451 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
451 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
452 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
452 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
453 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
453 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
454 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
454 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
455 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
455
456
456 $ cd ..
457 $ cd ..
457
458
458 clone of serve with repo in root and unserved subrepo (issue2970)
459 clone of serve with repo in root and unserved subrepo (issue2970)
459
460
460 $ hg --cwd test init sub
461 $ hg --cwd test init sub
461 $ echo empty > test/sub/empty
462 $ echo empty > test/sub/empty
462 $ hg --cwd test/sub add empty
463 $ hg --cwd test/sub add empty
463 $ hg --cwd test/sub commit -qm 'add empty'
464 $ hg --cwd test/sub commit -qm 'add empty'
464 $ hg --cwd test/sub tag -r 0 something
465 $ hg --cwd test/sub tag -r 0 something
465 $ echo sub = sub > test/.hgsub
466 $ echo sub = sub > test/.hgsub
466 $ hg --cwd test add .hgsub
467 $ hg --cwd test add .hgsub
467 $ hg --cwd test commit -qm 'add subrepo'
468 $ hg --cwd test commit -qm 'add subrepo'
468 $ hg clone http://localhost:$HGPORT noslash-clone
469 $ hg clone http://localhost:$HGPORT noslash-clone
469 requesting all changes
470 requesting all changes
470 adding changesets
471 adding changesets
471 adding manifests
472 adding manifests
472 adding file changes
473 adding file changes
473 added 3 changesets with 7 changes to 7 files
474 added 3 changesets with 7 changes to 7 files
474 new changesets 8b6053c928fe:56f9bc90cce6
475 new changesets 8b6053c928fe:56f9bc90cce6
475 updating to branch default
476 updating to branch default
476 cloning subrepo sub from http://localhost:$HGPORT/sub
477 cloning subrepo sub from http://localhost:$HGPORT/sub
477 abort: HTTP Error 404: Not Found
478 abort: HTTP Error 404: Not Found
478 [100]
479 [100]
479 $ hg clone http://localhost:$HGPORT/ slash-clone
480 $ hg clone http://localhost:$HGPORT/ slash-clone
480 requesting all changes
481 requesting all changes
481 adding changesets
482 adding changesets
482 adding manifests
483 adding manifests
483 adding file changes
484 adding file changes
484 added 3 changesets with 7 changes to 7 files
485 added 3 changesets with 7 changes to 7 files
485 new changesets 8b6053c928fe:56f9bc90cce6
486 new changesets 8b6053c928fe:56f9bc90cce6
486 updating to branch default
487 updating to branch default
487 cloning subrepo sub from http://localhost:$HGPORT/sub
488 cloning subrepo sub from http://localhost:$HGPORT/sub
488 abort: HTTP Error 404: Not Found
489 abort: HTTP Error 404: Not Found
489 [100]
490 [100]
490
491
491 check error log
492 check error log
492
493
493 $ cat error.log
494 $ cat error.log
494
495
495 $ cat errors2.log
496 $ cat errors2.log
496
497
497 check abort error reporting while pulling/cloning
498 check abort error reporting while pulling/cloning
498
499
499 $ $RUNTESTDIR/killdaemons.py
500 $ $RUNTESTDIR/killdaemons.py
500 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
501 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
501 $ cat hg3.pid >> $DAEMON_PIDS
502 $ cat hg3.pid >> $DAEMON_PIDS
502 $ hg clone http://localhost:$HGPORT/ abort-clone
503 $ hg clone http://localhost:$HGPORT/ abort-clone
503 requesting all changes
504 requesting all changes
504 remote: abort: this is an exercise
505 remote: abort: this is an exercise
505 abort: pull failed on remote
506 abort: pull failed on remote
506 [255]
507 [255]
507 $ cat error.log
508 $ cat error.log
508
509
509 disable pull-based clones
510 disable pull-based clones
510
511
511 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
512 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
512 $ cat hg4.pid >> $DAEMON_PIDS
513 $ cat hg4.pid >> $DAEMON_PIDS
513 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
514 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
514 requesting all changes
515 requesting all changes
515 remote: abort: server has pull-based clones disabled
516 remote: abort: server has pull-based clones disabled
516 abort: pull failed on remote
517 abort: pull failed on remote
517 (remove --pull if specified or upgrade Mercurial)
518 (remove --pull if specified or upgrade Mercurial)
518 [255]
519 [255]
519
520
520 #if no-reposimplestore
521 #if no-reposimplestore
521 ... but keep stream clones working
522 ... but keep stream clones working
522
523
523 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
524 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
524 streaming all changes
525 streaming all changes
525 * files to transfer, * of data (glob)
526 * files to transfer, * of data (glob)
526 transferred * in * seconds (*/sec) (glob)
527 transferred * in * seconds (*/sec) (glob)
527 $ cat error.log
528 $ cat error.log
528 #endif
529 #endif
529
530
530 ... and also keep partial clones and pulls working
531 ... and also keep partial clones and pulls working
531 $ hg clone http://localhost:$HGPORT1 --rev 0 test/partial/clone
532 $ hg clone http://localhost:$HGPORT1 --rev 0 test/partial/clone
532 adding changesets
533 adding changesets
533 adding manifests
534 adding manifests
534 adding file changes
535 adding file changes
535 added 1 changesets with 4 changes to 4 files
536 added 1 changesets with 4 changes to 4 files
536 new changesets 8b6053c928fe
537 new changesets 8b6053c928fe
537 updating to branch default
538 updating to branch default
538 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
539 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
539 $ hg pull -R test/partial/clone
540 $ hg pull -R test/partial/clone
540 pulling from http://localhost:$HGPORT1/
541 pulling from http://localhost:$HGPORT1/
541 searching for changes
542 searching for changes
542 adding changesets
543 adding changesets
543 adding manifests
544 adding manifests
544 adding file changes
545 adding file changes
545 added 2 changesets with 3 changes to 3 files
546 added 2 changesets with 3 changes to 3 files
546 new changesets 5fed3813f7f5:56f9bc90cce6
547 new changesets 5fed3813f7f5:56f9bc90cce6
547 (run 'hg update' to get a working copy)
548 (run 'hg update' to get a working copy)
548
549
549 $ hg clone -U -r 0 test/partial/clone test/another/clone
550 $ hg clone -U -r 0 test/partial/clone test/another/clone
550 adding changesets
551 adding changesets
551 adding manifests
552 adding manifests
552 adding file changes
553 adding file changes
553 added 1 changesets with 4 changes to 4 files
554 added 1 changesets with 4 changes to 4 files
554 new changesets 8b6053c928fe
555 new changesets 8b6053c928fe
555
556
556 corrupt cookies file should yield a warning
557 corrupt cookies file should yield a warning
557
558
558 $ cat > $TESTTMP/cookies.txt << EOF
559 $ cat > $TESTTMP/cookies.txt << EOF
559 > bad format
560 > bad format
560 > EOF
561 > EOF
561
562
562 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
563 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
563 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
564 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
564 56f9bc90cce6
565 56f9bc90cce6
565
566
566 $ killdaemons.py
567 $ killdaemons.py
567
568
568 Create dummy authentication handler that looks for cookies. It doesn't do anything
569 Create dummy authentication handler that looks for cookies. It doesn't do anything
569 useful. It just raises an HTTP 500 with details about the Cookie request header.
570 useful. It just raises an HTTP 500 with details about the Cookie request header.
570 We raise HTTP 500 because its message is printed in the abort message.
571 We raise HTTP 500 because its message is printed in the abort message.
571
572
572 $ cat > cookieauth.py << EOF
573 $ cat > cookieauth.py << EOF
573 > from mercurial import util
574 > from mercurial import util
574 > from mercurial.hgweb import common
575 > from mercurial.hgweb import common
575 > def perform_authentication(hgweb, req, op):
576 > def perform_authentication(hgweb, req, op):
576 > cookie = req.headers.get(b'Cookie')
577 > cookie = req.headers.get(b'Cookie')
577 > if not cookie:
578 > if not cookie:
578 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, b'no-cookie')
579 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, b'no-cookie')
579 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, b'Cookie: %s' % cookie)
580 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, b'Cookie: %s' % cookie)
580 > def extsetup(ui):
581 > def extsetup(ui):
581 > common.permhooks.insert(0, perform_authentication)
582 > common.permhooks.insert(0, perform_authentication)
582 > EOF
583 > EOF
583
584
584 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
585 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
585 $ cat pid > $DAEMON_PIDS
586 $ cat pid > $DAEMON_PIDS
586
587
587 Request without cookie sent should fail due to lack of cookie
588 Request without cookie sent should fail due to lack of cookie
588
589
589 $ hg id http://localhost:$HGPORT
590 $ hg id http://localhost:$HGPORT
590 abort: HTTP Error 500: no-cookie
591 abort: HTTP Error 500: no-cookie
591 [100]
592 [100]
592
593
593 Populate a cookies file
594 Populate a cookies file
594
595
595 $ cat > cookies.txt << EOF
596 $ cat > cookies.txt << EOF
596 > # HTTP Cookie File
597 > # HTTP Cookie File
597 > # Expiration is 2030-01-01 at midnight
598 > # Expiration is 2030-01-01 at midnight
598 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
599 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
599 > EOF
600 > EOF
600
601
601 Should not send a cookie for another domain
602 Should not send a cookie for another domain
602
603
603 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
604 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
604 abort: HTTP Error 500: no-cookie
605 abort: HTTP Error 500: no-cookie
605 [100]
606 [100]
606
607
607 Add a cookie entry for our test server and verify it is sent
608 Add a cookie entry for our test server and verify it is sent
608
609
609 $ cat >> cookies.txt << EOF
610 $ cat >> cookies.txt << EOF
610 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
611 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
611 > EOF
612 > EOF
612
613
613 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
614 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
614 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
615 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
615 [100]
616 [100]
@@ -1,693 +1,694 b''
1 #testcases lfsremote-on lfsremote-off
1 #testcases lfsremote-on lfsremote-off
2 #require serve no-reposimplestore no-chg
2 #require serve no-reposimplestore no-chg
3
3
4 This test splits `hg serve` with and without using the extension into separate
4 This test splits `hg serve` with and without using the extension into separate
5 tests cases. The tests are broken down as follows, where "LFS"/"No-LFS"
5 tests cases. The tests are broken down as follows, where "LFS"/"No-LFS"
6 indicates whether or not there are commits that use an LFS file, and "D"/"E"
6 indicates whether or not there are commits that use an LFS file, and "D"/"E"
7 indicates whether or not the extension is loaded. The "X" cases are not tested
7 indicates whether or not the extension is loaded. The "X" cases are not tested
8 individually, because the lfs requirement causes the process to bail early if
8 individually, because the lfs requirement causes the process to bail early if
9 the extension is disabled.
9 the extension is disabled.
10
10
11 . Server
11 . Server
12 .
12 .
13 . No-LFS LFS
13 . No-LFS LFS
14 . +----------------------------+
14 . +----------------------------+
15 . | || D | E | D | E |
15 . | || D | E | D | E |
16 . |---++=======================|
16 . |---++=======================|
17 . C | D || N/A | #1 | X | #4 |
17 . C | D || N/A | #1 | X | #4 |
18 . l No +---++-----------------------|
18 . l No +---++-----------------------|
19 . i LFS | E || #2 | #2 | X | #5 |
19 . i LFS | E || #2 | #2 | X | #5 |
20 . e +---++-----------------------|
20 . e +---++-----------------------|
21 . n | D || X | X | X | X |
21 . n | D || X | X | X | X |
22 . t LFS |---++-----------------------|
22 . t LFS |---++-----------------------|
23 . | E || #3 | #3 | X | #6 |
23 . | E || #3 | #3 | X | #6 |
24 . |---++-----------------------+
24 . |---++-----------------------+
25
25
26 make command server magic visible
26 make command server magic visible
27
27
28 #if windows
28 #if windows
29 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
29 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
30 #else
30 #else
31 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
31 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
32 #endif
32 #endif
33 $ export PYTHONPATH
33 $ export PYTHONPATH
34
34
35 $ hg init server
35 $ hg init server
36 $ SERVER_REQUIRES="$TESTTMP/server/.hg/requires"
36 $ SERVER_REQUIRES="$TESTTMP/server/.hg/requires"
37
37
38 $ cat > $TESTTMP/debugprocessors.py <<EOF
38 $ cat > $TESTTMP/debugprocessors.py <<EOF
39 > from mercurial import (
39 > from mercurial import (
40 > cmdutil,
40 > cmdutil,
41 > commands,
41 > commands,
42 > pycompat,
42 > pycompat,
43 > registrar,
43 > registrar,
44 > )
44 > )
45 > cmdtable = {}
45 > cmdtable = {}
46 > command = registrar.command(cmdtable)
46 > command = registrar.command(cmdtable)
47 > @command(b'debugprocessors', [], b'FILE')
47 > @command(b'debugprocessors', [], b'FILE')
48 > def debugprocessors(ui, repo, file_=None, **opts):
48 > def debugprocessors(ui, repo, file_=None, **opts):
49 > opts = pycompat.byteskwargs(opts)
49 > opts = pycompat.byteskwargs(opts)
50 > opts[b'changelog'] = False
50 > opts[b'changelog'] = False
51 > opts[b'manifest'] = False
51 > opts[b'manifest'] = False
52 > opts[b'dir'] = False
52 > opts[b'dir'] = False
53 > rl = cmdutil.openrevlog(repo, b'debugprocessors', file_, opts)
53 > rl = cmdutil.openrevlog(repo, b'debugprocessors', file_, opts)
54 > for flag, proc in rl._flagprocessors.items():
54 > for flag, proc in rl._flagprocessors.items():
55 > ui.status(b"registered processor '%#x'\n" % (flag))
55 > ui.status(b"registered processor '%#x'\n" % (flag))
56 > EOF
56 > EOF
57
57
58 Skip the experimental.changegroup3=True config. Failure to agree on this comes
58 Skip the experimental.changegroup3=True config. Failure to agree on this comes
59 first, and causes an "abort: no common changegroup version" if the extension is
59 first, and causes an "abort: no common changegroup version" if the extension is
60 only loaded on one side. If that *is* enabled, the subsequent failure is "abort:
60 only loaded on one side. If that *is* enabled, the subsequent failure is "abort:
61 missing processor for flag '0x2000'!" if the extension is only loaded on one side
61 missing processor for flag '0x2000'!" if the extension is only loaded on one side
62 (possibly also masked by the Internal Server Error message).
62 (possibly also masked by the Internal Server Error message).
63 $ cat >> $HGRCPATH <<EOF
63 $ cat >> $HGRCPATH <<EOF
64 > [extensions]
64 > [extensions]
65 > debugprocessors = $TESTTMP/debugprocessors.py
65 > debugprocessors = $TESTTMP/debugprocessors.py
66 > [experimental]
66 > [experimental]
67 > lfs.disableusercache = True
67 > lfs.disableusercache = True
68 > lfs.worker-enable = False
68 > lfs.worker-enable = False
69 > [lfs]
69 > [lfs]
70 > threshold=10
70 > threshold=10
71 > [web]
71 > [web]
72 > allow_push=*
72 > allow_push=*
73 > push_ssl=False
73 > push_ssl=False
74 > EOF
74 > EOF
75
75
76 $ cp $HGRCPATH $HGRCPATH.orig
76 $ cp $HGRCPATH $HGRCPATH.orig
77
77
78 #if lfsremote-on
78 #if lfsremote-on
79 $ hg --config extensions.lfs= -R server \
79 $ hg --config extensions.lfs= -R server \
80 > serve -p $HGPORT -d --pid-file=hg.pid --errorlog=$TESTTMP/errors.log
80 > serve -p $HGPORT -d --pid-file=hg.pid --errorlog=$TESTTMP/errors.log
81 #else
81 #else
82 $ hg --config extensions.lfs=! -R server \
82 $ hg --config extensions.lfs=! -R server \
83 > serve -p $HGPORT -d --pid-file=hg.pid --errorlog=$TESTTMP/errors.log
83 > serve -p $HGPORT -d --pid-file=hg.pid --errorlog=$TESTTMP/errors.log
84 #endif
84 #endif
85
85
86 $ cat hg.pid >> $DAEMON_PIDS
86 $ cat hg.pid >> $DAEMON_PIDS
87 $ hg clone -q http://localhost:$HGPORT client
87 $ hg clone -q http://localhost:$HGPORT client
88 $ grep 'lfs' client/.hg/requires $SERVER_REQUIRES
88 $ grep 'lfs' client/.hg/requires $SERVER_REQUIRES
89 [1]
89 [1]
90
90
91 This trivial repo will force commandserver to load the extension, but not call
91 This trivial repo will force commandserver to load the extension, but not call
92 reposetup() on another repo actually being operated on. This gives coverage
92 reposetup() on another repo actually being operated on. This gives coverage
93 that wrapper functions are not assuming reposetup() was called.
93 that wrapper functions are not assuming reposetup() was called.
94
94
95 $ hg init $TESTTMP/cmdservelfs
95 $ hg init $TESTTMP/cmdservelfs
96 $ cat >> $TESTTMP/cmdservelfs/.hg/hgrc << EOF
96 $ cat >> $TESTTMP/cmdservelfs/.hg/hgrc << EOF
97 > [extensions]
97 > [extensions]
98 > lfs =
98 > lfs =
99 > EOF
99 > EOF
100
100
101 --------------------------------------------------------------------------------
101 --------------------------------------------------------------------------------
102 Case #1: client with non-lfs content and the extension disabled; server with
102 Case #1: client with non-lfs content and the extension disabled; server with
103 non-lfs content, and the extension enabled.
103 non-lfs content, and the extension enabled.
104
104
105 $ cd client
105 $ cd client
106 $ echo 'non-lfs' > nonlfs.txt
106 $ echo 'non-lfs' > nonlfs.txt
107 >>> from __future__ import absolute_import
107 >>> from __future__ import absolute_import
108 >>> from hgclient import check, readchannel, runcommand
108 >>> from hgclient import check, readchannel, runcommand
109 >>> @check
109 >>> @check
110 ... def diff(server):
110 ... def diff(server):
111 ... readchannel(server)
111 ... readchannel(server)
112 ... # run an arbitrary command in the repo with the extension loaded
112 ... # run an arbitrary command in the repo with the extension loaded
113 ... runcommand(server, [b'id', b'-R', b'../cmdservelfs'])
113 ... runcommand(server, [b'id', b'-R', b'../cmdservelfs'])
114 ... # now run a command in a repo without the extension to ensure that
114 ... # now run a command in a repo without the extension to ensure that
115 ... # files are added safely..
115 ... # files are added safely..
116 ... runcommand(server, [b'ci', b'-Aqm', b'non-lfs'])
116 ... runcommand(server, [b'ci', b'-Aqm', b'non-lfs'])
117 ... # .. and that scmutil.prefetchfiles() safely no-ops..
117 ... # .. and that scmutil.prefetchfiles() safely no-ops..
118 ... runcommand(server, [b'diff', b'-r', b'.~1'])
118 ... runcommand(server, [b'diff', b'-r', b'.~1'])
119 ... # .. and that debugupgraderepo safely no-ops.
119 ... # .. and that debugupgraderepo safely no-ops.
120 ... runcommand(server, [b'debugupgraderepo', b'-q', b'--run'])
120 ... runcommand(server, [b'debugupgraderepo', b'-q', b'--run'])
121 *** runcommand id -R ../cmdservelfs
121 *** runcommand id -R ../cmdservelfs
122 000000000000 tip
122 000000000000 tip
123 *** runcommand ci -Aqm non-lfs
123 *** runcommand ci -Aqm non-lfs
124 *** runcommand diff -r .~1
124 *** runcommand diff -r .~1
125 diff -r 000000000000 nonlfs.txt
125 diff -r 000000000000 nonlfs.txt
126 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
126 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
127 +++ b/nonlfs.txt Thu Jan 01 00:00:00 1970 +0000
127 +++ b/nonlfs.txt Thu Jan 01 00:00:00 1970 +0000
128 @@ -0,0 +1,1 @@
128 @@ -0,0 +1,1 @@
129 +non-lfs
129 +non-lfs
130 *** runcommand debugupgraderepo -q --run
130 *** runcommand debugupgraderepo -q --run
131
131
132 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
132 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
133 [1]
133 [1]
134
134
135 #if lfsremote-on
135 #if lfsremote-on
136
136
137 $ hg push -q
137 $ hg push -q
138 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
138 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
139 [1]
139 [1]
140
140
141 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client1_clone
141 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client1_clone
142 $ grep 'lfs' $TESTTMP/client1_clone/.hg/requires $SERVER_REQUIRES
142 $ grep 'lfs' $TESTTMP/client1_clone/.hg/requires $SERVER_REQUIRES
143 [1]
143 [1]
144
144
145 $ hg init $TESTTMP/client1_pull
145 $ hg init $TESTTMP/client1_pull
146 $ hg -R $TESTTMP/client1_pull pull -q http://localhost:$HGPORT
146 $ hg -R $TESTTMP/client1_pull pull -q http://localhost:$HGPORT
147 $ grep 'lfs' $TESTTMP/client1_pull/.hg/requires $SERVER_REQUIRES
147 $ grep 'lfs' $TESTTMP/client1_pull/.hg/requires $SERVER_REQUIRES
148 [1]
148 [1]
149
149
150 $ hg identify http://localhost:$HGPORT
150 $ hg identify http://localhost:$HGPORT
151 d437e1d24fbd
151 d437e1d24fbd
152
152
153 #endif
153 #endif
154
154
155 --------------------------------------------------------------------------------
155 --------------------------------------------------------------------------------
156 Case #2: client with non-lfs content and the extension enabled; server with
156 Case #2: client with non-lfs content and the extension enabled; server with
157 non-lfs content, and the extension state controlled by #testcases.
157 non-lfs content, and the extension state controlled by #testcases.
158
158
159 $ cat >> $HGRCPATH <<EOF
159 $ cat >> $HGRCPATH <<EOF
160 > [extensions]
160 > [extensions]
161 > lfs =
161 > lfs =
162 > EOF
162 > EOF
163 $ echo 'non-lfs' > nonlfs2.txt
163 $ echo 'non-lfs' > nonlfs2.txt
164 $ hg ci -Aqm 'non-lfs file with lfs client'
164 $ hg ci -Aqm 'non-lfs file with lfs client'
165
165
166 Since no lfs content has been added yet, the push is allowed, even when the
166 Since no lfs content has been added yet, the push is allowed, even when the
167 extension is not enabled remotely.
167 extension is not enabled remotely.
168
168
169 $ hg push -q
169 $ hg push -q
170 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
170 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
171 [1]
171 [1]
172
172
173 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client2_clone
173 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client2_clone
174 $ grep 'lfs' $TESTTMP/client2_clone/.hg/requires $SERVER_REQUIRES
174 $ grep 'lfs' $TESTTMP/client2_clone/.hg/requires $SERVER_REQUIRES
175 [1]
175 [1]
176
176
177 $ hg init $TESTTMP/client2_pull
177 $ hg init $TESTTMP/client2_pull
178 $ hg -R $TESTTMP/client2_pull pull -q http://localhost:$HGPORT
178 $ hg -R $TESTTMP/client2_pull pull -q http://localhost:$HGPORT
179 $ grep 'lfs' $TESTTMP/client2_pull/.hg/requires $SERVER_REQUIRES
179 $ grep 'lfs' $TESTTMP/client2_pull/.hg/requires $SERVER_REQUIRES
180 [1]
180 [1]
181
181
182 $ hg identify http://localhost:$HGPORT
182 $ hg identify http://localhost:$HGPORT
183 1477875038c6
183 1477875038c6
184
184
185 --------------------------------------------------------------------------------
185 --------------------------------------------------------------------------------
186 Case #3: client with lfs content and the extension enabled; server with
186 Case #3: client with lfs content and the extension enabled; server with
187 non-lfs content, and the extension state controlled by #testcases. The server
187 non-lfs content, and the extension state controlled by #testcases. The server
188 should have an 'lfs' requirement after it picks up its first commit with a blob.
188 should have an 'lfs' requirement after it picks up its first commit with a blob.
189
189
190 $ echo 'this is a big lfs file' > lfs.bin
190 $ echo 'this is a big lfs file' > lfs.bin
191 $ hg ci -Aqm 'lfs'
191 $ hg ci -Aqm 'lfs'
192 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
192 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
193 .hg/requires:lfs
193 .hg/requires:lfs
194
194
195 #if lfsremote-off
195 #if lfsremote-off
196 $ hg push -q
196 $ hg push -q
197 abort: required features are not supported in the destination: lfs
197 abort: required features are not supported in the destination: lfs
198 (enable the lfs extension on the server)
198 (enable the lfs extension on the server)
199 [255]
199 [255]
200 #else
200 #else
201 $ hg push -q
201 $ hg push -q
202 #endif
202 #endif
203 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
203 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
204 .hg/requires:lfs
204 .hg/requires:lfs
205 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
205 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
206
206
207 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client3_clone
207 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client3_clone
208 $ grep 'lfs' $TESTTMP/client3_clone/.hg/requires $SERVER_REQUIRES || true
208 $ grep 'lfs' $TESTTMP/client3_clone/.hg/requires $SERVER_REQUIRES || true
209 $TESTTMP/client3_clone/.hg/requires:lfs (lfsremote-on !)
209 $TESTTMP/client3_clone/.hg/requires:lfs (lfsremote-on !)
210 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
210 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
211
211
212 $ hg init $TESTTMP/client3_pull
212 $ hg init $TESTTMP/client3_pull
213 $ hg -R $TESTTMP/client3_pull pull -q http://localhost:$HGPORT
213 $ hg -R $TESTTMP/client3_pull pull -q http://localhost:$HGPORT
214 $ grep 'lfs' $TESTTMP/client3_pull/.hg/requires $SERVER_REQUIRES || true
214 $ grep 'lfs' $TESTTMP/client3_pull/.hg/requires $SERVER_REQUIRES || true
215 $TESTTMP/client3_pull/.hg/requires:lfs (lfsremote-on !)
215 $TESTTMP/client3_pull/.hg/requires:lfs (lfsremote-on !)
216 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
216 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
217
217
218 Test that the commit/changegroup requirement check hook can be run multiple
218 Test that the commit/changegroup requirement check hook can be run multiple
219 times.
219 times.
220
220
221 $ hg clone -qr 0 http://localhost:$HGPORT $TESTTMP/cmdserve_client3
221 $ hg clone -qr 0 http://localhost:$HGPORT $TESTTMP/cmdserve_client3
222
222
223 $ cd ../cmdserve_client3
223 $ cd ../cmdserve_client3
224
224
225 >>> from __future__ import absolute_import
225 >>> from __future__ import absolute_import
226 >>> from hgclient import check, readchannel, runcommand
226 >>> from hgclient import check, readchannel, runcommand
227 >>> @check
227 >>> @check
228 ... def addrequirement(server):
228 ... def addrequirement(server):
229 ... readchannel(server)
229 ... readchannel(server)
230 ... # change the repo in a way that adds the lfs requirement
230 ... # change the repo in a way that adds the lfs requirement
231 ... runcommand(server, [b'pull', b'-qu'])
231 ... runcommand(server, [b'pull', b'-qu'])
232 ... # Now cause the requirement adding hook to fire again, without going
232 ... # Now cause the requirement adding hook to fire again, without going
233 ... # through reposetup() again.
233 ... # through reposetup() again.
234 ... with open('file.txt', 'wb') as fp:
234 ... with open('file.txt', 'wb') as fp:
235 ... fp.write(b'data')
235 ... fp.write(b'data')
236 ... runcommand(server, [b'ci', b'-Aqm', b'non-lfs'])
236 ... runcommand(server, [b'ci', b'-Aqm', b'non-lfs'])
237 *** runcommand pull -qu
237 *** runcommand pull -qu
238 *** runcommand ci -Aqm non-lfs
238 *** runcommand ci -Aqm non-lfs
239
239
240 $ cd ../client
240 $ cd ../client
241
241
242 The difference here is the push failed above when the extension isn't
242 The difference here is the push failed above when the extension isn't
243 enabled on the server.
243 enabled on the server.
244 $ hg identify http://localhost:$HGPORT
244 $ hg identify http://localhost:$HGPORT
245 8374dc4052cb (lfsremote-on !)
245 8374dc4052cb (lfsremote-on !)
246 1477875038c6 (lfsremote-off !)
246 1477875038c6 (lfsremote-off !)
247
247
248 Don't bother testing the lfsremote-off cases- the server won't be able
248 Don't bother testing the lfsremote-off cases- the server won't be able
249 to launch if there's lfs content and the extension is disabled.
249 to launch if there's lfs content and the extension is disabled.
250
250
251 #if lfsremote-on
251 #if lfsremote-on
252
252
253 --------------------------------------------------------------------------------
253 --------------------------------------------------------------------------------
254 Case #4: client with non-lfs content and the extension disabled; server with
254 Case #4: client with non-lfs content and the extension disabled; server with
255 lfs content, and the extension enabled.
255 lfs content, and the extension enabled.
256
256
257 $ cat >> $HGRCPATH <<EOF
257 $ cat >> $HGRCPATH <<EOF
258 > [extensions]
258 > [extensions]
259 > lfs = !
259 > lfs = !
260 > EOF
260 > EOF
261
261
262 $ hg init $TESTTMP/client4
262 $ hg init $TESTTMP/client4
263 $ cd $TESTTMP/client4
263 $ cd $TESTTMP/client4
264 $ cat >> .hg/hgrc <<EOF
264 $ cat >> .hg/hgrc <<EOF
265 > [paths]
265 > [paths]
266 > default = http://localhost:$HGPORT
266 > default = http://localhost:$HGPORT
267 > EOF
267 > EOF
268 $ echo 'non-lfs' > nonlfs2.txt
268 $ echo 'non-lfs' > nonlfs2.txt
269 $ hg ci -Aqm 'non-lfs'
269 $ hg ci -Aqm 'non-lfs'
270 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
270 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
271 $TESTTMP/server/.hg/requires:lfs
271 $TESTTMP/server/.hg/requires:lfs
272
272
273 $ hg push -q --force
273 $ hg push -q --force
274 warning: repository is unrelated
274 warning: repository is unrelated
275 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
275 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
276 $TESTTMP/server/.hg/requires:lfs
276 $TESTTMP/server/.hg/requires:lfs
277
277
278 $ hg clone http://localhost:$HGPORT $TESTTMP/client4_clone
278 $ hg clone http://localhost:$HGPORT $TESTTMP/client4_clone
279 (remote is using large file support (lfs), but it is explicitly disabled in the local configuration)
279 (remote is using large file support (lfs), but it is explicitly disabled in the local configuration)
280 abort: repository requires features unknown to this Mercurial: lfs
280 abort: repository requires features unknown to this Mercurial: lfs
281 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
281 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
282 [255]
282 [255]
283 $ grep 'lfs' $TESTTMP/client4_clone/.hg/requires $SERVER_REQUIRES
283 $ grep 'lfs' $TESTTMP/client4_clone/.hg/requires $SERVER_REQUIRES
284 grep: $TESTTMP/client4_clone/.hg/requires: $ENOENT$
284 grep: $TESTTMP/client4_clone/.hg/requires: $ENOENT$
285 $TESTTMP/server/.hg/requires:lfs
285 $TESTTMP/server/.hg/requires:lfs
286 [2]
286 [2]
287
287
288 TODO: fail more gracefully.
288 TODO: fail more gracefully.
289
289
290 $ hg init $TESTTMP/client4_pull
290 $ hg init $TESTTMP/client4_pull
291 $ hg -R $TESTTMP/client4_pull pull http://localhost:$HGPORT
291 $ hg -R $TESTTMP/client4_pull pull http://localhost:$HGPORT
292 pulling from http://localhost:$HGPORT/
292 pulling from http://localhost:$HGPORT/
293 requesting all changes
293 requesting all changes
294 remote: abort: no common changegroup version
294 remote: abort: no common changegroup version
295 abort: pull failed on remote
295 abort: pull failed on remote
296 [255]
296 [255]
297 $ grep 'lfs' $TESTTMP/client4_pull/.hg/requires $SERVER_REQUIRES
297 $ grep 'lfs' $TESTTMP/client4_pull/.hg/requires $SERVER_REQUIRES
298 $TESTTMP/server/.hg/requires:lfs
298 $TESTTMP/server/.hg/requires:lfs
299
299
300 $ hg identify http://localhost:$HGPORT
300 $ hg identify http://localhost:$HGPORT
301 03b080fa9d93
301 03b080fa9d93
302
302
303 --------------------------------------------------------------------------------
303 --------------------------------------------------------------------------------
304 Case #5: client with non-lfs content and the extension enabled; server with
304 Case #5: client with non-lfs content and the extension enabled; server with
305 lfs content, and the extension enabled.
305 lfs content, and the extension enabled.
306
306
307 $ cat >> $HGRCPATH <<EOF
307 $ cat >> $HGRCPATH <<EOF
308 > [extensions]
308 > [extensions]
309 > lfs =
309 > lfs =
310 > EOF
310 > EOF
311 $ echo 'non-lfs' > nonlfs3.txt
311 $ echo 'non-lfs' > nonlfs3.txt
312 $ hg ci -Aqm 'non-lfs file with lfs client'
312 $ hg ci -Aqm 'non-lfs file with lfs client'
313
313
314 $ hg push -q
314 $ hg push -q
315 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
315 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
316 $TESTTMP/server/.hg/requires:lfs
316 $TESTTMP/server/.hg/requires:lfs
317
317
318 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client5_clone
318 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client5_clone
319 $ grep 'lfs' $TESTTMP/client5_clone/.hg/requires $SERVER_REQUIRES
319 $ grep 'lfs' $TESTTMP/client5_clone/.hg/requires $SERVER_REQUIRES
320 $TESTTMP/client5_clone/.hg/requires:lfs
320 $TESTTMP/client5_clone/.hg/requires:lfs
321 $TESTTMP/server/.hg/requires:lfs
321 $TESTTMP/server/.hg/requires:lfs
322
322
323 $ hg init $TESTTMP/client5_pull
323 $ hg init $TESTTMP/client5_pull
324 $ hg -R $TESTTMP/client5_pull pull -q http://localhost:$HGPORT
324 $ hg -R $TESTTMP/client5_pull pull -q http://localhost:$HGPORT
325 $ grep 'lfs' $TESTTMP/client5_pull/.hg/requires $SERVER_REQUIRES
325 $ grep 'lfs' $TESTTMP/client5_pull/.hg/requires $SERVER_REQUIRES
326 $TESTTMP/client5_pull/.hg/requires:lfs
326 $TESTTMP/client5_pull/.hg/requires:lfs
327 $TESTTMP/server/.hg/requires:lfs
327 $TESTTMP/server/.hg/requires:lfs
328
328
329 $ hg identify http://localhost:$HGPORT
329 $ hg identify http://localhost:$HGPORT
330 c729025cc5e3
330 c729025cc5e3
331
331
332 $ mv $HGRCPATH $HGRCPATH.tmp
332 $ mv $HGRCPATH $HGRCPATH.tmp
333 $ cp $HGRCPATH.orig $HGRCPATH
333 $ cp $HGRCPATH.orig $HGRCPATH
334
334
335 >>> from __future__ import absolute_import
335 >>> from __future__ import absolute_import
336 >>> from hgclient import bprint, check, readchannel, runcommand, stdout
336 >>> from hgclient import bprint, check, readchannel, runcommand, stdout
337 >>> @check
337 >>> @check
338 ... def checkflags(server):
338 ... def checkflags(server):
339 ... readchannel(server)
339 ... readchannel(server)
340 ... bprint(b'')
340 ... bprint(b'')
341 ... bprint(b'# LFS required- both lfs and non-lfs revlogs have 0x2000 flag')
341 ... bprint(b'# LFS required- both lfs and non-lfs revlogs have 0x2000 flag')
342 ... stdout.flush()
342 ... stdout.flush()
343 ... runcommand(server, [b'debugprocessors', b'lfs.bin', b'-R',
343 ... runcommand(server, [b'debugprocessors', b'lfs.bin', b'-R',
344 ... b'../server'])
344 ... b'../server'])
345 ... runcommand(server, [b'debugprocessors', b'nonlfs2.txt', b'-R',
345 ... runcommand(server, [b'debugprocessors', b'nonlfs2.txt', b'-R',
346 ... b'../server'])
346 ... b'../server'])
347 ... runcommand(server, [b'config', b'extensions', b'--cwd',
347 ... runcommand(server, [b'config', b'extensions', b'--cwd',
348 ... b'../server'])
348 ... b'../server'])
349 ...
349 ...
350 ... bprint(b"\n# LFS not enabled- revlogs don't have 0x2000 flag")
350 ... bprint(b"\n# LFS not enabled- revlogs don't have 0x2000 flag")
351 ... stdout.flush()
351 ... stdout.flush()
352 ... runcommand(server, [b'debugprocessors', b'nonlfs3.txt'])
352 ... runcommand(server, [b'debugprocessors', b'nonlfs3.txt'])
353 ... runcommand(server, [b'config', b'extensions'])
353 ... runcommand(server, [b'config', b'extensions'])
354
354
355 # LFS required- both lfs and non-lfs revlogs have 0x2000 flag
355 # LFS required- both lfs and non-lfs revlogs have 0x2000 flag
356 *** runcommand debugprocessors lfs.bin -R ../server
356 *** runcommand debugprocessors lfs.bin -R ../server
357 registered processor '0x8000'
357 registered processor '0x8000'
358 registered processor '0x800'
358 registered processor '0x800'
359 registered processor '0x2000'
359 registered processor '0x2000'
360 *** runcommand debugprocessors nonlfs2.txt -R ../server
360 *** runcommand debugprocessors nonlfs2.txt -R ../server
361 registered processor '0x8000'
361 registered processor '0x8000'
362 registered processor '0x800'
362 registered processor '0x800'
363 registered processor '0x2000'
363 registered processor '0x2000'
364 *** runcommand config extensions --cwd ../server
364 *** runcommand config extensions --cwd ../server
365 extensions.debugprocessors=$TESTTMP/debugprocessors.py
365 extensions.debugprocessors=$TESTTMP/debugprocessors.py
366 extensions.lfs=
366 extensions.lfs=
367
367
368 # LFS not enabled- revlogs don't have 0x2000 flag
368 # LFS not enabled- revlogs don't have 0x2000 flag
369 *** runcommand debugprocessors nonlfs3.txt
369 *** runcommand debugprocessors nonlfs3.txt
370 registered processor '0x8000'
370 registered processor '0x8000'
371 registered processor '0x800'
371 registered processor '0x800'
372 *** runcommand config extensions
372 *** runcommand config extensions
373 extensions.debugprocessors=$TESTTMP/debugprocessors.py
373 extensions.debugprocessors=$TESTTMP/debugprocessors.py
374
374
375 $ rm $HGRCPATH
375 $ rm $HGRCPATH
376 $ mv $HGRCPATH.tmp $HGRCPATH
376 $ mv $HGRCPATH.tmp $HGRCPATH
377
377
378 $ hg clone $TESTTMP/client $TESTTMP/nonlfs -qr 0 --config extensions.lfs=
378 $ hg clone $TESTTMP/client $TESTTMP/nonlfs -qr 0 --config extensions.lfs=
379 $ cat >> $TESTTMP/nonlfs/.hg/hgrc <<EOF
379 $ cat >> $TESTTMP/nonlfs/.hg/hgrc <<EOF
380 > [extensions]
380 > [extensions]
381 > lfs = !
381 > lfs = !
382 > EOF
382 > EOF
383
383
384 >>> from __future__ import absolute_import, print_function
384 >>> from __future__ import absolute_import, print_function
385 >>> from hgclient import bprint, check, readchannel, runcommand, stdout
385 >>> from hgclient import bprint, check, readchannel, runcommand, stdout
386 >>> @check
386 >>> @check
387 ... def checkflags2(server):
387 ... def checkflags2(server):
388 ... readchannel(server)
388 ... readchannel(server)
389 ... bprint(b'')
389 ... bprint(b'')
390 ... bprint(b'# LFS enabled- both lfs and non-lfs revlogs have 0x2000 flag')
390 ... bprint(b'# LFS enabled- both lfs and non-lfs revlogs have 0x2000 flag')
391 ... stdout.flush()
391 ... stdout.flush()
392 ... runcommand(server, [b'debugprocessors', b'lfs.bin', b'-R',
392 ... runcommand(server, [b'debugprocessors', b'lfs.bin', b'-R',
393 ... b'../server'])
393 ... b'../server'])
394 ... runcommand(server, [b'debugprocessors', b'nonlfs2.txt', b'-R',
394 ... runcommand(server, [b'debugprocessors', b'nonlfs2.txt', b'-R',
395 ... b'../server'])
395 ... b'../server'])
396 ... runcommand(server, [b'config', b'extensions', b'--cwd',
396 ... runcommand(server, [b'config', b'extensions', b'--cwd',
397 ... b'../server'])
397 ... b'../server'])
398 ...
398 ...
399 ... bprint(b'\n# LFS enabled without requirement- revlogs have 0x2000 flag')
399 ... bprint(b'\n# LFS enabled without requirement- revlogs have 0x2000 flag')
400 ... stdout.flush()
400 ... stdout.flush()
401 ... runcommand(server, [b'debugprocessors', b'nonlfs3.txt'])
401 ... runcommand(server, [b'debugprocessors', b'nonlfs3.txt'])
402 ... runcommand(server, [b'config', b'extensions'])
402 ... runcommand(server, [b'config', b'extensions'])
403 ...
403 ...
404 ... bprint(b"\n# LFS disabled locally- revlogs don't have 0x2000 flag")
404 ... bprint(b"\n# LFS disabled locally- revlogs don't have 0x2000 flag")
405 ... stdout.flush()
405 ... stdout.flush()
406 ... runcommand(server, [b'debugprocessors', b'nonlfs.txt', b'-R',
406 ... runcommand(server, [b'debugprocessors', b'nonlfs.txt', b'-R',
407 ... b'../nonlfs'])
407 ... b'../nonlfs'])
408 ... runcommand(server, [b'config', b'extensions', b'--cwd',
408 ... runcommand(server, [b'config', b'extensions', b'--cwd',
409 ... b'../nonlfs'])
409 ... b'../nonlfs'])
410
410
411 # LFS enabled- both lfs and non-lfs revlogs have 0x2000 flag
411 # LFS enabled- both lfs and non-lfs revlogs have 0x2000 flag
412 *** runcommand debugprocessors lfs.bin -R ../server
412 *** runcommand debugprocessors lfs.bin -R ../server
413 registered processor '0x8000'
413 registered processor '0x8000'
414 registered processor '0x800'
414 registered processor '0x800'
415 registered processor '0x2000'
415 registered processor '0x2000'
416 *** runcommand debugprocessors nonlfs2.txt -R ../server
416 *** runcommand debugprocessors nonlfs2.txt -R ../server
417 registered processor '0x8000'
417 registered processor '0x8000'
418 registered processor '0x800'
418 registered processor '0x800'
419 registered processor '0x2000'
419 registered processor '0x2000'
420 *** runcommand config extensions --cwd ../server
420 *** runcommand config extensions --cwd ../server
421 extensions.debugprocessors=$TESTTMP/debugprocessors.py
421 extensions.debugprocessors=$TESTTMP/debugprocessors.py
422 extensions.lfs=
422 extensions.lfs=
423
423
424 # LFS enabled without requirement- revlogs have 0x2000 flag
424 # LFS enabled without requirement- revlogs have 0x2000 flag
425 *** runcommand debugprocessors nonlfs3.txt
425 *** runcommand debugprocessors nonlfs3.txt
426 registered processor '0x8000'
426 registered processor '0x8000'
427 registered processor '0x800'
427 registered processor '0x800'
428 registered processor '0x2000'
428 registered processor '0x2000'
429 *** runcommand config extensions
429 *** runcommand config extensions
430 extensions.debugprocessors=$TESTTMP/debugprocessors.py
430 extensions.debugprocessors=$TESTTMP/debugprocessors.py
431 extensions.lfs=
431 extensions.lfs=
432
432
433 # LFS disabled locally- revlogs don't have 0x2000 flag
433 # LFS disabled locally- revlogs don't have 0x2000 flag
434 *** runcommand debugprocessors nonlfs.txt -R ../nonlfs
434 *** runcommand debugprocessors nonlfs.txt -R ../nonlfs
435 registered processor '0x8000'
435 registered processor '0x8000'
436 registered processor '0x800'
436 registered processor '0x800'
437 *** runcommand config extensions --cwd ../nonlfs
437 *** runcommand config extensions --cwd ../nonlfs
438 extensions.debugprocessors=$TESTTMP/debugprocessors.py
438 extensions.debugprocessors=$TESTTMP/debugprocessors.py
439 extensions.lfs=!
439 extensions.lfs=!
440
440
441 --------------------------------------------------------------------------------
441 --------------------------------------------------------------------------------
442 Case #6: client with lfs content and the extension enabled; server with
442 Case #6: client with lfs content and the extension enabled; server with
443 lfs content, and the extension enabled.
443 lfs content, and the extension enabled.
444
444
445 $ echo 'this is another lfs file' > lfs2.txt
445 $ echo 'this is another lfs file' > lfs2.txt
446 $ hg ci -Aqm 'lfs file with lfs client'
446 $ hg ci -Aqm 'lfs file with lfs client'
447
447
448 $ hg --config paths.default= push -v http://localhost:$HGPORT
448 $ hg --config paths.default= push -v http://localhost:$HGPORT
449 pushing to http://localhost:$HGPORT/
449 pushing to http://localhost:$HGPORT/
450 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
450 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
451 searching for changes
451 searching for changes
452 remote has heads on branch 'default' that are not known locally: 8374dc4052cb
452 remote has heads on branch 'default' that are not known locally: 8374dc4052cb
453 lfs: uploading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
453 lfs: uploading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
454 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
454 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
455 lfs: uploaded 1 files (25 bytes)
455 lfs: uploaded 1 files (25 bytes)
456 1 changesets found
456 1 changesets found
457 uncompressed size of bundle content:
457 uncompressed size of bundle content:
458 206 (changelog)
458 206 (changelog)
459 172 (manifests)
459 172 (manifests)
460 275 lfs2.txt
460 275 lfs2.txt
461 remote: adding changesets
461 remote: adding changesets
462 remote: adding manifests
462 remote: adding manifests
463 remote: adding file changes
463 remote: adding file changes
464 remote: added 1 changesets with 1 changes to 1 files
464 remote: added 1 changesets with 1 changes to 1 files
465 (sent 8 HTTP requests and 3526 bytes; received 961 bytes in responses) (?)
465 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
466 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
466 .hg/requires:lfs
467 .hg/requires:lfs
467 $TESTTMP/server/.hg/requires:lfs
468 $TESTTMP/server/.hg/requires:lfs
468
469
469 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client6_clone
470 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client6_clone
470 $ grep 'lfs' $TESTTMP/client6_clone/.hg/requires $SERVER_REQUIRES
471 $ grep 'lfs' $TESTTMP/client6_clone/.hg/requires $SERVER_REQUIRES
471 $TESTTMP/client6_clone/.hg/requires:lfs
472 $TESTTMP/client6_clone/.hg/requires:lfs
472 $TESTTMP/server/.hg/requires:lfs
473 $TESTTMP/server/.hg/requires:lfs
473
474
474 $ hg init $TESTTMP/client6_pull
475 $ hg init $TESTTMP/client6_pull
475 $ hg -R $TESTTMP/client6_pull pull -u -v http://localhost:$HGPORT
476 $ hg -R $TESTTMP/client6_pull pull -u -v http://localhost:$HGPORT
476 pulling from http://localhost:$HGPORT/
477 pulling from http://localhost:$HGPORT/
477 requesting all changes
478 requesting all changes
478 adding changesets
479 adding changesets
479 adding manifests
480 adding manifests
480 adding file changes
481 adding file changes
481 calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs
482 calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs
482 added 6 changesets with 5 changes to 5 files (+1 heads)
483 added 6 changesets with 5 changes to 5 files (+1 heads)
483 new changesets d437e1d24fbd:d3b84d50eacb
484 new changesets d437e1d24fbd:d3b84d50eacb
484 resolving manifests
485 resolving manifests
485 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
486 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
486 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
487 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
487 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
488 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
488 lfs: downloaded 1 files (25 bytes)
489 lfs: downloaded 1 files (25 bytes)
489 getting lfs2.txt
490 getting lfs2.txt
490 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
491 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
491 getting nonlfs2.txt
492 getting nonlfs2.txt
492 getting nonlfs3.txt
493 getting nonlfs3.txt
493 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
494 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
494 updated to "d3b84d50eacb: lfs file with lfs client"
495 updated to "d3b84d50eacb: lfs file with lfs client"
495 1 other heads for branch "default"
496 1 other heads for branch "default"
496 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
497 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
497 $ grep 'lfs' $TESTTMP/client6_pull/.hg/requires $SERVER_REQUIRES
498 $ grep 'lfs' $TESTTMP/client6_pull/.hg/requires $SERVER_REQUIRES
498 $TESTTMP/client6_pull/.hg/requires:lfs
499 $TESTTMP/client6_pull/.hg/requires:lfs
499 $TESTTMP/server/.hg/requires:lfs
500 $TESTTMP/server/.hg/requires:lfs
500
501
501 $ hg identify http://localhost:$HGPORT
502 $ hg identify http://localhost:$HGPORT
502 d3b84d50eacb
503 d3b84d50eacb
503
504
504 --------------------------------------------------------------------------------
505 --------------------------------------------------------------------------------
505 Misc: process dies early if a requirement exists and the extension is disabled
506 Misc: process dies early if a requirement exists and the extension is disabled
506
507
507 $ hg --config extensions.lfs=! summary
508 $ hg --config extensions.lfs=! summary
508 abort: repository requires features unknown to this Mercurial: lfs
509 abort: repository requires features unknown to this Mercurial: lfs
509 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
510 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
510 [255]
511 [255]
511
512
512 $ echo 'this is an lfs file' > $TESTTMP/client6_clone/lfspair1.bin
513 $ echo 'this is an lfs file' > $TESTTMP/client6_clone/lfspair1.bin
513 $ echo 'this is an lfs file too' > $TESTTMP/client6_clone/lfspair2.bin
514 $ echo 'this is an lfs file too' > $TESTTMP/client6_clone/lfspair2.bin
514 $ hg -R $TESTTMP/client6_clone ci -Aqm 'add lfs pair'
515 $ hg -R $TESTTMP/client6_clone ci -Aqm 'add lfs pair'
515 $ hg -R $TESTTMP/client6_clone push -q
516 $ hg -R $TESTTMP/client6_clone push -q
516
517
517 $ hg clone -qU http://localhost:$HGPORT $TESTTMP/bulkfetch
518 $ hg clone -qU http://localhost:$HGPORT $TESTTMP/bulkfetch
518
519
519 Cat doesn't prefetch unless data is needed (e.g. '-T {rawdata}' doesn't need it)
520 Cat doesn't prefetch unless data is needed (e.g. '-T {rawdata}' doesn't need it)
520
521
521 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T '{rawdata}\n{path}\n'
522 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T '{rawdata}\n{path}\n'
522 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
523 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
523 version https://git-lfs.github.com/spec/v1
524 version https://git-lfs.github.com/spec/v1
524 oid sha256:cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
525 oid sha256:cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
525 size 20
526 size 20
526 x-is-binary 0
527 x-is-binary 0
527
528
528 lfspair1.bin
529 lfspair1.bin
529
530
530 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T json
531 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T json
531 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
532 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
532 [lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
533 [lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
533 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
534 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
534 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
535 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
535 lfs: downloaded 1 files (20 bytes)
536 lfs: downloaded 1 files (20 bytes)
536 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
537 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
537
538
538 {
539 {
539 "data": "this is an lfs file\n",
540 "data": "this is an lfs file\n",
540 "path": "lfspair1.bin",
541 "path": "lfspair1.bin",
541 "rawdata": "version https://git-lfs.github.com/spec/v1\noid sha256:cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782\nsize 20\nx-is-binary 0\n"
542 "rawdata": "version https://git-lfs.github.com/spec/v1\noid sha256:cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782\nsize 20\nx-is-binary 0\n"
542 }
543 }
543 ]
544 ]
544
545
545 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
546 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
546
547
547 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T '{data}\n'
548 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T '{data}\n'
548 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
549 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
549 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
550 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
550 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
551 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
551 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
552 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
552 lfs: downloaded 1 files (20 bytes)
553 lfs: downloaded 1 files (20 bytes)
553 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
554 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
554 this is an lfs file
555 this is an lfs file
555
556
556 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair2.bin
557 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair2.bin
557 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
558 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
558 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
559 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
559 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
560 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
560 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
561 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
561 lfs: downloaded 1 files (24 bytes)
562 lfs: downloaded 1 files (24 bytes)
562 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
563 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
563 this is an lfs file too
564 this is an lfs file too
564
565
565 Export will prefetch all needed files across all needed revisions
566 Export will prefetch all needed files across all needed revisions
566
567
567 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
568 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
568 $ hg -R $TESTTMP/bulkfetch -v export -r 0:tip -o all.export
569 $ hg -R $TESTTMP/bulkfetch -v export -r 0:tip -o all.export
569 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
570 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
570 exporting patches:
571 exporting patches:
571 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
572 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
572 lfs: need to transfer 4 objects (92 bytes)
573 lfs: need to transfer 4 objects (92 bytes)
573 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
574 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
574 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
575 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
575 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
576 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
576 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
577 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
577 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
578 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
578 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
579 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
579 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
580 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
580 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
581 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
581 lfs: downloaded 4 files (92 bytes)
582 lfs: downloaded 4 files (92 bytes)
582 all.export
583 all.export
583 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
584 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
584 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
585 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
585 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
586 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
586 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
587 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
587
588
588 Export with selected files is used with `extdiff --patch`
589 Export with selected files is used with `extdiff --patch`
589
590
590 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
591 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
591 $ hg --config extensions.extdiff= \
592 $ hg --config extensions.extdiff= \
592 > -R $TESTTMP/bulkfetch -v extdiff -r 2:tip --patch $TESTTMP/bulkfetch/lfs.bin
593 > -R $TESTTMP/bulkfetch -v extdiff -r 2:tip --patch $TESTTMP/bulkfetch/lfs.bin
593 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
594 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
594 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
595 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
595 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
596 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
596 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
597 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
597 lfs: downloaded 1 files (23 bytes)
598 lfs: downloaded 1 files (23 bytes)
598 */hg-8374dc4052cb.patch (glob)
599 */hg-8374dc4052cb.patch (glob)
599 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
600 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
600 */hg-9640b57e77b1.patch (glob)
601 */hg-9640b57e77b1.patch (glob)
601 --- */hg-8374dc4052cb.patch * (glob)
602 --- */hg-8374dc4052cb.patch * (glob)
602 +++ */hg-9640b57e77b1.patch * (glob)
603 +++ */hg-9640b57e77b1.patch * (glob)
603 @@ -2,12 +2,7 @@
604 @@ -2,12 +2,7 @@
604 # User test
605 # User test
605 # Date 0 0
606 # Date 0 0
606 # Thu Jan 01 00:00:00 1970 +0000
607 # Thu Jan 01 00:00:00 1970 +0000
607 -# Node ID 8374dc4052cbd388e79d9dc4ddb29784097aa354
608 -# Node ID 8374dc4052cbd388e79d9dc4ddb29784097aa354
608 -# Parent 1477875038c60152e391238920a16381c627b487
609 -# Parent 1477875038c60152e391238920a16381c627b487
609 -lfs
610 -lfs
610 +# Node ID 9640b57e77b14c3a0144fb4478b6cc13e13ea0d1
611 +# Node ID 9640b57e77b14c3a0144fb4478b6cc13e13ea0d1
611 +# Parent d3b84d50eacbd56638e11abce6b8616aaba54420
612 +# Parent d3b84d50eacbd56638e11abce6b8616aaba54420
612 +add lfs pair
613 +add lfs pair
613
614
614 -diff -r 1477875038c6 -r 8374dc4052cb lfs.bin
615 -diff -r 1477875038c6 -r 8374dc4052cb lfs.bin
615 ---- /dev/null Thu Jan 01 00:00:00 1970 +0000
616 ---- /dev/null Thu Jan 01 00:00:00 1970 +0000
616 -+++ b/lfs.bin Thu Jan 01 00:00:00 1970 +0000
617 -+++ b/lfs.bin Thu Jan 01 00:00:00 1970 +0000
617 -@@ -0,0 +1,1 @@
618 -@@ -0,0 +1,1 @@
618 -+this is a big lfs file
619 -+this is a big lfs file
619 cleaning up temp directory
620 cleaning up temp directory
620 [1]
621 [1]
621
622
622 Diff will prefetch files
623 Diff will prefetch files
623
624
624 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
625 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
625 $ hg -R $TESTTMP/bulkfetch -v diff -r 2:tip
626 $ hg -R $TESTTMP/bulkfetch -v diff -r 2:tip
626 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
627 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
627 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
628 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
628 lfs: need to transfer 4 objects (92 bytes)
629 lfs: need to transfer 4 objects (92 bytes)
629 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
630 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
630 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
631 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
631 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
632 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
632 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
633 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
633 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
634 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
634 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
635 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
635 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
636 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
636 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
637 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
637 lfs: downloaded 4 files (92 bytes)
638 lfs: downloaded 4 files (92 bytes)
638 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
639 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
639 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
640 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
640 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
641 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
641 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
642 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
642 diff -r 8374dc4052cb -r 9640b57e77b1 lfs.bin
643 diff -r 8374dc4052cb -r 9640b57e77b1 lfs.bin
643 --- a/lfs.bin Thu Jan 01 00:00:00 1970 +0000
644 --- a/lfs.bin Thu Jan 01 00:00:00 1970 +0000
644 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
645 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
645 @@ -1,1 +0,0 @@
646 @@ -1,1 +0,0 @@
646 -this is a big lfs file
647 -this is a big lfs file
647 diff -r 8374dc4052cb -r 9640b57e77b1 lfs2.txt
648 diff -r 8374dc4052cb -r 9640b57e77b1 lfs2.txt
648 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
649 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
649 +++ b/lfs2.txt Thu Jan 01 00:00:00 1970 +0000
650 +++ b/lfs2.txt Thu Jan 01 00:00:00 1970 +0000
650 @@ -0,0 +1,1 @@
651 @@ -0,0 +1,1 @@
651 +this is another lfs file
652 +this is another lfs file
652 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair1.bin
653 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair1.bin
653 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
654 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
654 +++ b/lfspair1.bin Thu Jan 01 00:00:00 1970 +0000
655 +++ b/lfspair1.bin Thu Jan 01 00:00:00 1970 +0000
655 @@ -0,0 +1,1 @@
656 @@ -0,0 +1,1 @@
656 +this is an lfs file
657 +this is an lfs file
657 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair2.bin
658 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair2.bin
658 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
659 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
659 +++ b/lfspair2.bin Thu Jan 01 00:00:00 1970 +0000
660 +++ b/lfspair2.bin Thu Jan 01 00:00:00 1970 +0000
660 @@ -0,0 +1,1 @@
661 @@ -0,0 +1,1 @@
661 +this is an lfs file too
662 +this is an lfs file too
662 diff -r 8374dc4052cb -r 9640b57e77b1 nonlfs.txt
663 diff -r 8374dc4052cb -r 9640b57e77b1 nonlfs.txt
663 --- a/nonlfs.txt Thu Jan 01 00:00:00 1970 +0000
664 --- a/nonlfs.txt Thu Jan 01 00:00:00 1970 +0000
664 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
665 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
665 @@ -1,1 +0,0 @@
666 @@ -1,1 +0,0 @@
666 -non-lfs
667 -non-lfs
667 diff -r 8374dc4052cb -r 9640b57e77b1 nonlfs3.txt
668 diff -r 8374dc4052cb -r 9640b57e77b1 nonlfs3.txt
668 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
669 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
669 +++ b/nonlfs3.txt Thu Jan 01 00:00:00 1970 +0000
670 +++ b/nonlfs3.txt Thu Jan 01 00:00:00 1970 +0000
670 @@ -0,0 +1,1 @@
671 @@ -0,0 +1,1 @@
671 +non-lfs
672 +non-lfs
672
673
673 Only the files required by diff are prefetched
674 Only the files required by diff are prefetched
674
675
675 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
676 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
676 $ hg -R $TESTTMP/bulkfetch -v diff -r 2:tip $TESTTMP/bulkfetch/lfspair2.bin
677 $ hg -R $TESTTMP/bulkfetch -v diff -r 2:tip $TESTTMP/bulkfetch/lfspair2.bin
677 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
678 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
678 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
679 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
679 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
680 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
680 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
681 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
681 lfs: downloaded 1 files (24 bytes)
682 lfs: downloaded 1 files (24 bytes)
682 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
683 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
683 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair2.bin
684 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair2.bin
684 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
685 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
685 +++ b/lfspair2.bin Thu Jan 01 00:00:00 1970 +0000
686 +++ b/lfspair2.bin Thu Jan 01 00:00:00 1970 +0000
686 @@ -0,0 +1,1 @@
687 @@ -0,0 +1,1 @@
687 +this is an lfs file too
688 +this is an lfs file too
688
689
689 #endif
690 #endif
690
691
691 $ "$PYTHON" $TESTDIR/killdaemons.py $DAEMON_PIDS
692 $ "$PYTHON" $TESTDIR/killdaemons.py $DAEMON_PIDS
692
693
693 $ cat $TESTTMP/errors.log
694 $ cat $TESTTMP/errors.log
General Comments 0
You need to be logged in to leave comments. Login now