##// END OF EJS Templates
hg: raise Abort on invalid path...
Gregory Szorc -
r41625:7f366dd3 default
parent child Browse files
Show More
@@ -1,1225 +1,1234
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 hashlib
12 import hashlib
13 import os
13 import os
14 import shutil
14 import shutil
15 import stat
15 import stat
16
16
17 from .i18n import _
17 from .i18n import _
18 from .node import (
18 from .node import (
19 nullid,
19 nullid,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 bookmarks,
23 bookmarks,
24 bundlerepo,
24 bundlerepo,
25 cacheutil,
25 cacheutil,
26 cmdutil,
26 cmdutil,
27 destutil,
27 destutil,
28 discovery,
28 discovery,
29 error,
29 error,
30 exchange,
30 exchange,
31 extensions,
31 extensions,
32 httppeer,
32 httppeer,
33 localrepo,
33 localrepo,
34 lock,
34 lock,
35 logcmdutil,
35 logcmdutil,
36 logexchange,
36 logexchange,
37 merge as mergemod,
37 merge as mergemod,
38 narrowspec,
38 narrowspec,
39 node,
39 node,
40 phases,
40 phases,
41 pycompat,
41 repository as repositorymod,
42 repository as repositorymod,
42 scmutil,
43 scmutil,
43 sshpeer,
44 sshpeer,
44 statichttprepo,
45 statichttprepo,
45 ui as uimod,
46 ui as uimod,
46 unionrepo,
47 unionrepo,
47 url,
48 url,
48 util,
49 util,
49 verify as verifymod,
50 verify as verifymod,
50 vfs as vfsmod,
51 vfs as vfsmod,
51 )
52 )
52
53
53 release = lock.release
54 release = lock.release
54
55
55 # shared features
56 # shared features
56 sharedbookmarks = 'bookmarks'
57 sharedbookmarks = 'bookmarks'
57
58
58 def _local(path):
59 def _local(path):
59 path = util.expandpath(util.urllocalpath(path))
60 path = util.expandpath(util.urllocalpath(path))
60 return (os.path.isfile(path) and bundlerepo or localrepo)
61
62 try:
63 isfile = os.path.isfile(path)
64 # Python 2 raises TypeError, Python 3 ValueError.
65 except (TypeError, ValueError) as e:
66 raise error.Abort(_('invalid path %s: %s') % (
67 path, pycompat.bytestr(e)))
68
69 return isfile and bundlerepo or localrepo
61
70
62 def addbranchrevs(lrepo, other, branches, revs):
71 def addbranchrevs(lrepo, other, branches, revs):
63 peer = other.peer() # a courtesy to callers using a localrepo for other
72 peer = other.peer() # a courtesy to callers using a localrepo for other
64 hashbranch, branches = branches
73 hashbranch, branches = branches
65 if not hashbranch and not branches:
74 if not hashbranch and not branches:
66 x = revs or None
75 x = revs or None
67 if revs:
76 if revs:
68 y = revs[0]
77 y = revs[0]
69 else:
78 else:
70 y = None
79 y = None
71 return x, y
80 return x, y
72 if revs:
81 if revs:
73 revs = list(revs)
82 revs = list(revs)
74 else:
83 else:
75 revs = []
84 revs = []
76
85
77 if not peer.capable('branchmap'):
86 if not peer.capable('branchmap'):
78 if branches:
87 if branches:
79 raise error.Abort(_("remote branch lookup not supported"))
88 raise error.Abort(_("remote branch lookup not supported"))
80 revs.append(hashbranch)
89 revs.append(hashbranch)
81 return revs, revs[0]
90 return revs, revs[0]
82
91
83 with peer.commandexecutor() as e:
92 with peer.commandexecutor() as e:
84 branchmap = e.callcommand('branchmap', {}).result()
93 branchmap = e.callcommand('branchmap', {}).result()
85
94
86 def primary(branch):
95 def primary(branch):
87 if branch == '.':
96 if branch == '.':
88 if not lrepo:
97 if not lrepo:
89 raise error.Abort(_("dirstate branch not accessible"))
98 raise error.Abort(_("dirstate branch not accessible"))
90 branch = lrepo.dirstate.branch()
99 branch = lrepo.dirstate.branch()
91 if branch in branchmap:
100 if branch in branchmap:
92 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
101 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
93 return True
102 return True
94 else:
103 else:
95 return False
104 return False
96
105
97 for branch in branches:
106 for branch in branches:
98 if not primary(branch):
107 if not primary(branch):
99 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
108 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
100 if hashbranch:
109 if hashbranch:
101 if not primary(hashbranch):
110 if not primary(hashbranch):
102 revs.append(hashbranch)
111 revs.append(hashbranch)
103 return revs, revs[0]
112 return revs, revs[0]
104
113
105 def parseurl(path, branches=None):
114 def parseurl(path, branches=None):
106 '''parse url#branch, returning (url, (branch, branches))'''
115 '''parse url#branch, returning (url, (branch, branches))'''
107
116
108 u = util.url(path)
117 u = util.url(path)
109 branch = None
118 branch = None
110 if u.fragment:
119 if u.fragment:
111 branch = u.fragment
120 branch = u.fragment
112 u.fragment = None
121 u.fragment = None
113 return bytes(u), (branch, branches or [])
122 return bytes(u), (branch, branches or [])
114
123
115 schemes = {
124 schemes = {
116 'bundle': bundlerepo,
125 'bundle': bundlerepo,
117 'union': unionrepo,
126 'union': unionrepo,
118 'file': _local,
127 'file': _local,
119 'http': httppeer,
128 'http': httppeer,
120 'https': httppeer,
129 'https': httppeer,
121 'ssh': sshpeer,
130 'ssh': sshpeer,
122 'static-http': statichttprepo,
131 'static-http': statichttprepo,
123 }
132 }
124
133
125 def _peerlookup(path):
134 def _peerlookup(path):
126 u = util.url(path)
135 u = util.url(path)
127 scheme = u.scheme or 'file'
136 scheme = u.scheme or 'file'
128 thing = schemes.get(scheme) or schemes['file']
137 thing = schemes.get(scheme) or schemes['file']
129 try:
138 try:
130 return thing(path)
139 return thing(path)
131 except TypeError:
140 except TypeError:
132 # we can't test callable(thing) because 'thing' can be an unloaded
141 # we can't test callable(thing) because 'thing' can be an unloaded
133 # module that implements __call__
142 # module that implements __call__
134 if not util.safehasattr(thing, 'instance'):
143 if not util.safehasattr(thing, 'instance'):
135 raise
144 raise
136 return thing
145 return thing
137
146
138 def islocal(repo):
147 def islocal(repo):
139 '''return true if repo (or path pointing to repo) is local'''
148 '''return true if repo (or path pointing to repo) is local'''
140 if isinstance(repo, bytes):
149 if isinstance(repo, bytes):
141 try:
150 try:
142 return _peerlookup(repo).islocal(repo)
151 return _peerlookup(repo).islocal(repo)
143 except AttributeError:
152 except AttributeError:
144 return False
153 return False
145 return repo.local()
154 return repo.local()
146
155
147 def openpath(ui, path):
156 def openpath(ui, path):
148 '''open path with open if local, url.open if remote'''
157 '''open path with open if local, url.open if remote'''
149 pathurl = util.url(path, parsequery=False, parsefragment=False)
158 pathurl = util.url(path, parsequery=False, parsefragment=False)
150 if pathurl.islocal():
159 if pathurl.islocal():
151 return util.posixfile(pathurl.localpath(), 'rb')
160 return util.posixfile(pathurl.localpath(), 'rb')
152 else:
161 else:
153 return url.open(ui, path)
162 return url.open(ui, path)
154
163
155 # a list of (ui, repo) functions called for wire peer initialization
164 # a list of (ui, repo) functions called for wire peer initialization
156 wirepeersetupfuncs = []
165 wirepeersetupfuncs = []
157
166
158 def _peerorrepo(ui, path, create=False, presetupfuncs=None,
167 def _peerorrepo(ui, path, create=False, presetupfuncs=None,
159 intents=None, createopts=None):
168 intents=None, createopts=None):
160 """return a repository object for the specified path"""
169 """return a repository object for the specified path"""
161 obj = _peerlookup(path).instance(ui, path, create, intents=intents,
170 obj = _peerlookup(path).instance(ui, path, create, intents=intents,
162 createopts=createopts)
171 createopts=createopts)
163 ui = getattr(obj, "ui", ui)
172 ui = getattr(obj, "ui", ui)
164 for f in presetupfuncs or []:
173 for f in presetupfuncs or []:
165 f(ui, obj)
174 f(ui, obj)
166 ui.log(b'extension', b'- executing reposetup hooks\n')
175 ui.log(b'extension', b'- executing reposetup hooks\n')
167 with util.timedcm('all reposetup') as allreposetupstats:
176 with util.timedcm('all reposetup') as allreposetupstats:
168 for name, module in extensions.extensions(ui):
177 for name, module in extensions.extensions(ui):
169 ui.log(b'extension', b' - running reposetup for %s\n', name)
178 ui.log(b'extension', b' - running reposetup for %s\n', name)
170 hook = getattr(module, 'reposetup', None)
179 hook = getattr(module, 'reposetup', None)
171 if hook:
180 if hook:
172 with util.timedcm('reposetup %r', name) as stats:
181 with util.timedcm('reposetup %r', name) as stats:
173 hook(ui, obj)
182 hook(ui, obj)
174 ui.log(b'extension', b' > reposetup for %s took %s\n',
183 ui.log(b'extension', b' > reposetup for %s took %s\n',
175 name, stats)
184 name, stats)
176 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
185 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
177 if not obj.local():
186 if not obj.local():
178 for f in wirepeersetupfuncs:
187 for f in wirepeersetupfuncs:
179 f(ui, obj)
188 f(ui, obj)
180 return obj
189 return obj
181
190
182 def repository(ui, path='', create=False, presetupfuncs=None, intents=None,
191 def repository(ui, path='', create=False, presetupfuncs=None, intents=None,
183 createopts=None):
192 createopts=None):
184 """return a repository object for the specified path"""
193 """return a repository object for the specified path"""
185 peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs,
194 peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs,
186 intents=intents, createopts=createopts)
195 intents=intents, createopts=createopts)
187 repo = peer.local()
196 repo = peer.local()
188 if not repo:
197 if not repo:
189 raise error.Abort(_("repository '%s' is not local") %
198 raise error.Abort(_("repository '%s' is not local") %
190 (path or peer.url()))
199 (path or peer.url()))
191 return repo.filtered('visible')
200 return repo.filtered('visible')
192
201
193 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
202 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
194 '''return a repository peer for the specified path'''
203 '''return a repository peer for the specified path'''
195 rui = remoteui(uiorrepo, opts)
204 rui = remoteui(uiorrepo, opts)
196 return _peerorrepo(rui, path, create, intents=intents,
205 return _peerorrepo(rui, path, create, intents=intents,
197 createopts=createopts).peer()
206 createopts=createopts).peer()
198
207
199 def defaultdest(source):
208 def defaultdest(source):
200 '''return default destination of clone if none is given
209 '''return default destination of clone if none is given
201
210
202 >>> defaultdest(b'foo')
211 >>> defaultdest(b'foo')
203 'foo'
212 'foo'
204 >>> defaultdest(b'/foo/bar')
213 >>> defaultdest(b'/foo/bar')
205 'bar'
214 'bar'
206 >>> defaultdest(b'/')
215 >>> defaultdest(b'/')
207 ''
216 ''
208 >>> defaultdest(b'')
217 >>> defaultdest(b'')
209 ''
218 ''
210 >>> defaultdest(b'http://example.org/')
219 >>> defaultdest(b'http://example.org/')
211 ''
220 ''
212 >>> defaultdest(b'http://example.org/foo/')
221 >>> defaultdest(b'http://example.org/foo/')
213 'foo'
222 'foo'
214 '''
223 '''
215 path = util.url(source).path
224 path = util.url(source).path
216 if not path:
225 if not path:
217 return ''
226 return ''
218 return os.path.basename(os.path.normpath(path))
227 return os.path.basename(os.path.normpath(path))
219
228
220 def sharedreposource(repo):
229 def sharedreposource(repo):
221 """Returns repository object for source repository of a shared repo.
230 """Returns repository object for source repository of a shared repo.
222
231
223 If repo is not a shared repository, returns None.
232 If repo is not a shared repository, returns None.
224 """
233 """
225 if repo.sharedpath == repo.path:
234 if repo.sharedpath == repo.path:
226 return None
235 return None
227
236
228 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
237 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
229 return repo.srcrepo
238 return repo.srcrepo
230
239
231 # the sharedpath always ends in the .hg; we want the path to the repo
240 # the sharedpath always ends in the .hg; we want the path to the repo
232 source = repo.vfs.split(repo.sharedpath)[0]
241 source = repo.vfs.split(repo.sharedpath)[0]
233 srcurl, branches = parseurl(source)
242 srcurl, branches = parseurl(source)
234 srcrepo = repository(repo.ui, srcurl)
243 srcrepo = repository(repo.ui, srcurl)
235 repo.srcrepo = srcrepo
244 repo.srcrepo = srcrepo
236 return srcrepo
245 return srcrepo
237
246
238 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
247 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
239 relative=False):
248 relative=False):
240 '''create a shared repository'''
249 '''create a shared repository'''
241
250
242 if not islocal(source):
251 if not islocal(source):
243 raise error.Abort(_('can only share local repositories'))
252 raise error.Abort(_('can only share local repositories'))
244
253
245 if not dest:
254 if not dest:
246 dest = defaultdest(source)
255 dest = defaultdest(source)
247 else:
256 else:
248 dest = ui.expandpath(dest)
257 dest = ui.expandpath(dest)
249
258
250 if isinstance(source, bytes):
259 if isinstance(source, bytes):
251 origsource = ui.expandpath(source)
260 origsource = ui.expandpath(source)
252 source, branches = parseurl(origsource)
261 source, branches = parseurl(origsource)
253 srcrepo = repository(ui, source)
262 srcrepo = repository(ui, source)
254 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
263 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
255 else:
264 else:
256 srcrepo = source.local()
265 srcrepo = source.local()
257 checkout = None
266 checkout = None
258
267
259 shareditems = set()
268 shareditems = set()
260 if bookmarks:
269 if bookmarks:
261 shareditems.add(sharedbookmarks)
270 shareditems.add(sharedbookmarks)
262
271
263 r = repository(ui, dest, create=True, createopts={
272 r = repository(ui, dest, create=True, createopts={
264 'sharedrepo': srcrepo,
273 'sharedrepo': srcrepo,
265 'sharedrelative': relative,
274 'sharedrelative': relative,
266 'shareditems': shareditems,
275 'shareditems': shareditems,
267 })
276 })
268
277
269 postshare(srcrepo, r, defaultpath=defaultpath)
278 postshare(srcrepo, r, defaultpath=defaultpath)
270 r = repository(ui, dest)
279 r = repository(ui, dest)
271 _postshareupdate(r, update, checkout=checkout)
280 _postshareupdate(r, update, checkout=checkout)
272 return r
281 return r
273
282
274 def unshare(ui, repo):
283 def unshare(ui, repo):
275 """convert a shared repository to a normal one
284 """convert a shared repository to a normal one
276
285
277 Copy the store data to the repo and remove the sharedpath data.
286 Copy the store data to the repo and remove the sharedpath data.
278
287
279 Returns a new repository object representing the unshared repository.
288 Returns a new repository object representing the unshared repository.
280
289
281 The passed repository object is not usable after this function is
290 The passed repository object is not usable after this function is
282 called.
291 called.
283 """
292 """
284
293
285 with repo.lock():
294 with repo.lock():
286 # we use locks here because if we race with commit, we
295 # we use locks here because if we race with commit, we
287 # can end up with extra data in the cloned revlogs that's
296 # can end up with extra data in the cloned revlogs that's
288 # not pointed to by changesets, thus causing verify to
297 # not pointed to by changesets, thus causing verify to
289 # fail
298 # fail
290 destlock = copystore(ui, repo, repo.path)
299 destlock = copystore(ui, repo, repo.path)
291 with destlock or util.nullcontextmanager():
300 with destlock or util.nullcontextmanager():
292
301
293 sharefile = repo.vfs.join('sharedpath')
302 sharefile = repo.vfs.join('sharedpath')
294 util.rename(sharefile, sharefile + '.old')
303 util.rename(sharefile, sharefile + '.old')
295
304
296 repo.requirements.discard('shared')
305 repo.requirements.discard('shared')
297 repo.requirements.discard('relshared')
306 repo.requirements.discard('relshared')
298 repo._writerequirements()
307 repo._writerequirements()
299
308
300 # Removing share changes some fundamental properties of the repo instance.
309 # Removing share changes some fundamental properties of the repo instance.
301 # So we instantiate a new repo object and operate on it rather than
310 # So we instantiate a new repo object and operate on it rather than
302 # try to keep the existing repo usable.
311 # try to keep the existing repo usable.
303 newrepo = repository(repo.baseui, repo.root, create=False)
312 newrepo = repository(repo.baseui, repo.root, create=False)
304
313
305 # TODO: figure out how to access subrepos that exist, but were previously
314 # TODO: figure out how to access subrepos that exist, but were previously
306 # removed from .hgsub
315 # removed from .hgsub
307 c = newrepo['.']
316 c = newrepo['.']
308 subs = c.substate
317 subs = c.substate
309 for s in sorted(subs):
318 for s in sorted(subs):
310 c.sub(s).unshare()
319 c.sub(s).unshare()
311
320
312 localrepo.poisonrepository(repo)
321 localrepo.poisonrepository(repo)
313
322
314 return newrepo
323 return newrepo
315
324
316 def postshare(sourcerepo, destrepo, defaultpath=None):
325 def postshare(sourcerepo, destrepo, defaultpath=None):
317 """Called after a new shared repo is created.
326 """Called after a new shared repo is created.
318
327
319 The new repo only has a requirements file and pointer to the source.
328 The new repo only has a requirements file and pointer to the source.
320 This function configures additional shared data.
329 This function configures additional shared data.
321
330
322 Extensions can wrap this function and write additional entries to
331 Extensions can wrap this function and write additional entries to
323 destrepo/.hg/shared to indicate additional pieces of data to be shared.
332 destrepo/.hg/shared to indicate additional pieces of data to be shared.
324 """
333 """
325 default = defaultpath or sourcerepo.ui.config('paths', 'default')
334 default = defaultpath or sourcerepo.ui.config('paths', 'default')
326 if default:
335 if default:
327 template = ('[paths]\n'
336 template = ('[paths]\n'
328 'default = %s\n')
337 'default = %s\n')
329 destrepo.vfs.write('hgrc', util.tonativeeol(template % default))
338 destrepo.vfs.write('hgrc', util.tonativeeol(template % default))
330 if repositorymod.NARROW_REQUIREMENT in sourcerepo.requirements:
339 if repositorymod.NARROW_REQUIREMENT in sourcerepo.requirements:
331 with destrepo.wlock():
340 with destrepo.wlock():
332 narrowspec.copytoworkingcopy(destrepo)
341 narrowspec.copytoworkingcopy(destrepo)
333
342
334 def _postshareupdate(repo, update, checkout=None):
343 def _postshareupdate(repo, update, checkout=None):
335 """Maybe perform a working directory update after a shared repo is created.
344 """Maybe perform a working directory update after a shared repo is created.
336
345
337 ``update`` can be a boolean or a revision to update to.
346 ``update`` can be a boolean or a revision to update to.
338 """
347 """
339 if not update:
348 if not update:
340 return
349 return
341
350
342 repo.ui.status(_("updating working directory\n"))
351 repo.ui.status(_("updating working directory\n"))
343 if update is not True:
352 if update is not True:
344 checkout = update
353 checkout = update
345 for test in (checkout, 'default', 'tip'):
354 for test in (checkout, 'default', 'tip'):
346 if test is None:
355 if test is None:
347 continue
356 continue
348 try:
357 try:
349 uprev = repo.lookup(test)
358 uprev = repo.lookup(test)
350 break
359 break
351 except error.RepoLookupError:
360 except error.RepoLookupError:
352 continue
361 continue
353 _update(repo, uprev)
362 _update(repo, uprev)
354
363
355 def copystore(ui, srcrepo, destpath):
364 def copystore(ui, srcrepo, destpath):
356 '''copy files from store of srcrepo in destpath
365 '''copy files from store of srcrepo in destpath
357
366
358 returns destlock
367 returns destlock
359 '''
368 '''
360 destlock = None
369 destlock = None
361 try:
370 try:
362 hardlink = None
371 hardlink = None
363 topic = _('linking') if hardlink else _('copying')
372 topic = _('linking') if hardlink else _('copying')
364 with ui.makeprogress(topic, unit=_('files')) as progress:
373 with ui.makeprogress(topic, unit=_('files')) as progress:
365 num = 0
374 num = 0
366 srcpublishing = srcrepo.publishing()
375 srcpublishing = srcrepo.publishing()
367 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
376 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
368 dstvfs = vfsmod.vfs(destpath)
377 dstvfs = vfsmod.vfs(destpath)
369 for f in srcrepo.store.copylist():
378 for f in srcrepo.store.copylist():
370 if srcpublishing and f.endswith('phaseroots'):
379 if srcpublishing and f.endswith('phaseroots'):
371 continue
380 continue
372 dstbase = os.path.dirname(f)
381 dstbase = os.path.dirname(f)
373 if dstbase and not dstvfs.exists(dstbase):
382 if dstbase and not dstvfs.exists(dstbase):
374 dstvfs.mkdir(dstbase)
383 dstvfs.mkdir(dstbase)
375 if srcvfs.exists(f):
384 if srcvfs.exists(f):
376 if f.endswith('data'):
385 if f.endswith('data'):
377 # 'dstbase' may be empty (e.g. revlog format 0)
386 # 'dstbase' may be empty (e.g. revlog format 0)
378 lockfile = os.path.join(dstbase, "lock")
387 lockfile = os.path.join(dstbase, "lock")
379 # lock to avoid premature writing to the target
388 # lock to avoid premature writing to the target
380 destlock = lock.lock(dstvfs, lockfile)
389 destlock = lock.lock(dstvfs, lockfile)
381 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
390 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
382 hardlink, progress)
391 hardlink, progress)
383 num += n
392 num += n
384 if hardlink:
393 if hardlink:
385 ui.debug("linked %d files\n" % num)
394 ui.debug("linked %d files\n" % num)
386 else:
395 else:
387 ui.debug("copied %d files\n" % num)
396 ui.debug("copied %d files\n" % num)
388 return destlock
397 return destlock
389 except: # re-raises
398 except: # re-raises
390 release(destlock)
399 release(destlock)
391 raise
400 raise
392
401
393 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
402 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
394 rev=None, update=True, stream=False):
403 rev=None, update=True, stream=False):
395 """Perform a clone using a shared repo.
404 """Perform a clone using a shared repo.
396
405
397 The store for the repository will be located at <sharepath>/.hg. The
406 The store for the repository will be located at <sharepath>/.hg. The
398 specified revisions will be cloned or pulled from "source". A shared repo
407 specified revisions will be cloned or pulled from "source". A shared repo
399 will be created at "dest" and a working copy will be created if "update" is
408 will be created at "dest" and a working copy will be created if "update" is
400 True.
409 True.
401 """
410 """
402 revs = None
411 revs = None
403 if rev:
412 if rev:
404 if not srcpeer.capable('lookup'):
413 if not srcpeer.capable('lookup'):
405 raise error.Abort(_("src repository does not support "
414 raise error.Abort(_("src repository does not support "
406 "revision lookup and so doesn't "
415 "revision lookup and so doesn't "
407 "support clone by revision"))
416 "support clone by revision"))
408
417
409 # TODO this is batchable.
418 # TODO this is batchable.
410 remoterevs = []
419 remoterevs = []
411 for r in rev:
420 for r in rev:
412 with srcpeer.commandexecutor() as e:
421 with srcpeer.commandexecutor() as e:
413 remoterevs.append(e.callcommand('lookup', {
422 remoterevs.append(e.callcommand('lookup', {
414 'key': r,
423 'key': r,
415 }).result())
424 }).result())
416 revs = remoterevs
425 revs = remoterevs
417
426
418 # Obtain a lock before checking for or cloning the pooled repo otherwise
427 # Obtain a lock before checking for or cloning the pooled repo otherwise
419 # 2 clients may race creating or populating it.
428 # 2 clients may race creating or populating it.
420 pooldir = os.path.dirname(sharepath)
429 pooldir = os.path.dirname(sharepath)
421 # lock class requires the directory to exist.
430 # lock class requires the directory to exist.
422 try:
431 try:
423 util.makedir(pooldir, False)
432 util.makedir(pooldir, False)
424 except OSError as e:
433 except OSError as e:
425 if e.errno != errno.EEXIST:
434 if e.errno != errno.EEXIST:
426 raise
435 raise
427
436
428 poolvfs = vfsmod.vfs(pooldir)
437 poolvfs = vfsmod.vfs(pooldir)
429 basename = os.path.basename(sharepath)
438 basename = os.path.basename(sharepath)
430
439
431 with lock.lock(poolvfs, '%s.lock' % basename):
440 with lock.lock(poolvfs, '%s.lock' % basename):
432 if os.path.exists(sharepath):
441 if os.path.exists(sharepath):
433 ui.status(_('(sharing from existing pooled repository %s)\n') %
442 ui.status(_('(sharing from existing pooled repository %s)\n') %
434 basename)
443 basename)
435 else:
444 else:
436 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
445 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
437 # Always use pull mode because hardlinks in share mode don't work
446 # Always use pull mode because hardlinks in share mode don't work
438 # well. Never update because working copies aren't necessary in
447 # well. Never update because working copies aren't necessary in
439 # share mode.
448 # share mode.
440 clone(ui, peeropts, source, dest=sharepath, pull=True,
449 clone(ui, peeropts, source, dest=sharepath, pull=True,
441 revs=rev, update=False, stream=stream)
450 revs=rev, update=False, stream=stream)
442
451
443 # Resolve the value to put in [paths] section for the source.
452 # Resolve the value to put in [paths] section for the source.
444 if islocal(source):
453 if islocal(source):
445 defaultpath = os.path.abspath(util.urllocalpath(source))
454 defaultpath = os.path.abspath(util.urllocalpath(source))
446 else:
455 else:
447 defaultpath = source
456 defaultpath = source
448
457
449 sharerepo = repository(ui, path=sharepath)
458 sharerepo = repository(ui, path=sharepath)
450 destrepo = share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
459 destrepo = share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
451 defaultpath=defaultpath)
460 defaultpath=defaultpath)
452
461
453 # We need to perform a pull against the dest repo to fetch bookmarks
462 # We need to perform a pull against the dest repo to fetch bookmarks
454 # and other non-store data that isn't shared by default. In the case of
463 # and other non-store data that isn't shared by default. In the case of
455 # non-existing shared repo, this means we pull from the remote twice. This
464 # non-existing shared repo, this means we pull from the remote twice. This
456 # is a bit weird. But at the time it was implemented, there wasn't an easy
465 # is a bit weird. But at the time it was implemented, there wasn't an easy
457 # way to pull just non-changegroup data.
466 # way to pull just non-changegroup data.
458 exchange.pull(destrepo, srcpeer, heads=revs)
467 exchange.pull(destrepo, srcpeer, heads=revs)
459
468
460 _postshareupdate(destrepo, update)
469 _postshareupdate(destrepo, update)
461
470
462 return srcpeer, peer(ui, peeropts, dest)
471 return srcpeer, peer(ui, peeropts, dest)
463
472
464 # Recomputing branch cache might be slow on big repos,
473 # Recomputing branch cache might be slow on big repos,
465 # so just copy it
474 # so just copy it
466 def _copycache(srcrepo, dstcachedir, fname):
475 def _copycache(srcrepo, dstcachedir, fname):
467 """copy a cache from srcrepo to destcachedir (if it exists)"""
476 """copy a cache from srcrepo to destcachedir (if it exists)"""
468 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
477 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
469 dstbranchcache = os.path.join(dstcachedir, fname)
478 dstbranchcache = os.path.join(dstcachedir, fname)
470 if os.path.exists(srcbranchcache):
479 if os.path.exists(srcbranchcache):
471 if not os.path.exists(dstcachedir):
480 if not os.path.exists(dstcachedir):
472 os.mkdir(dstcachedir)
481 os.mkdir(dstcachedir)
473 util.copyfile(srcbranchcache, dstbranchcache)
482 util.copyfile(srcbranchcache, dstbranchcache)
474
483
475 def clone(ui, peeropts, source, dest=None, pull=False, revs=None,
484 def clone(ui, peeropts, source, dest=None, pull=False, revs=None,
476 update=True, stream=False, branch=None, shareopts=None,
485 update=True, stream=False, branch=None, shareopts=None,
477 storeincludepats=None, storeexcludepats=None, depth=None):
486 storeincludepats=None, storeexcludepats=None, depth=None):
478 """Make a copy of an existing repository.
487 """Make a copy of an existing repository.
479
488
480 Create a copy of an existing repository in a new directory. The
489 Create a copy of an existing repository in a new directory. The
481 source and destination are URLs, as passed to the repository
490 source and destination are URLs, as passed to the repository
482 function. Returns a pair of repository peers, the source and
491 function. Returns a pair of repository peers, the source and
483 newly created destination.
492 newly created destination.
484
493
485 The location of the source is added to the new repository's
494 The location of the source is added to the new repository's
486 .hg/hgrc file, as the default to be used for future pulls and
495 .hg/hgrc file, as the default to be used for future pulls and
487 pushes.
496 pushes.
488
497
489 If an exception is raised, the partly cloned/updated destination
498 If an exception is raised, the partly cloned/updated destination
490 repository will be deleted.
499 repository will be deleted.
491
500
492 Arguments:
501 Arguments:
493
502
494 source: repository object or URL
503 source: repository object or URL
495
504
496 dest: URL of destination repository to create (defaults to base
505 dest: URL of destination repository to create (defaults to base
497 name of source repository)
506 name of source repository)
498
507
499 pull: always pull from source repository, even in local case or if the
508 pull: always pull from source repository, even in local case or if the
500 server prefers streaming
509 server prefers streaming
501
510
502 stream: stream raw data uncompressed from repository (fast over
511 stream: stream raw data uncompressed from repository (fast over
503 LAN, slow over WAN)
512 LAN, slow over WAN)
504
513
505 revs: revision to clone up to (implies pull=True)
514 revs: revision to clone up to (implies pull=True)
506
515
507 update: update working directory after clone completes, if
516 update: update working directory after clone completes, if
508 destination is local repository (True means update to default rev,
517 destination is local repository (True means update to default rev,
509 anything else is treated as a revision)
518 anything else is treated as a revision)
510
519
511 branch: branches to clone
520 branch: branches to clone
512
521
513 shareopts: dict of options to control auto sharing behavior. The "pool" key
522 shareopts: dict of options to control auto sharing behavior. The "pool" key
514 activates auto sharing mode and defines the directory for stores. The
523 activates auto sharing mode and defines the directory for stores. The
515 "mode" key determines how to construct the directory name of the shared
524 "mode" key determines how to construct the directory name of the shared
516 repository. "identity" means the name is derived from the node of the first
525 repository. "identity" means the name is derived from the node of the first
517 changeset in the repository. "remote" means the name is derived from the
526 changeset in the repository. "remote" means the name is derived from the
518 remote's path/URL. Defaults to "identity."
527 remote's path/URL. Defaults to "identity."
519
528
520 storeincludepats and storeexcludepats: sets of file patterns to include and
529 storeincludepats and storeexcludepats: sets of file patterns to include and
521 exclude in the repository copy, respectively. If not defined, all files
530 exclude in the repository copy, respectively. If not defined, all files
522 will be included (a "full" clone). Otherwise a "narrow" clone containing
531 will be included (a "full" clone). Otherwise a "narrow" clone containing
523 only the requested files will be performed. If ``storeincludepats`` is not
532 only the requested files will be performed. If ``storeincludepats`` is not
524 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
533 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
525 ``path:.``. If both are empty sets, no files will be cloned.
534 ``path:.``. If both are empty sets, no files will be cloned.
526 """
535 """
527
536
528 if isinstance(source, bytes):
537 if isinstance(source, bytes):
529 origsource = ui.expandpath(source)
538 origsource = ui.expandpath(source)
530 source, branches = parseurl(origsource, branch)
539 source, branches = parseurl(origsource, branch)
531 srcpeer = peer(ui, peeropts, source)
540 srcpeer = peer(ui, peeropts, source)
532 else:
541 else:
533 srcpeer = source.peer() # in case we were called with a localrepo
542 srcpeer = source.peer() # in case we were called with a localrepo
534 branches = (None, branch or [])
543 branches = (None, branch or [])
535 origsource = source = srcpeer.url()
544 origsource = source = srcpeer.url()
536 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
545 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
537
546
538 if dest is None:
547 if dest is None:
539 dest = defaultdest(source)
548 dest = defaultdest(source)
540 if dest:
549 if dest:
541 ui.status(_("destination directory: %s\n") % dest)
550 ui.status(_("destination directory: %s\n") % dest)
542 else:
551 else:
543 dest = ui.expandpath(dest)
552 dest = ui.expandpath(dest)
544
553
545 dest = util.urllocalpath(dest)
554 dest = util.urllocalpath(dest)
546 source = util.urllocalpath(source)
555 source = util.urllocalpath(source)
547
556
548 if not dest:
557 if not dest:
549 raise error.Abort(_("empty destination path is not valid"))
558 raise error.Abort(_("empty destination path is not valid"))
550
559
551 destvfs = vfsmod.vfs(dest, expandpath=True)
560 destvfs = vfsmod.vfs(dest, expandpath=True)
552 if destvfs.lexists():
561 if destvfs.lexists():
553 if not destvfs.isdir():
562 if not destvfs.isdir():
554 raise error.Abort(_("destination '%s' already exists") % dest)
563 raise error.Abort(_("destination '%s' already exists") % dest)
555 elif destvfs.listdir():
564 elif destvfs.listdir():
556 raise error.Abort(_("destination '%s' is not empty") % dest)
565 raise error.Abort(_("destination '%s' is not empty") % dest)
557
566
558 createopts = {}
567 createopts = {}
559 narrow = False
568 narrow = False
560
569
561 if storeincludepats is not None:
570 if storeincludepats is not None:
562 narrowspec.validatepatterns(storeincludepats)
571 narrowspec.validatepatterns(storeincludepats)
563 narrow = True
572 narrow = True
564
573
565 if storeexcludepats is not None:
574 if storeexcludepats is not None:
566 narrowspec.validatepatterns(storeexcludepats)
575 narrowspec.validatepatterns(storeexcludepats)
567 narrow = True
576 narrow = True
568
577
569 if narrow:
578 if narrow:
570 # Include everything by default if only exclusion patterns defined.
579 # Include everything by default if only exclusion patterns defined.
571 if storeexcludepats and not storeincludepats:
580 if storeexcludepats and not storeincludepats:
572 storeincludepats = {'path:.'}
581 storeincludepats = {'path:.'}
573
582
574 createopts['narrowfiles'] = True
583 createopts['narrowfiles'] = True
575
584
576 if depth:
585 if depth:
577 createopts['shallowfilestore'] = True
586 createopts['shallowfilestore'] = True
578
587
579 if srcpeer.capable(b'lfs-serve'):
588 if srcpeer.capable(b'lfs-serve'):
580 # Repository creation honors the config if it disabled the extension, so
589 # Repository creation honors the config if it disabled the extension, so
581 # we can't just announce that lfs will be enabled. This check avoids
590 # we can't just announce that lfs will be enabled. This check avoids
582 # saying that lfs will be enabled, and then saying it's an unknown
591 # saying that lfs will be enabled, and then saying it's an unknown
583 # feature. The lfs creation option is set in either case so that a
592 # feature. The lfs creation option is set in either case so that a
584 # requirement is added. If the extension is explicitly disabled but the
593 # requirement is added. If the extension is explicitly disabled but the
585 # requirement is set, the clone aborts early, before transferring any
594 # requirement is set, the clone aborts early, before transferring any
586 # data.
595 # data.
587 createopts['lfs'] = True
596 createopts['lfs'] = True
588
597
589 if extensions.disabledext('lfs'):
598 if extensions.disabledext('lfs'):
590 ui.status(_('(remote is using large file support (lfs), but it is '
599 ui.status(_('(remote is using large file support (lfs), but it is '
591 'explicitly disabled in the local configuration)\n'))
600 'explicitly disabled in the local configuration)\n'))
592 else:
601 else:
593 ui.status(_('(remote is using large file support (lfs); lfs will '
602 ui.status(_('(remote is using large file support (lfs); lfs will '
594 'be enabled for this repository)\n'))
603 'be enabled for this repository)\n'))
595
604
596 shareopts = shareopts or {}
605 shareopts = shareopts or {}
597 sharepool = shareopts.get('pool')
606 sharepool = shareopts.get('pool')
598 sharenamemode = shareopts.get('mode')
607 sharenamemode = shareopts.get('mode')
599 if sharepool and islocal(dest):
608 if sharepool and islocal(dest):
600 sharepath = None
609 sharepath = None
601 if sharenamemode == 'identity':
610 if sharenamemode == 'identity':
602 # Resolve the name from the initial changeset in the remote
611 # Resolve the name from the initial changeset in the remote
603 # repository. This returns nullid when the remote is empty. It
612 # repository. This returns nullid when the remote is empty. It
604 # raises RepoLookupError if revision 0 is filtered or otherwise
613 # raises RepoLookupError if revision 0 is filtered or otherwise
605 # not available. If we fail to resolve, sharing is not enabled.
614 # not available. If we fail to resolve, sharing is not enabled.
606 try:
615 try:
607 with srcpeer.commandexecutor() as e:
616 with srcpeer.commandexecutor() as e:
608 rootnode = e.callcommand('lookup', {
617 rootnode = e.callcommand('lookup', {
609 'key': '0',
618 'key': '0',
610 }).result()
619 }).result()
611
620
612 if rootnode != node.nullid:
621 if rootnode != node.nullid:
613 sharepath = os.path.join(sharepool, node.hex(rootnode))
622 sharepath = os.path.join(sharepool, node.hex(rootnode))
614 else:
623 else:
615 ui.status(_('(not using pooled storage: '
624 ui.status(_('(not using pooled storage: '
616 'remote appears to be empty)\n'))
625 'remote appears to be empty)\n'))
617 except error.RepoLookupError:
626 except error.RepoLookupError:
618 ui.status(_('(not using pooled storage: '
627 ui.status(_('(not using pooled storage: '
619 'unable to resolve identity of remote)\n'))
628 'unable to resolve identity of remote)\n'))
620 elif sharenamemode == 'remote':
629 elif sharenamemode == 'remote':
621 sharepath = os.path.join(
630 sharepath = os.path.join(
622 sharepool, node.hex(hashlib.sha1(source).digest()))
631 sharepool, node.hex(hashlib.sha1(source).digest()))
623 else:
632 else:
624 raise error.Abort(_('unknown share naming mode: %s') %
633 raise error.Abort(_('unknown share naming mode: %s') %
625 sharenamemode)
634 sharenamemode)
626
635
627 # TODO this is a somewhat arbitrary restriction.
636 # TODO this is a somewhat arbitrary restriction.
628 if narrow:
637 if narrow:
629 ui.status(_('(pooled storage not supported for narrow clones)\n'))
638 ui.status(_('(pooled storage not supported for narrow clones)\n'))
630 sharepath = None
639 sharepath = None
631
640
632 if sharepath:
641 if sharepath:
633 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
642 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
634 dest, pull=pull, rev=revs, update=update,
643 dest, pull=pull, rev=revs, update=update,
635 stream=stream)
644 stream=stream)
636
645
637 srclock = destlock = cleandir = None
646 srclock = destlock = cleandir = None
638 srcrepo = srcpeer.local()
647 srcrepo = srcpeer.local()
639 try:
648 try:
640 abspath = origsource
649 abspath = origsource
641 if islocal(origsource):
650 if islocal(origsource):
642 abspath = os.path.abspath(util.urllocalpath(origsource))
651 abspath = os.path.abspath(util.urllocalpath(origsource))
643
652
644 if islocal(dest):
653 if islocal(dest):
645 cleandir = dest
654 cleandir = dest
646
655
647 copy = False
656 copy = False
648 if (srcrepo and srcrepo.cancopy() and islocal(dest)
657 if (srcrepo and srcrepo.cancopy() and islocal(dest)
649 and not phases.hassecret(srcrepo)):
658 and not phases.hassecret(srcrepo)):
650 copy = not pull and not revs
659 copy = not pull and not revs
651
660
652 # TODO this is a somewhat arbitrary restriction.
661 # TODO this is a somewhat arbitrary restriction.
653 if narrow:
662 if narrow:
654 copy = False
663 copy = False
655
664
656 if copy:
665 if copy:
657 try:
666 try:
658 # we use a lock here because if we race with commit, we
667 # we use a lock here because if we race with commit, we
659 # can end up with extra data in the cloned revlogs that's
668 # can end up with extra data in the cloned revlogs that's
660 # not pointed to by changesets, thus causing verify to
669 # not pointed to by changesets, thus causing verify to
661 # fail
670 # fail
662 srclock = srcrepo.lock(wait=False)
671 srclock = srcrepo.lock(wait=False)
663 except error.LockError:
672 except error.LockError:
664 copy = False
673 copy = False
665
674
666 if copy:
675 if copy:
667 srcrepo.hook('preoutgoing', throw=True, source='clone')
676 srcrepo.hook('preoutgoing', throw=True, source='clone')
668 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
677 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
669 if not os.path.exists(dest):
678 if not os.path.exists(dest):
670 util.makedirs(dest)
679 util.makedirs(dest)
671 else:
680 else:
672 # only clean up directories we create ourselves
681 # only clean up directories we create ourselves
673 cleandir = hgdir
682 cleandir = hgdir
674 try:
683 try:
675 destpath = hgdir
684 destpath = hgdir
676 util.makedir(destpath, notindexed=True)
685 util.makedir(destpath, notindexed=True)
677 except OSError as inst:
686 except OSError as inst:
678 if inst.errno == errno.EEXIST:
687 if inst.errno == errno.EEXIST:
679 cleandir = None
688 cleandir = None
680 raise error.Abort(_("destination '%s' already exists")
689 raise error.Abort(_("destination '%s' already exists")
681 % dest)
690 % dest)
682 raise
691 raise
683
692
684 destlock = copystore(ui, srcrepo, destpath)
693 destlock = copystore(ui, srcrepo, destpath)
685 # copy bookmarks over
694 # copy bookmarks over
686 srcbookmarks = srcrepo.vfs.join('bookmarks')
695 srcbookmarks = srcrepo.vfs.join('bookmarks')
687 dstbookmarks = os.path.join(destpath, 'bookmarks')
696 dstbookmarks = os.path.join(destpath, 'bookmarks')
688 if os.path.exists(srcbookmarks):
697 if os.path.exists(srcbookmarks):
689 util.copyfile(srcbookmarks, dstbookmarks)
698 util.copyfile(srcbookmarks, dstbookmarks)
690
699
691 dstcachedir = os.path.join(destpath, 'cache')
700 dstcachedir = os.path.join(destpath, 'cache')
692 for cache in cacheutil.cachetocopy(srcrepo):
701 for cache in cacheutil.cachetocopy(srcrepo):
693 _copycache(srcrepo, dstcachedir, cache)
702 _copycache(srcrepo, dstcachedir, cache)
694
703
695 # we need to re-init the repo after manually copying the data
704 # we need to re-init the repo after manually copying the data
696 # into it
705 # into it
697 destpeer = peer(srcrepo, peeropts, dest)
706 destpeer = peer(srcrepo, peeropts, dest)
698 srcrepo.hook('outgoing', source='clone',
707 srcrepo.hook('outgoing', source='clone',
699 node=node.hex(node.nullid))
708 node=node.hex(node.nullid))
700 else:
709 else:
701 try:
710 try:
702 # only pass ui when no srcrepo
711 # only pass ui when no srcrepo
703 destpeer = peer(srcrepo or ui, peeropts, dest, create=True,
712 destpeer = peer(srcrepo or ui, peeropts, dest, create=True,
704 createopts=createopts)
713 createopts=createopts)
705 except OSError as inst:
714 except OSError as inst:
706 if inst.errno == errno.EEXIST:
715 if inst.errno == errno.EEXIST:
707 cleandir = None
716 cleandir = None
708 raise error.Abort(_("destination '%s' already exists")
717 raise error.Abort(_("destination '%s' already exists")
709 % dest)
718 % dest)
710 raise
719 raise
711
720
712 if revs:
721 if revs:
713 if not srcpeer.capable('lookup'):
722 if not srcpeer.capable('lookup'):
714 raise error.Abort(_("src repository does not support "
723 raise error.Abort(_("src repository does not support "
715 "revision lookup and so doesn't "
724 "revision lookup and so doesn't "
716 "support clone by revision"))
725 "support clone by revision"))
717
726
718 # TODO this is batchable.
727 # TODO this is batchable.
719 remoterevs = []
728 remoterevs = []
720 for rev in revs:
729 for rev in revs:
721 with srcpeer.commandexecutor() as e:
730 with srcpeer.commandexecutor() as e:
722 remoterevs.append(e.callcommand('lookup', {
731 remoterevs.append(e.callcommand('lookup', {
723 'key': rev,
732 'key': rev,
724 }).result())
733 }).result())
725 revs = remoterevs
734 revs = remoterevs
726
735
727 checkout = revs[0]
736 checkout = revs[0]
728 else:
737 else:
729 revs = None
738 revs = None
730 local = destpeer.local()
739 local = destpeer.local()
731 if local:
740 if local:
732 if narrow:
741 if narrow:
733 with local.wlock(), local.lock():
742 with local.wlock(), local.lock():
734 local.setnarrowpats(storeincludepats, storeexcludepats)
743 local.setnarrowpats(storeincludepats, storeexcludepats)
735 narrowspec.copytoworkingcopy(local)
744 narrowspec.copytoworkingcopy(local)
736
745
737 u = util.url(abspath)
746 u = util.url(abspath)
738 defaulturl = bytes(u)
747 defaulturl = bytes(u)
739 local.ui.setconfig('paths', 'default', defaulturl, 'clone')
748 local.ui.setconfig('paths', 'default', defaulturl, 'clone')
740 if not stream:
749 if not stream:
741 if pull:
750 if pull:
742 stream = False
751 stream = False
743 else:
752 else:
744 stream = None
753 stream = None
745 # internal config: ui.quietbookmarkmove
754 # internal config: ui.quietbookmarkmove
746 overrides = {('ui', 'quietbookmarkmove'): True}
755 overrides = {('ui', 'quietbookmarkmove'): True}
747 with local.ui.configoverride(overrides, 'clone'):
756 with local.ui.configoverride(overrides, 'clone'):
748 exchange.pull(local, srcpeer, revs,
757 exchange.pull(local, srcpeer, revs,
749 streamclonerequested=stream,
758 streamclonerequested=stream,
750 includepats=storeincludepats,
759 includepats=storeincludepats,
751 excludepats=storeexcludepats,
760 excludepats=storeexcludepats,
752 depth=depth)
761 depth=depth)
753 elif srcrepo:
762 elif srcrepo:
754 # TODO lift restriction once exchange.push() accepts narrow
763 # TODO lift restriction once exchange.push() accepts narrow
755 # push.
764 # push.
756 if narrow:
765 if narrow:
757 raise error.Abort(_('narrow clone not available for '
766 raise error.Abort(_('narrow clone not available for '
758 'remote destinations'))
767 'remote destinations'))
759
768
760 exchange.push(srcrepo, destpeer, revs=revs,
769 exchange.push(srcrepo, destpeer, revs=revs,
761 bookmarks=srcrepo._bookmarks.keys())
770 bookmarks=srcrepo._bookmarks.keys())
762 else:
771 else:
763 raise error.Abort(_("clone from remote to remote not supported")
772 raise error.Abort(_("clone from remote to remote not supported")
764 )
773 )
765
774
766 cleandir = None
775 cleandir = None
767
776
768 destrepo = destpeer.local()
777 destrepo = destpeer.local()
769 if destrepo:
778 if destrepo:
770 template = uimod.samplehgrcs['cloned']
779 template = uimod.samplehgrcs['cloned']
771 u = util.url(abspath)
780 u = util.url(abspath)
772 u.passwd = None
781 u.passwd = None
773 defaulturl = bytes(u)
782 defaulturl = bytes(u)
774 destrepo.vfs.write('hgrc', util.tonativeeol(template % defaulturl))
783 destrepo.vfs.write('hgrc', util.tonativeeol(template % defaulturl))
775 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
784 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
776
785
777 if ui.configbool('experimental', 'remotenames'):
786 if ui.configbool('experimental', 'remotenames'):
778 logexchange.pullremotenames(destrepo, srcpeer)
787 logexchange.pullremotenames(destrepo, srcpeer)
779
788
780 if update:
789 if update:
781 if update is not True:
790 if update is not True:
782 with srcpeer.commandexecutor() as e:
791 with srcpeer.commandexecutor() as e:
783 checkout = e.callcommand('lookup', {
792 checkout = e.callcommand('lookup', {
784 'key': update,
793 'key': update,
785 }).result()
794 }).result()
786
795
787 uprev = None
796 uprev = None
788 status = None
797 status = None
789 if checkout is not None:
798 if checkout is not None:
790 # Some extensions (at least hg-git and hg-subversion) have
799 # Some extensions (at least hg-git and hg-subversion) have
791 # a peer.lookup() implementation that returns a name instead
800 # a peer.lookup() implementation that returns a name instead
792 # of a nodeid. We work around it here until we've figured
801 # of a nodeid. We work around it here until we've figured
793 # out a better solution.
802 # out a better solution.
794 if len(checkout) == 20 and checkout in destrepo:
803 if len(checkout) == 20 and checkout in destrepo:
795 uprev = checkout
804 uprev = checkout
796 elif scmutil.isrevsymbol(destrepo, checkout):
805 elif scmutil.isrevsymbol(destrepo, checkout):
797 uprev = scmutil.revsymbol(destrepo, checkout).node()
806 uprev = scmutil.revsymbol(destrepo, checkout).node()
798 else:
807 else:
799 if update is not True:
808 if update is not True:
800 try:
809 try:
801 uprev = destrepo.lookup(update)
810 uprev = destrepo.lookup(update)
802 except error.RepoLookupError:
811 except error.RepoLookupError:
803 pass
812 pass
804 if uprev is None:
813 if uprev is None:
805 try:
814 try:
806 uprev = destrepo._bookmarks['@']
815 uprev = destrepo._bookmarks['@']
807 update = '@'
816 update = '@'
808 bn = destrepo[uprev].branch()
817 bn = destrepo[uprev].branch()
809 if bn == 'default':
818 if bn == 'default':
810 status = _("updating to bookmark @\n")
819 status = _("updating to bookmark @\n")
811 else:
820 else:
812 status = (_("updating to bookmark @ on branch %s\n")
821 status = (_("updating to bookmark @ on branch %s\n")
813 % bn)
822 % bn)
814 except KeyError:
823 except KeyError:
815 try:
824 try:
816 uprev = destrepo.branchtip('default')
825 uprev = destrepo.branchtip('default')
817 except error.RepoLookupError:
826 except error.RepoLookupError:
818 uprev = destrepo.lookup('tip')
827 uprev = destrepo.lookup('tip')
819 if not status:
828 if not status:
820 bn = destrepo[uprev].branch()
829 bn = destrepo[uprev].branch()
821 status = _("updating to branch %s\n") % bn
830 status = _("updating to branch %s\n") % bn
822 destrepo.ui.status(status)
831 destrepo.ui.status(status)
823 _update(destrepo, uprev)
832 _update(destrepo, uprev)
824 if update in destrepo._bookmarks:
833 if update in destrepo._bookmarks:
825 bookmarks.activate(destrepo, update)
834 bookmarks.activate(destrepo, update)
826 finally:
835 finally:
827 release(srclock, destlock)
836 release(srclock, destlock)
828 if cleandir is not None:
837 if cleandir is not None:
829 shutil.rmtree(cleandir, True)
838 shutil.rmtree(cleandir, True)
830 if srcpeer is not None:
839 if srcpeer is not None:
831 srcpeer.close()
840 srcpeer.close()
832 return srcpeer, destpeer
841 return srcpeer, destpeer
833
842
834 def _showstats(repo, stats, quietempty=False):
843 def _showstats(repo, stats, quietempty=False):
835 if quietempty and stats.isempty():
844 if quietempty and stats.isempty():
836 return
845 return
837 repo.ui.status(_("%d files updated, %d files merged, "
846 repo.ui.status(_("%d files updated, %d files merged, "
838 "%d files removed, %d files unresolved\n") % (
847 "%d files removed, %d files unresolved\n") % (
839 stats.updatedcount, stats.mergedcount,
848 stats.updatedcount, stats.mergedcount,
840 stats.removedcount, stats.unresolvedcount))
849 stats.removedcount, stats.unresolvedcount))
841
850
842 def updaterepo(repo, node, overwrite, updatecheck=None):
851 def updaterepo(repo, node, overwrite, updatecheck=None):
843 """Update the working directory to node.
852 """Update the working directory to node.
844
853
845 When overwrite is set, changes are clobbered, merged else
854 When overwrite is set, changes are clobbered, merged else
846
855
847 returns stats (see pydoc mercurial.merge.applyupdates)"""
856 returns stats (see pydoc mercurial.merge.applyupdates)"""
848 return mergemod.update(repo, node, branchmerge=False, force=overwrite,
857 return mergemod.update(repo, node, branchmerge=False, force=overwrite,
849 labels=['working copy', 'destination'],
858 labels=['working copy', 'destination'],
850 updatecheck=updatecheck)
859 updatecheck=updatecheck)
851
860
852 def update(repo, node, quietempty=False, updatecheck=None):
861 def update(repo, node, quietempty=False, updatecheck=None):
853 """update the working directory to node"""
862 """update the working directory to node"""
854 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
863 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
855 _showstats(repo, stats, quietempty)
864 _showstats(repo, stats, quietempty)
856 if stats.unresolvedcount:
865 if stats.unresolvedcount:
857 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
866 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
858 return stats.unresolvedcount > 0
867 return stats.unresolvedcount > 0
859
868
860 # naming conflict in clone()
869 # naming conflict in clone()
861 _update = update
870 _update = update
862
871
863 def clean(repo, node, show_stats=True, quietempty=False):
872 def clean(repo, node, show_stats=True, quietempty=False):
864 """forcibly switch the working directory to node, clobbering changes"""
873 """forcibly switch the working directory to node, clobbering changes"""
865 stats = updaterepo(repo, node, True)
874 stats = updaterepo(repo, node, True)
866 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
875 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
867 if show_stats:
876 if show_stats:
868 _showstats(repo, stats, quietempty)
877 _showstats(repo, stats, quietempty)
869 return stats.unresolvedcount > 0
878 return stats.unresolvedcount > 0
870
879
871 # naming conflict in updatetotally()
880 # naming conflict in updatetotally()
872 _clean = clean
881 _clean = clean
873
882
874 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
883 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
875 """Update the working directory with extra care for non-file components
884 """Update the working directory with extra care for non-file components
876
885
877 This takes care of non-file components below:
886 This takes care of non-file components below:
878
887
879 :bookmark: might be advanced or (in)activated
888 :bookmark: might be advanced or (in)activated
880
889
881 This takes arguments below:
890 This takes arguments below:
882
891
883 :checkout: to which revision the working directory is updated
892 :checkout: to which revision the working directory is updated
884 :brev: a name, which might be a bookmark to be activated after updating
893 :brev: a name, which might be a bookmark to be activated after updating
885 :clean: whether changes in the working directory can be discarded
894 :clean: whether changes in the working directory can be discarded
886 :updatecheck: how to deal with a dirty working directory
895 :updatecheck: how to deal with a dirty working directory
887
896
888 Valid values for updatecheck are (None => linear):
897 Valid values for updatecheck are (None => linear):
889
898
890 * abort: abort if the working directory is dirty
899 * abort: abort if the working directory is dirty
891 * none: don't check (merge working directory changes into destination)
900 * none: don't check (merge working directory changes into destination)
892 * linear: check that update is linear before merging working directory
901 * linear: check that update is linear before merging working directory
893 changes into destination
902 changes into destination
894 * noconflict: check that the update does not result in file merges
903 * noconflict: check that the update does not result in file merges
895
904
896 This returns whether conflict is detected at updating or not.
905 This returns whether conflict is detected at updating or not.
897 """
906 """
898 if updatecheck is None:
907 if updatecheck is None:
899 updatecheck = ui.config('commands', 'update.check')
908 updatecheck = ui.config('commands', 'update.check')
900 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
909 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
901 # If not configured, or invalid value configured
910 # If not configured, or invalid value configured
902 updatecheck = 'linear'
911 updatecheck = 'linear'
903 with repo.wlock():
912 with repo.wlock():
904 movemarkfrom = None
913 movemarkfrom = None
905 warndest = False
914 warndest = False
906 if checkout is None:
915 if checkout is None:
907 updata = destutil.destupdate(repo, clean=clean)
916 updata = destutil.destupdate(repo, clean=clean)
908 checkout, movemarkfrom, brev = updata
917 checkout, movemarkfrom, brev = updata
909 warndest = True
918 warndest = True
910
919
911 if clean:
920 if clean:
912 ret = _clean(repo, checkout)
921 ret = _clean(repo, checkout)
913 else:
922 else:
914 if updatecheck == 'abort':
923 if updatecheck == 'abort':
915 cmdutil.bailifchanged(repo, merge=False)
924 cmdutil.bailifchanged(repo, merge=False)
916 updatecheck = 'none'
925 updatecheck = 'none'
917 ret = _update(repo, checkout, updatecheck=updatecheck)
926 ret = _update(repo, checkout, updatecheck=updatecheck)
918
927
919 if not ret and movemarkfrom:
928 if not ret and movemarkfrom:
920 if movemarkfrom == repo['.'].node():
929 if movemarkfrom == repo['.'].node():
921 pass # no-op update
930 pass # no-op update
922 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
931 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
923 b = ui.label(repo._activebookmark, 'bookmarks.active')
932 b = ui.label(repo._activebookmark, 'bookmarks.active')
924 ui.status(_("updating bookmark %s\n") % b)
933 ui.status(_("updating bookmark %s\n") % b)
925 else:
934 else:
926 # this can happen with a non-linear update
935 # this can happen with a non-linear update
927 b = ui.label(repo._activebookmark, 'bookmarks')
936 b = ui.label(repo._activebookmark, 'bookmarks')
928 ui.status(_("(leaving bookmark %s)\n") % b)
937 ui.status(_("(leaving bookmark %s)\n") % b)
929 bookmarks.deactivate(repo)
938 bookmarks.deactivate(repo)
930 elif brev in repo._bookmarks:
939 elif brev in repo._bookmarks:
931 if brev != repo._activebookmark:
940 if brev != repo._activebookmark:
932 b = ui.label(brev, 'bookmarks.active')
941 b = ui.label(brev, 'bookmarks.active')
933 ui.status(_("(activating bookmark %s)\n") % b)
942 ui.status(_("(activating bookmark %s)\n") % b)
934 bookmarks.activate(repo, brev)
943 bookmarks.activate(repo, brev)
935 elif brev:
944 elif brev:
936 if repo._activebookmark:
945 if repo._activebookmark:
937 b = ui.label(repo._activebookmark, 'bookmarks')
946 b = ui.label(repo._activebookmark, 'bookmarks')
938 ui.status(_("(leaving bookmark %s)\n") % b)
947 ui.status(_("(leaving bookmark %s)\n") % b)
939 bookmarks.deactivate(repo)
948 bookmarks.deactivate(repo)
940
949
941 if warndest:
950 if warndest:
942 destutil.statusotherdests(ui, repo)
951 destutil.statusotherdests(ui, repo)
943
952
944 return ret
953 return ret
945
954
946 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None,
955 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None,
947 abort=False):
956 abort=False):
948 """Branch merge with node, resolving changes. Return true if any
957 """Branch merge with node, resolving changes. Return true if any
949 unresolved conflicts."""
958 unresolved conflicts."""
950 if not abort:
959 if not abort:
951 stats = mergemod.update(repo, node, branchmerge=True, force=force,
960 stats = mergemod.update(repo, node, branchmerge=True, force=force,
952 mergeforce=mergeforce, labels=labels)
961 mergeforce=mergeforce, labels=labels)
953 else:
962 else:
954 ms = mergemod.mergestate.read(repo)
963 ms = mergemod.mergestate.read(repo)
955 if ms.active():
964 if ms.active():
956 # there were conflicts
965 # there were conflicts
957 node = ms.localctx.hex()
966 node = ms.localctx.hex()
958 else:
967 else:
959 # there were no conficts, mergestate was not stored
968 # there were no conficts, mergestate was not stored
960 node = repo['.'].hex()
969 node = repo['.'].hex()
961
970
962 repo.ui.status(_("aborting the merge, updating back to"
971 repo.ui.status(_("aborting the merge, updating back to"
963 " %s\n") % node[:12])
972 " %s\n") % node[:12])
964 stats = mergemod.update(repo, node, branchmerge=False, force=True,
973 stats = mergemod.update(repo, node, branchmerge=False, force=True,
965 labels=labels)
974 labels=labels)
966
975
967 _showstats(repo, stats)
976 _showstats(repo, stats)
968 if stats.unresolvedcount:
977 if stats.unresolvedcount:
969 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
978 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
970 "or 'hg merge --abort' to abandon\n"))
979 "or 'hg merge --abort' to abandon\n"))
971 elif remind and not abort:
980 elif remind and not abort:
972 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
981 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
973 return stats.unresolvedcount > 0
982 return stats.unresolvedcount > 0
974
983
975 def _incoming(displaychlist, subreporecurse, ui, repo, source,
984 def _incoming(displaychlist, subreporecurse, ui, repo, source,
976 opts, buffered=False):
985 opts, buffered=False):
977 """
986 """
978 Helper for incoming / gincoming.
987 Helper for incoming / gincoming.
979 displaychlist gets called with
988 displaychlist gets called with
980 (remoterepo, incomingchangesetlist, displayer) parameters,
989 (remoterepo, incomingchangesetlist, displayer) parameters,
981 and is supposed to contain only code that can't be unified.
990 and is supposed to contain only code that can't be unified.
982 """
991 """
983 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
992 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
984 other = peer(repo, opts, source)
993 other = peer(repo, opts, source)
985 ui.status(_('comparing with %s\n') % util.hidepassword(source))
994 ui.status(_('comparing with %s\n') % util.hidepassword(source))
986 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
995 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
987
996
988 if revs:
997 if revs:
989 revs = [other.lookup(rev) for rev in revs]
998 revs = [other.lookup(rev) for rev in revs]
990 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
999 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
991 revs, opts["bundle"], opts["force"])
1000 revs, opts["bundle"], opts["force"])
992 try:
1001 try:
993 if not chlist:
1002 if not chlist:
994 ui.status(_("no changes found\n"))
1003 ui.status(_("no changes found\n"))
995 return subreporecurse()
1004 return subreporecurse()
996 ui.pager('incoming')
1005 ui.pager('incoming')
997 displayer = logcmdutil.changesetdisplayer(ui, other, opts,
1006 displayer = logcmdutil.changesetdisplayer(ui, other, opts,
998 buffered=buffered)
1007 buffered=buffered)
999 displaychlist(other, chlist, displayer)
1008 displaychlist(other, chlist, displayer)
1000 displayer.close()
1009 displayer.close()
1001 finally:
1010 finally:
1002 cleanupfn()
1011 cleanupfn()
1003 subreporecurse()
1012 subreporecurse()
1004 return 0 # exit code is zero since we found incoming changes
1013 return 0 # exit code is zero since we found incoming changes
1005
1014
1006 def incoming(ui, repo, source, opts):
1015 def incoming(ui, repo, source, opts):
1007 def subreporecurse():
1016 def subreporecurse():
1008 ret = 1
1017 ret = 1
1009 if opts.get('subrepos'):
1018 if opts.get('subrepos'):
1010 ctx = repo[None]
1019 ctx = repo[None]
1011 for subpath in sorted(ctx.substate):
1020 for subpath in sorted(ctx.substate):
1012 sub = ctx.sub(subpath)
1021 sub = ctx.sub(subpath)
1013 ret = min(ret, sub.incoming(ui, source, opts))
1022 ret = min(ret, sub.incoming(ui, source, opts))
1014 return ret
1023 return ret
1015
1024
1016 def display(other, chlist, displayer):
1025 def display(other, chlist, displayer):
1017 limit = logcmdutil.getlimit(opts)
1026 limit = logcmdutil.getlimit(opts)
1018 if opts.get('newest_first'):
1027 if opts.get('newest_first'):
1019 chlist.reverse()
1028 chlist.reverse()
1020 count = 0
1029 count = 0
1021 for n in chlist:
1030 for n in chlist:
1022 if limit is not None and count >= limit:
1031 if limit is not None and count >= limit:
1023 break
1032 break
1024 parents = [p for p in other.changelog.parents(n) if p != nullid]
1033 parents = [p for p in other.changelog.parents(n) if p != nullid]
1025 if opts.get('no_merges') and len(parents) == 2:
1034 if opts.get('no_merges') and len(parents) == 2:
1026 continue
1035 continue
1027 count += 1
1036 count += 1
1028 displayer.show(other[n])
1037 displayer.show(other[n])
1029 return _incoming(display, subreporecurse, ui, repo, source, opts)
1038 return _incoming(display, subreporecurse, ui, repo, source, opts)
1030
1039
1031 def _outgoing(ui, repo, dest, opts):
1040 def _outgoing(ui, repo, dest, opts):
1032 path = ui.paths.getpath(dest, default=('default-push', 'default'))
1041 path = ui.paths.getpath(dest, default=('default-push', 'default'))
1033 if not path:
1042 if not path:
1034 raise error.Abort(_('default repository not configured!'),
1043 raise error.Abort(_('default repository not configured!'),
1035 hint=_("see 'hg help config.paths'"))
1044 hint=_("see 'hg help config.paths'"))
1036 dest = path.pushloc or path.loc
1045 dest = path.pushloc or path.loc
1037 branches = path.branch, opts.get('branch') or []
1046 branches = path.branch, opts.get('branch') or []
1038
1047
1039 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1048 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1040 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
1049 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
1041 if revs:
1050 if revs:
1042 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1051 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1043
1052
1044 other = peer(repo, opts, dest)
1053 other = peer(repo, opts, dest)
1045 outgoing = discovery.findcommonoutgoing(repo, other, revs,
1054 outgoing = discovery.findcommonoutgoing(repo, other, revs,
1046 force=opts.get('force'))
1055 force=opts.get('force'))
1047 o = outgoing.missing
1056 o = outgoing.missing
1048 if not o:
1057 if not o:
1049 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1058 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1050 return o, other
1059 return o, other
1051
1060
1052 def outgoing(ui, repo, dest, opts):
1061 def outgoing(ui, repo, dest, opts):
1053 def recurse():
1062 def recurse():
1054 ret = 1
1063 ret = 1
1055 if opts.get('subrepos'):
1064 if opts.get('subrepos'):
1056 ctx = repo[None]
1065 ctx = repo[None]
1057 for subpath in sorted(ctx.substate):
1066 for subpath in sorted(ctx.substate):
1058 sub = ctx.sub(subpath)
1067 sub = ctx.sub(subpath)
1059 ret = min(ret, sub.outgoing(ui, dest, opts))
1068 ret = min(ret, sub.outgoing(ui, dest, opts))
1060 return ret
1069 return ret
1061
1070
1062 limit = logcmdutil.getlimit(opts)
1071 limit = logcmdutil.getlimit(opts)
1063 o, other = _outgoing(ui, repo, dest, opts)
1072 o, other = _outgoing(ui, repo, dest, opts)
1064 if not o:
1073 if not o:
1065 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1074 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1066 return recurse()
1075 return recurse()
1067
1076
1068 if opts.get('newest_first'):
1077 if opts.get('newest_first'):
1069 o.reverse()
1078 o.reverse()
1070 ui.pager('outgoing')
1079 ui.pager('outgoing')
1071 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1080 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1072 count = 0
1081 count = 0
1073 for n in o:
1082 for n in o:
1074 if limit is not None and count >= limit:
1083 if limit is not None and count >= limit:
1075 break
1084 break
1076 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1085 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1077 if opts.get('no_merges') and len(parents) == 2:
1086 if opts.get('no_merges') and len(parents) == 2:
1078 continue
1087 continue
1079 count += 1
1088 count += 1
1080 displayer.show(repo[n])
1089 displayer.show(repo[n])
1081 displayer.close()
1090 displayer.close()
1082 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1091 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1083 recurse()
1092 recurse()
1084 return 0 # exit code is zero since we found outgoing changes
1093 return 0 # exit code is zero since we found outgoing changes
1085
1094
1086 def verify(repo):
1095 def verify(repo):
1087 """verify the consistency of a repository"""
1096 """verify the consistency of a repository"""
1088 ret = verifymod.verify(repo)
1097 ret = verifymod.verify(repo)
1089
1098
1090 # Broken subrepo references in hidden csets don't seem worth worrying about,
1099 # Broken subrepo references in hidden csets don't seem worth worrying about,
1091 # since they can't be pushed/pulled, and --hidden can be used if they are a
1100 # since they can't be pushed/pulled, and --hidden can be used if they are a
1092 # concern.
1101 # concern.
1093
1102
1094 # pathto() is needed for -R case
1103 # pathto() is needed for -R case
1095 revs = repo.revs("filelog(%s)",
1104 revs = repo.revs("filelog(%s)",
1096 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
1105 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
1097
1106
1098 if revs:
1107 if revs:
1099 repo.ui.status(_('checking subrepo links\n'))
1108 repo.ui.status(_('checking subrepo links\n'))
1100 for rev in revs:
1109 for rev in revs:
1101 ctx = repo[rev]
1110 ctx = repo[rev]
1102 try:
1111 try:
1103 for subpath in ctx.substate:
1112 for subpath in ctx.substate:
1104 try:
1113 try:
1105 ret = (ctx.sub(subpath, allowcreate=False).verify()
1114 ret = (ctx.sub(subpath, allowcreate=False).verify()
1106 or ret)
1115 or ret)
1107 except error.RepoError as e:
1116 except error.RepoError as e:
1108 repo.ui.warn(('%d: %s\n') % (rev, e))
1117 repo.ui.warn(('%d: %s\n') % (rev, e))
1109 except Exception:
1118 except Exception:
1110 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
1119 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
1111 node.short(ctx.node()))
1120 node.short(ctx.node()))
1112
1121
1113 return ret
1122 return ret
1114
1123
1115 def remoteui(src, opts):
1124 def remoteui(src, opts):
1116 'build a remote ui from ui or repo and opts'
1125 'build a remote ui from ui or repo and opts'
1117 if util.safehasattr(src, 'baseui'): # looks like a repository
1126 if util.safehasattr(src, 'baseui'): # looks like a repository
1118 dst = src.baseui.copy() # drop repo-specific config
1127 dst = src.baseui.copy() # drop repo-specific config
1119 src = src.ui # copy target options from repo
1128 src = src.ui # copy target options from repo
1120 else: # assume it's a global ui object
1129 else: # assume it's a global ui object
1121 dst = src.copy() # keep all global options
1130 dst = src.copy() # keep all global options
1122
1131
1123 # copy ssh-specific options
1132 # copy ssh-specific options
1124 for o in 'ssh', 'remotecmd':
1133 for o in 'ssh', 'remotecmd':
1125 v = opts.get(o) or src.config('ui', o)
1134 v = opts.get(o) or src.config('ui', o)
1126 if v:
1135 if v:
1127 dst.setconfig("ui", o, v, 'copied')
1136 dst.setconfig("ui", o, v, 'copied')
1128
1137
1129 # copy bundle-specific options
1138 # copy bundle-specific options
1130 r = src.config('bundle', 'mainreporoot')
1139 r = src.config('bundle', 'mainreporoot')
1131 if r:
1140 if r:
1132 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
1141 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
1133
1142
1134 # copy selected local settings to the remote ui
1143 # copy selected local settings to the remote ui
1135 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
1144 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
1136 for key, val in src.configitems(sect):
1145 for key, val in src.configitems(sect):
1137 dst.setconfig(sect, key, val, 'copied')
1146 dst.setconfig(sect, key, val, 'copied')
1138 v = src.config('web', 'cacerts')
1147 v = src.config('web', 'cacerts')
1139 if v:
1148 if v:
1140 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
1149 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
1141
1150
1142 return dst
1151 return dst
1143
1152
1144 # Files of interest
1153 # Files of interest
1145 # Used to check if the repository has changed looking at mtime and size of
1154 # Used to check if the repository has changed looking at mtime and size of
1146 # these files.
1155 # these files.
1147 foi = [('spath', '00changelog.i'),
1156 foi = [('spath', '00changelog.i'),
1148 ('spath', 'phaseroots'), # ! phase can change content at the same size
1157 ('spath', 'phaseroots'), # ! phase can change content at the same size
1149 ('spath', 'obsstore'),
1158 ('spath', 'obsstore'),
1150 ('path', 'bookmarks'), # ! bookmark can change content at the same size
1159 ('path', 'bookmarks'), # ! bookmark can change content at the same size
1151 ]
1160 ]
1152
1161
1153 class cachedlocalrepo(object):
1162 class cachedlocalrepo(object):
1154 """Holds a localrepository that can be cached and reused."""
1163 """Holds a localrepository that can be cached and reused."""
1155
1164
1156 def __init__(self, repo):
1165 def __init__(self, repo):
1157 """Create a new cached repo from an existing repo.
1166 """Create a new cached repo from an existing repo.
1158
1167
1159 We assume the passed in repo was recently created. If the
1168 We assume the passed in repo was recently created. If the
1160 repo has changed between when it was created and when it was
1169 repo has changed between when it was created and when it was
1161 turned into a cache, it may not refresh properly.
1170 turned into a cache, it may not refresh properly.
1162 """
1171 """
1163 assert isinstance(repo, localrepo.localrepository)
1172 assert isinstance(repo, localrepo.localrepository)
1164 self._repo = repo
1173 self._repo = repo
1165 self._state, self.mtime = self._repostate()
1174 self._state, self.mtime = self._repostate()
1166 self._filtername = repo.filtername
1175 self._filtername = repo.filtername
1167
1176
1168 def fetch(self):
1177 def fetch(self):
1169 """Refresh (if necessary) and return a repository.
1178 """Refresh (if necessary) and return a repository.
1170
1179
1171 If the cached instance is out of date, it will be recreated
1180 If the cached instance is out of date, it will be recreated
1172 automatically and returned.
1181 automatically and returned.
1173
1182
1174 Returns a tuple of the repo and a boolean indicating whether a new
1183 Returns a tuple of the repo and a boolean indicating whether a new
1175 repo instance was created.
1184 repo instance was created.
1176 """
1185 """
1177 # We compare the mtimes and sizes of some well-known files to
1186 # We compare the mtimes and sizes of some well-known files to
1178 # determine if the repo changed. This is not precise, as mtimes
1187 # determine if the repo changed. This is not precise, as mtimes
1179 # are susceptible to clock skew and imprecise filesystems and
1188 # are susceptible to clock skew and imprecise filesystems and
1180 # file content can change while maintaining the same size.
1189 # file content can change while maintaining the same size.
1181
1190
1182 state, mtime = self._repostate()
1191 state, mtime = self._repostate()
1183 if state == self._state:
1192 if state == self._state:
1184 return self._repo, False
1193 return self._repo, False
1185
1194
1186 repo = repository(self._repo.baseui, self._repo.url())
1195 repo = repository(self._repo.baseui, self._repo.url())
1187 if self._filtername:
1196 if self._filtername:
1188 self._repo = repo.filtered(self._filtername)
1197 self._repo = repo.filtered(self._filtername)
1189 else:
1198 else:
1190 self._repo = repo.unfiltered()
1199 self._repo = repo.unfiltered()
1191 self._state = state
1200 self._state = state
1192 self.mtime = mtime
1201 self.mtime = mtime
1193
1202
1194 return self._repo, True
1203 return self._repo, True
1195
1204
1196 def _repostate(self):
1205 def _repostate(self):
1197 state = []
1206 state = []
1198 maxmtime = -1
1207 maxmtime = -1
1199 for attr, fname in foi:
1208 for attr, fname in foi:
1200 prefix = getattr(self._repo, attr)
1209 prefix = getattr(self._repo, attr)
1201 p = os.path.join(prefix, fname)
1210 p = os.path.join(prefix, fname)
1202 try:
1211 try:
1203 st = os.stat(p)
1212 st = os.stat(p)
1204 except OSError:
1213 except OSError:
1205 st = os.stat(prefix)
1214 st = os.stat(prefix)
1206 state.append((st[stat.ST_MTIME], st.st_size))
1215 state.append((st[stat.ST_MTIME], st.st_size))
1207 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1216 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1208
1217
1209 return tuple(state), maxmtime
1218 return tuple(state), maxmtime
1210
1219
1211 def copy(self):
1220 def copy(self):
1212 """Obtain a copy of this class instance.
1221 """Obtain a copy of this class instance.
1213
1222
1214 A new localrepository instance is obtained. The new instance should be
1223 A new localrepository instance is obtained. The new instance should be
1215 completely independent of the original.
1224 completely independent of the original.
1216 """
1225 """
1217 repo = repository(self._repo.baseui, self._repo.origroot)
1226 repo = repository(self._repo.baseui, self._repo.origroot)
1218 if self._filtername:
1227 if self._filtername:
1219 repo = repo.filtered(self._filtername)
1228 repo = repo.filtered(self._filtername)
1220 else:
1229 else:
1221 repo = repo.unfiltered()
1230 repo = repo.unfiltered()
1222 c = cachedlocalrepo(repo)
1231 c = cachedlocalrepo(repo)
1223 c._state = self._state
1232 c._state = self._state
1224 c.mtime = self.mtime
1233 c.mtime = self.mtime
1225 return c
1234 return c
@@ -1,2108 +1,2112
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import traceback
21 import traceback
22
22
23 from .i18n import _
23 from .i18n import _
24 from .node import hex
24 from .node import hex
25
25
26 from . import (
26 from . import (
27 color,
27 color,
28 config,
28 config,
29 configitems,
29 configitems,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 loggingutil,
33 loggingutil,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40 from .utils import (
40 from .utils import (
41 dateutil,
41 dateutil,
42 procutil,
42 procutil,
43 stringutil,
43 stringutil,
44 )
44 )
45
45
46 urlreq = util.urlreq
46 urlreq = util.urlreq
47
47
48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
49 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
49 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
50 if not c.isalnum())
50 if not c.isalnum())
51
51
52 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
52 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
53 tweakrc = b"""
53 tweakrc = b"""
54 [ui]
54 [ui]
55 # The rollback command is dangerous. As a rule, don't use it.
55 # The rollback command is dangerous. As a rule, don't use it.
56 rollback = False
56 rollback = False
57 # Make `hg status` report copy information
57 # Make `hg status` report copy information
58 statuscopies = yes
58 statuscopies = yes
59 # Prefer curses UIs when available. Revert to plain-text with `text`.
59 # Prefer curses UIs when available. Revert to plain-text with `text`.
60 interface = curses
60 interface = curses
61
61
62 [commands]
62 [commands]
63 # Grep working directory by default.
63 # Grep working directory by default.
64 grep.all-files = True
64 grep.all-files = True
65 # Make `hg status` emit cwd-relative paths by default.
65 # Make `hg status` emit cwd-relative paths by default.
66 status.relative = yes
66 status.relative = yes
67 # Refuse to perform an `hg update` that would cause a file content merge
67 # Refuse to perform an `hg update` that would cause a file content merge
68 update.check = noconflict
68 update.check = noconflict
69 # Show conflicts information in `hg status`
69 # Show conflicts information in `hg status`
70 status.verbose = True
70 status.verbose = True
71
71
72 [diff]
72 [diff]
73 git = 1
73 git = 1
74 showfunc = 1
74 showfunc = 1
75 word-diff = 1
75 word-diff = 1
76 """
76 """
77
77
78 samplehgrcs = {
78 samplehgrcs = {
79 'user':
79 'user':
80 b"""# example user config (see 'hg help config' for more info)
80 b"""# example user config (see 'hg help config' for more info)
81 [ui]
81 [ui]
82 # name and email, e.g.
82 # name and email, e.g.
83 # username = Jane Doe <jdoe@example.com>
83 # username = Jane Doe <jdoe@example.com>
84 username =
84 username =
85
85
86 # We recommend enabling tweakdefaults to get slight improvements to
86 # We recommend enabling tweakdefaults to get slight improvements to
87 # the UI over time. Make sure to set HGPLAIN in the environment when
87 # the UI over time. Make sure to set HGPLAIN in the environment when
88 # writing scripts!
88 # writing scripts!
89 # tweakdefaults = True
89 # tweakdefaults = True
90
90
91 # uncomment to disable color in command output
91 # uncomment to disable color in command output
92 # (see 'hg help color' for details)
92 # (see 'hg help color' for details)
93 # color = never
93 # color = never
94
94
95 # uncomment to disable command output pagination
95 # uncomment to disable command output pagination
96 # (see 'hg help pager' for details)
96 # (see 'hg help pager' for details)
97 # paginate = never
97 # paginate = never
98
98
99 [extensions]
99 [extensions]
100 # uncomment these lines to enable some popular extensions
100 # uncomment these lines to enable some popular extensions
101 # (see 'hg help extensions' for more info)
101 # (see 'hg help extensions' for more info)
102 #
102 #
103 # churn =
103 # churn =
104 """,
104 """,
105
105
106 'cloned':
106 'cloned':
107 b"""# example repository config (see 'hg help config' for more info)
107 b"""# example repository config (see 'hg help config' for more info)
108 [paths]
108 [paths]
109 default = %s
109 default = %s
110
110
111 # path aliases to other clones of this repo in URLs or filesystem paths
111 # path aliases to other clones of this repo in URLs or filesystem paths
112 # (see 'hg help config.paths' for more info)
112 # (see 'hg help config.paths' for more info)
113 #
113 #
114 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
114 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
115 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
115 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
116 # my-clone = /home/jdoe/jdoes-clone
116 # my-clone = /home/jdoe/jdoes-clone
117
117
118 [ui]
118 [ui]
119 # name and email (local to this repository, optional), e.g.
119 # name and email (local to this repository, optional), e.g.
120 # username = Jane Doe <jdoe@example.com>
120 # username = Jane Doe <jdoe@example.com>
121 """,
121 """,
122
122
123 'local':
123 'local':
124 b"""# example repository config (see 'hg help config' for more info)
124 b"""# example repository config (see 'hg help config' for more info)
125 [paths]
125 [paths]
126 # path aliases to other clones of this repo in URLs or filesystem paths
126 # path aliases to other clones of this repo in URLs or filesystem paths
127 # (see 'hg help config.paths' for more info)
127 # (see 'hg help config.paths' for more info)
128 #
128 #
129 # default = http://example.com/hg/example-repo
129 # default = http://example.com/hg/example-repo
130 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
130 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
131 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
131 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
132 # my-clone = /home/jdoe/jdoes-clone
132 # my-clone = /home/jdoe/jdoes-clone
133
133
134 [ui]
134 [ui]
135 # name and email (local to this repository, optional), e.g.
135 # name and email (local to this repository, optional), e.g.
136 # username = Jane Doe <jdoe@example.com>
136 # username = Jane Doe <jdoe@example.com>
137 """,
137 """,
138
138
139 'global':
139 'global':
140 b"""# example system-wide hg config (see 'hg help config' for more info)
140 b"""# example system-wide hg config (see 'hg help config' for more info)
141
141
142 [ui]
142 [ui]
143 # uncomment to disable color in command output
143 # uncomment to disable color in command output
144 # (see 'hg help color' for details)
144 # (see 'hg help color' for details)
145 # color = never
145 # color = never
146
146
147 # uncomment to disable command output pagination
147 # uncomment to disable command output pagination
148 # (see 'hg help pager' for details)
148 # (see 'hg help pager' for details)
149 # paginate = never
149 # paginate = never
150
150
151 [extensions]
151 [extensions]
152 # uncomment these lines to enable some popular extensions
152 # uncomment these lines to enable some popular extensions
153 # (see 'hg help extensions' for more info)
153 # (see 'hg help extensions' for more info)
154 #
154 #
155 # blackbox =
155 # blackbox =
156 # churn =
156 # churn =
157 """,
157 """,
158 }
158 }
159
159
160 def _maybestrurl(maybebytes):
160 def _maybestrurl(maybebytes):
161 return pycompat.rapply(pycompat.strurl, maybebytes)
161 return pycompat.rapply(pycompat.strurl, maybebytes)
162
162
163 def _maybebytesurl(maybestr):
163 def _maybebytesurl(maybestr):
164 return pycompat.rapply(pycompat.bytesurl, maybestr)
164 return pycompat.rapply(pycompat.bytesurl, maybestr)
165
165
166 class httppasswordmgrdbproxy(object):
166 class httppasswordmgrdbproxy(object):
167 """Delays loading urllib2 until it's needed."""
167 """Delays loading urllib2 until it's needed."""
168 def __init__(self):
168 def __init__(self):
169 self._mgr = None
169 self._mgr = None
170
170
171 def _get_mgr(self):
171 def _get_mgr(self):
172 if self._mgr is None:
172 if self._mgr is None:
173 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
173 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
174 return self._mgr
174 return self._mgr
175
175
176 def add_password(self, realm, uris, user, passwd):
176 def add_password(self, realm, uris, user, passwd):
177 return self._get_mgr().add_password(
177 return self._get_mgr().add_password(
178 _maybestrurl(realm), _maybestrurl(uris),
178 _maybestrurl(realm), _maybestrurl(uris),
179 _maybestrurl(user), _maybestrurl(passwd))
179 _maybestrurl(user), _maybestrurl(passwd))
180
180
181 def find_user_password(self, realm, uri):
181 def find_user_password(self, realm, uri):
182 mgr = self._get_mgr()
182 mgr = self._get_mgr()
183 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
183 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
184 _maybestrurl(uri)))
184 _maybestrurl(uri)))
185
185
186 def _catchterm(*args):
186 def _catchterm(*args):
187 raise error.SignalInterrupt
187 raise error.SignalInterrupt
188
188
189 # unique object used to detect no default value has been provided when
189 # unique object used to detect no default value has been provided when
190 # retrieving configuration value.
190 # retrieving configuration value.
191 _unset = object()
191 _unset = object()
192
192
193 # _reqexithandlers: callbacks run at the end of a request
193 # _reqexithandlers: callbacks run at the end of a request
194 _reqexithandlers = []
194 _reqexithandlers = []
195
195
196 class ui(object):
196 class ui(object):
197 def __init__(self, src=None):
197 def __init__(self, src=None):
198 """Create a fresh new ui object if no src given
198 """Create a fresh new ui object if no src given
199
199
200 Use uimod.ui.load() to create a ui which knows global and user configs.
200 Use uimod.ui.load() to create a ui which knows global and user configs.
201 In most cases, you should use ui.copy() to create a copy of an existing
201 In most cases, you should use ui.copy() to create a copy of an existing
202 ui object.
202 ui object.
203 """
203 """
204 # _buffers: used for temporary capture of output
204 # _buffers: used for temporary capture of output
205 self._buffers = []
205 self._buffers = []
206 # 3-tuple describing how each buffer in the stack behaves.
206 # 3-tuple describing how each buffer in the stack behaves.
207 # Values are (capture stderr, capture subprocesses, apply labels).
207 # Values are (capture stderr, capture subprocesses, apply labels).
208 self._bufferstates = []
208 self._bufferstates = []
209 # When a buffer is active, defines whether we are expanding labels.
209 # When a buffer is active, defines whether we are expanding labels.
210 # This exists to prevent an extra list lookup.
210 # This exists to prevent an extra list lookup.
211 self._bufferapplylabels = None
211 self._bufferapplylabels = None
212 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
212 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
213 self._reportuntrusted = True
213 self._reportuntrusted = True
214 self._knownconfig = configitems.coreitems
214 self._knownconfig = configitems.coreitems
215 self._ocfg = config.config() # overlay
215 self._ocfg = config.config() # overlay
216 self._tcfg = config.config() # trusted
216 self._tcfg = config.config() # trusted
217 self._ucfg = config.config() # untrusted
217 self._ucfg = config.config() # untrusted
218 self._trustusers = set()
218 self._trustusers = set()
219 self._trustgroups = set()
219 self._trustgroups = set()
220 self.callhooks = True
220 self.callhooks = True
221 # Insecure server connections requested.
221 # Insecure server connections requested.
222 self.insecureconnections = False
222 self.insecureconnections = False
223 # Blocked time
223 # Blocked time
224 self.logblockedtimes = False
224 self.logblockedtimes = False
225 # color mode: see mercurial/color.py for possible value
225 # color mode: see mercurial/color.py for possible value
226 self._colormode = None
226 self._colormode = None
227 self._terminfoparams = {}
227 self._terminfoparams = {}
228 self._styles = {}
228 self._styles = {}
229 self._uninterruptible = False
229 self._uninterruptible = False
230
230
231 if src:
231 if src:
232 self._fout = src._fout
232 self._fout = src._fout
233 self._ferr = src._ferr
233 self._ferr = src._ferr
234 self._fin = src._fin
234 self._fin = src._fin
235 self._fmsg = src._fmsg
235 self._fmsg = src._fmsg
236 self._fmsgout = src._fmsgout
236 self._fmsgout = src._fmsgout
237 self._fmsgerr = src._fmsgerr
237 self._fmsgerr = src._fmsgerr
238 self._finoutredirected = src._finoutredirected
238 self._finoutredirected = src._finoutredirected
239 self._loggers = src._loggers.copy()
239 self._loggers = src._loggers.copy()
240 self.pageractive = src.pageractive
240 self.pageractive = src.pageractive
241 self._disablepager = src._disablepager
241 self._disablepager = src._disablepager
242 self._tweaked = src._tweaked
242 self._tweaked = src._tweaked
243
243
244 self._tcfg = src._tcfg.copy()
244 self._tcfg = src._tcfg.copy()
245 self._ucfg = src._ucfg.copy()
245 self._ucfg = src._ucfg.copy()
246 self._ocfg = src._ocfg.copy()
246 self._ocfg = src._ocfg.copy()
247 self._trustusers = src._trustusers.copy()
247 self._trustusers = src._trustusers.copy()
248 self._trustgroups = src._trustgroups.copy()
248 self._trustgroups = src._trustgroups.copy()
249 self.environ = src.environ
249 self.environ = src.environ
250 self.callhooks = src.callhooks
250 self.callhooks = src.callhooks
251 self.insecureconnections = src.insecureconnections
251 self.insecureconnections = src.insecureconnections
252 self._colormode = src._colormode
252 self._colormode = src._colormode
253 self._terminfoparams = src._terminfoparams.copy()
253 self._terminfoparams = src._terminfoparams.copy()
254 self._styles = src._styles.copy()
254 self._styles = src._styles.copy()
255
255
256 self.fixconfig()
256 self.fixconfig()
257
257
258 self.httppasswordmgrdb = src.httppasswordmgrdb
258 self.httppasswordmgrdb = src.httppasswordmgrdb
259 self._blockedtimes = src._blockedtimes
259 self._blockedtimes = src._blockedtimes
260 else:
260 else:
261 self._fout = procutil.stdout
261 self._fout = procutil.stdout
262 self._ferr = procutil.stderr
262 self._ferr = procutil.stderr
263 self._fin = procutil.stdin
263 self._fin = procutil.stdin
264 self._fmsg = None
264 self._fmsg = None
265 self._fmsgout = self.fout # configurable
265 self._fmsgout = self.fout # configurable
266 self._fmsgerr = self.ferr # configurable
266 self._fmsgerr = self.ferr # configurable
267 self._finoutredirected = False
267 self._finoutredirected = False
268 self._loggers = {}
268 self._loggers = {}
269 self.pageractive = False
269 self.pageractive = False
270 self._disablepager = False
270 self._disablepager = False
271 self._tweaked = False
271 self._tweaked = False
272
272
273 # shared read-only environment
273 # shared read-only environment
274 self.environ = encoding.environ
274 self.environ = encoding.environ
275
275
276 self.httppasswordmgrdb = httppasswordmgrdbproxy()
276 self.httppasswordmgrdb = httppasswordmgrdbproxy()
277 self._blockedtimes = collections.defaultdict(int)
277 self._blockedtimes = collections.defaultdict(int)
278
278
279 allowed = self.configlist('experimental', 'exportableenviron')
279 allowed = self.configlist('experimental', 'exportableenviron')
280 if '*' in allowed:
280 if '*' in allowed:
281 self._exportableenviron = self.environ
281 self._exportableenviron = self.environ
282 else:
282 else:
283 self._exportableenviron = {}
283 self._exportableenviron = {}
284 for k in allowed:
284 for k in allowed:
285 if k in self.environ:
285 if k in self.environ:
286 self._exportableenviron[k] = self.environ[k]
286 self._exportableenviron[k] = self.environ[k]
287
287
288 @classmethod
288 @classmethod
289 def load(cls):
289 def load(cls):
290 """Create a ui and load global and user configs"""
290 """Create a ui and load global and user configs"""
291 u = cls()
291 u = cls()
292 # we always trust global config files and environment variables
292 # we always trust global config files and environment variables
293 for t, f in rcutil.rccomponents():
293 for t, f in rcutil.rccomponents():
294 if t == 'path':
294 if t == 'path':
295 u.readconfig(f, trust=True)
295 u.readconfig(f, trust=True)
296 elif t == 'items':
296 elif t == 'items':
297 sections = set()
297 sections = set()
298 for section, name, value, source in f:
298 for section, name, value, source in f:
299 # do not set u._ocfg
299 # do not set u._ocfg
300 # XXX clean this up once immutable config object is a thing
300 # XXX clean this up once immutable config object is a thing
301 u._tcfg.set(section, name, value, source)
301 u._tcfg.set(section, name, value, source)
302 u._ucfg.set(section, name, value, source)
302 u._ucfg.set(section, name, value, source)
303 sections.add(section)
303 sections.add(section)
304 for section in sections:
304 for section in sections:
305 u.fixconfig(section=section)
305 u.fixconfig(section=section)
306 else:
306 else:
307 raise error.ProgrammingError('unknown rctype: %s' % t)
307 raise error.ProgrammingError('unknown rctype: %s' % t)
308 u._maybetweakdefaults()
308 u._maybetweakdefaults()
309 return u
309 return u
310
310
311 def _maybetweakdefaults(self):
311 def _maybetweakdefaults(self):
312 if not self.configbool('ui', 'tweakdefaults'):
312 if not self.configbool('ui', 'tweakdefaults'):
313 return
313 return
314 if self._tweaked or self.plain('tweakdefaults'):
314 if self._tweaked or self.plain('tweakdefaults'):
315 return
315 return
316
316
317 # Note: it is SUPER IMPORTANT that you set self._tweaked to
317 # Note: it is SUPER IMPORTANT that you set self._tweaked to
318 # True *before* any calls to setconfig(), otherwise you'll get
318 # True *before* any calls to setconfig(), otherwise you'll get
319 # infinite recursion between setconfig and this method.
319 # infinite recursion between setconfig and this method.
320 #
320 #
321 # TODO: We should extract an inner method in setconfig() to
321 # TODO: We should extract an inner method in setconfig() to
322 # avoid this weirdness.
322 # avoid this weirdness.
323 self._tweaked = True
323 self._tweaked = True
324 tmpcfg = config.config()
324 tmpcfg = config.config()
325 tmpcfg.parse('<tweakdefaults>', tweakrc)
325 tmpcfg.parse('<tweakdefaults>', tweakrc)
326 for section in tmpcfg:
326 for section in tmpcfg:
327 for name, value in tmpcfg.items(section):
327 for name, value in tmpcfg.items(section):
328 if not self.hasconfig(section, name):
328 if not self.hasconfig(section, name):
329 self.setconfig(section, name, value, "<tweakdefaults>")
329 self.setconfig(section, name, value, "<tweakdefaults>")
330
330
331 def copy(self):
331 def copy(self):
332 return self.__class__(self)
332 return self.__class__(self)
333
333
334 def resetstate(self):
334 def resetstate(self):
335 """Clear internal state that shouldn't persist across commands"""
335 """Clear internal state that shouldn't persist across commands"""
336 if self._progbar:
336 if self._progbar:
337 self._progbar.resetstate() # reset last-print time of progress bar
337 self._progbar.resetstate() # reset last-print time of progress bar
338 self.httppasswordmgrdb = httppasswordmgrdbproxy()
338 self.httppasswordmgrdb = httppasswordmgrdbproxy()
339
339
340 @contextlib.contextmanager
340 @contextlib.contextmanager
341 def timeblockedsection(self, key):
341 def timeblockedsection(self, key):
342 # this is open-coded below - search for timeblockedsection to find them
342 # this is open-coded below - search for timeblockedsection to find them
343 starttime = util.timer()
343 starttime = util.timer()
344 try:
344 try:
345 yield
345 yield
346 finally:
346 finally:
347 self._blockedtimes[key + '_blocked'] += \
347 self._blockedtimes[key + '_blocked'] += \
348 (util.timer() - starttime) * 1000
348 (util.timer() - starttime) * 1000
349
349
350 @contextlib.contextmanager
350 @contextlib.contextmanager
351 def uninterruptible(self):
351 def uninterruptible(self):
352 """Mark an operation as unsafe.
352 """Mark an operation as unsafe.
353
353
354 Most operations on a repository are safe to interrupt, but a
354 Most operations on a repository are safe to interrupt, but a
355 few are risky (for example repair.strip). This context manager
355 few are risky (for example repair.strip). This context manager
356 lets you advise Mercurial that something risky is happening so
356 lets you advise Mercurial that something risky is happening so
357 that control-C etc can be blocked if desired.
357 that control-C etc can be blocked if desired.
358 """
358 """
359 enabled = self.configbool('experimental', 'nointerrupt')
359 enabled = self.configbool('experimental', 'nointerrupt')
360 if (enabled and
360 if (enabled and
361 self.configbool('experimental', 'nointerrupt-interactiveonly')):
361 self.configbool('experimental', 'nointerrupt-interactiveonly')):
362 enabled = self.interactive()
362 enabled = self.interactive()
363 if self._uninterruptible or not enabled:
363 if self._uninterruptible or not enabled:
364 # if nointerrupt support is turned off, the process isn't
364 # if nointerrupt support is turned off, the process isn't
365 # interactive, or we're already in an uninterruptible
365 # interactive, or we're already in an uninterruptible
366 # block, do nothing.
366 # block, do nothing.
367 yield
367 yield
368 return
368 return
369 def warn():
369 def warn():
370 self.warn(_("shutting down cleanly\n"))
370 self.warn(_("shutting down cleanly\n"))
371 self.warn(
371 self.warn(
372 _("press ^C again to terminate immediately (dangerous)\n"))
372 _("press ^C again to terminate immediately (dangerous)\n"))
373 return True
373 return True
374 with procutil.uninterruptible(warn):
374 with procutil.uninterruptible(warn):
375 try:
375 try:
376 self._uninterruptible = True
376 self._uninterruptible = True
377 yield
377 yield
378 finally:
378 finally:
379 self._uninterruptible = False
379 self._uninterruptible = False
380
380
381 def formatter(self, topic, opts):
381 def formatter(self, topic, opts):
382 return formatter.formatter(self, self, topic, opts)
382 return formatter.formatter(self, self, topic, opts)
383
383
384 def _trusted(self, fp, f):
384 def _trusted(self, fp, f):
385 st = util.fstat(fp)
385 st = util.fstat(fp)
386 if util.isowner(st):
386 if util.isowner(st):
387 return True
387 return True
388
388
389 tusers, tgroups = self._trustusers, self._trustgroups
389 tusers, tgroups = self._trustusers, self._trustgroups
390 if '*' in tusers or '*' in tgroups:
390 if '*' in tusers or '*' in tgroups:
391 return True
391 return True
392
392
393 user = util.username(st.st_uid)
393 user = util.username(st.st_uid)
394 group = util.groupname(st.st_gid)
394 group = util.groupname(st.st_gid)
395 if user in tusers or group in tgroups or user == util.username():
395 if user in tusers or group in tgroups or user == util.username():
396 return True
396 return True
397
397
398 if self._reportuntrusted:
398 if self._reportuntrusted:
399 self.warn(_('not trusting file %s from untrusted '
399 self.warn(_('not trusting file %s from untrusted '
400 'user %s, group %s\n') % (f, user, group))
400 'user %s, group %s\n') % (f, user, group))
401 return False
401 return False
402
402
403 def readconfig(self, filename, root=None, trust=False,
403 def readconfig(self, filename, root=None, trust=False,
404 sections=None, remap=None):
404 sections=None, remap=None):
405 try:
405 try:
406 fp = open(filename, r'rb')
406 fp = open(filename, r'rb')
407 except IOError:
407 except IOError:
408 if not sections: # ignore unless we were looking for something
408 if not sections: # ignore unless we were looking for something
409 return
409 return
410 raise
410 raise
411
411
412 cfg = config.config()
412 cfg = config.config()
413 trusted = sections or trust or self._trusted(fp, filename)
413 trusted = sections or trust or self._trusted(fp, filename)
414
414
415 try:
415 try:
416 cfg.read(filename, fp, sections=sections, remap=remap)
416 cfg.read(filename, fp, sections=sections, remap=remap)
417 fp.close()
417 fp.close()
418 except error.ConfigError as inst:
418 except error.ConfigError as inst:
419 if trusted:
419 if trusted:
420 raise
420 raise
421 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
421 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
422
422
423 if self.plain():
423 if self.plain():
424 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
424 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
425 'logtemplate', 'message-output', 'statuscopies', 'style',
425 'logtemplate', 'message-output', 'statuscopies', 'style',
426 'traceback', 'verbose'):
426 'traceback', 'verbose'):
427 if k in cfg['ui']:
427 if k in cfg['ui']:
428 del cfg['ui'][k]
428 del cfg['ui'][k]
429 for k, v in cfg.items('defaults'):
429 for k, v in cfg.items('defaults'):
430 del cfg['defaults'][k]
430 del cfg['defaults'][k]
431 for k, v in cfg.items('commands'):
431 for k, v in cfg.items('commands'):
432 del cfg['commands'][k]
432 del cfg['commands'][k]
433 # Don't remove aliases from the configuration if in the exceptionlist
433 # Don't remove aliases from the configuration if in the exceptionlist
434 if self.plain('alias'):
434 if self.plain('alias'):
435 for k, v in cfg.items('alias'):
435 for k, v in cfg.items('alias'):
436 del cfg['alias'][k]
436 del cfg['alias'][k]
437 if self.plain('revsetalias'):
437 if self.plain('revsetalias'):
438 for k, v in cfg.items('revsetalias'):
438 for k, v in cfg.items('revsetalias'):
439 del cfg['revsetalias'][k]
439 del cfg['revsetalias'][k]
440 if self.plain('templatealias'):
440 if self.plain('templatealias'):
441 for k, v in cfg.items('templatealias'):
441 for k, v in cfg.items('templatealias'):
442 del cfg['templatealias'][k]
442 del cfg['templatealias'][k]
443
443
444 if trusted:
444 if trusted:
445 self._tcfg.update(cfg)
445 self._tcfg.update(cfg)
446 self._tcfg.update(self._ocfg)
446 self._tcfg.update(self._ocfg)
447 self._ucfg.update(cfg)
447 self._ucfg.update(cfg)
448 self._ucfg.update(self._ocfg)
448 self._ucfg.update(self._ocfg)
449
449
450 if root is None:
450 if root is None:
451 root = os.path.expanduser('~')
451 root = os.path.expanduser('~')
452 self.fixconfig(root=root)
452 self.fixconfig(root=root)
453
453
454 def fixconfig(self, root=None, section=None):
454 def fixconfig(self, root=None, section=None):
455 if section in (None, 'paths'):
455 if section in (None, 'paths'):
456 # expand vars and ~
456 # expand vars and ~
457 # translate paths relative to root (or home) into absolute paths
457 # translate paths relative to root (or home) into absolute paths
458 root = root or encoding.getcwd()
458 root = root or encoding.getcwd()
459 for c in self._tcfg, self._ucfg, self._ocfg:
459 for c in self._tcfg, self._ucfg, self._ocfg:
460 for n, p in c.items('paths'):
460 for n, p in c.items('paths'):
461 # Ignore sub-options.
461 # Ignore sub-options.
462 if ':' in n:
462 if ':' in n:
463 continue
463 continue
464 if not p:
464 if not p:
465 continue
465 continue
466 if '%%' in p:
466 if '%%' in p:
467 s = self.configsource('paths', n) or 'none'
467 s = self.configsource('paths', n) or 'none'
468 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
468 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
469 % (n, p, s))
469 % (n, p, s))
470 p = p.replace('%%', '%')
470 p = p.replace('%%', '%')
471 p = util.expandpath(p)
471 p = util.expandpath(p)
472 if not util.hasscheme(p) and not os.path.isabs(p):
472 if not util.hasscheme(p) and not os.path.isabs(p):
473 p = os.path.normpath(os.path.join(root, p))
473 p = os.path.normpath(os.path.join(root, p))
474 c.set("paths", n, p)
474 c.set("paths", n, p)
475
475
476 if section in (None, 'ui'):
476 if section in (None, 'ui'):
477 # update ui options
477 # update ui options
478 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
478 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
479 self.debugflag = self.configbool('ui', 'debug')
479 self.debugflag = self.configbool('ui', 'debug')
480 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
480 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
481 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
481 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
482 if self.verbose and self.quiet:
482 if self.verbose and self.quiet:
483 self.quiet = self.verbose = False
483 self.quiet = self.verbose = False
484 self._reportuntrusted = self.debugflag or self.configbool("ui",
484 self._reportuntrusted = self.debugflag or self.configbool("ui",
485 "report_untrusted")
485 "report_untrusted")
486 self.tracebackflag = self.configbool('ui', 'traceback')
486 self.tracebackflag = self.configbool('ui', 'traceback')
487 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
487 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
488
488
489 if section in (None, 'trusted'):
489 if section in (None, 'trusted'):
490 # update trust information
490 # update trust information
491 self._trustusers.update(self.configlist('trusted', 'users'))
491 self._trustusers.update(self.configlist('trusted', 'users'))
492 self._trustgroups.update(self.configlist('trusted', 'groups'))
492 self._trustgroups.update(self.configlist('trusted', 'groups'))
493
493
494 if section in (None, b'devel', b'ui') and self.debugflag:
494 if section in (None, b'devel', b'ui') and self.debugflag:
495 tracked = set()
495 tracked = set()
496 if self.configbool(b'devel', b'debug.extensions'):
496 if self.configbool(b'devel', b'debug.extensions'):
497 tracked.add(b'extension')
497 tracked.add(b'extension')
498 if tracked:
498 if tracked:
499 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
499 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
500 self.setlogger(b'debug', logger)
500 self.setlogger(b'debug', logger)
501
501
502 def backupconfig(self, section, item):
502 def backupconfig(self, section, item):
503 return (self._ocfg.backup(section, item),
503 return (self._ocfg.backup(section, item),
504 self._tcfg.backup(section, item),
504 self._tcfg.backup(section, item),
505 self._ucfg.backup(section, item),)
505 self._ucfg.backup(section, item),)
506 def restoreconfig(self, data):
506 def restoreconfig(self, data):
507 self._ocfg.restore(data[0])
507 self._ocfg.restore(data[0])
508 self._tcfg.restore(data[1])
508 self._tcfg.restore(data[1])
509 self._ucfg.restore(data[2])
509 self._ucfg.restore(data[2])
510
510
511 def setconfig(self, section, name, value, source=''):
511 def setconfig(self, section, name, value, source=''):
512 for cfg in (self._ocfg, self._tcfg, self._ucfg):
512 for cfg in (self._ocfg, self._tcfg, self._ucfg):
513 cfg.set(section, name, value, source)
513 cfg.set(section, name, value, source)
514 self.fixconfig(section=section)
514 self.fixconfig(section=section)
515 self._maybetweakdefaults()
515 self._maybetweakdefaults()
516
516
517 def _data(self, untrusted):
517 def _data(self, untrusted):
518 return untrusted and self._ucfg or self._tcfg
518 return untrusted and self._ucfg or self._tcfg
519
519
520 def configsource(self, section, name, untrusted=False):
520 def configsource(self, section, name, untrusted=False):
521 return self._data(untrusted).source(section, name)
521 return self._data(untrusted).source(section, name)
522
522
523 def config(self, section, name, default=_unset, untrusted=False):
523 def config(self, section, name, default=_unset, untrusted=False):
524 """return the plain string version of a config"""
524 """return the plain string version of a config"""
525 value = self._config(section, name, default=default,
525 value = self._config(section, name, default=default,
526 untrusted=untrusted)
526 untrusted=untrusted)
527 if value is _unset:
527 if value is _unset:
528 return None
528 return None
529 return value
529 return value
530
530
531 def _config(self, section, name, default=_unset, untrusted=False):
531 def _config(self, section, name, default=_unset, untrusted=False):
532 value = itemdefault = default
532 value = itemdefault = default
533 item = self._knownconfig.get(section, {}).get(name)
533 item = self._knownconfig.get(section, {}).get(name)
534 alternates = [(section, name)]
534 alternates = [(section, name)]
535
535
536 if item is not None:
536 if item is not None:
537 alternates.extend(item.alias)
537 alternates.extend(item.alias)
538 if callable(item.default):
538 if callable(item.default):
539 itemdefault = item.default()
539 itemdefault = item.default()
540 else:
540 else:
541 itemdefault = item.default
541 itemdefault = item.default
542 else:
542 else:
543 msg = ("accessing unregistered config item: '%s.%s'")
543 msg = ("accessing unregistered config item: '%s.%s'")
544 msg %= (section, name)
544 msg %= (section, name)
545 self.develwarn(msg, 2, 'warn-config-unknown')
545 self.develwarn(msg, 2, 'warn-config-unknown')
546
546
547 if default is _unset:
547 if default is _unset:
548 if item is None:
548 if item is None:
549 value = default
549 value = default
550 elif item.default is configitems.dynamicdefault:
550 elif item.default is configitems.dynamicdefault:
551 value = None
551 value = None
552 msg = "config item requires an explicit default value: '%s.%s'"
552 msg = "config item requires an explicit default value: '%s.%s'"
553 msg %= (section, name)
553 msg %= (section, name)
554 self.develwarn(msg, 2, 'warn-config-default')
554 self.develwarn(msg, 2, 'warn-config-default')
555 else:
555 else:
556 value = itemdefault
556 value = itemdefault
557 elif (item is not None
557 elif (item is not None
558 and item.default is not configitems.dynamicdefault
558 and item.default is not configitems.dynamicdefault
559 and default != itemdefault):
559 and default != itemdefault):
560 msg = ("specifying a mismatched default value for a registered "
560 msg = ("specifying a mismatched default value for a registered "
561 "config item: '%s.%s' '%s'")
561 "config item: '%s.%s' '%s'")
562 msg %= (section, name, pycompat.bytestr(default))
562 msg %= (section, name, pycompat.bytestr(default))
563 self.develwarn(msg, 2, 'warn-config-default')
563 self.develwarn(msg, 2, 'warn-config-default')
564
564
565 for s, n in alternates:
565 for s, n in alternates:
566 candidate = self._data(untrusted).get(s, n, None)
566 candidate = self._data(untrusted).get(s, n, None)
567 if candidate is not None:
567 if candidate is not None:
568 value = candidate
568 value = candidate
569 break
569 break
570
570
571 if self.debugflag and not untrusted and self._reportuntrusted:
571 if self.debugflag and not untrusted and self._reportuntrusted:
572 for s, n in alternates:
572 for s, n in alternates:
573 uvalue = self._ucfg.get(s, n)
573 uvalue = self._ucfg.get(s, n)
574 if uvalue is not None and uvalue != value:
574 if uvalue is not None and uvalue != value:
575 self.debug("ignoring untrusted configuration option "
575 self.debug("ignoring untrusted configuration option "
576 "%s.%s = %s\n" % (s, n, uvalue))
576 "%s.%s = %s\n" % (s, n, uvalue))
577 return value
577 return value
578
578
579 def configsuboptions(self, section, name, default=_unset, untrusted=False):
579 def configsuboptions(self, section, name, default=_unset, untrusted=False):
580 """Get a config option and all sub-options.
580 """Get a config option and all sub-options.
581
581
582 Some config options have sub-options that are declared with the
582 Some config options have sub-options that are declared with the
583 format "key:opt = value". This method is used to return the main
583 format "key:opt = value". This method is used to return the main
584 option and all its declared sub-options.
584 option and all its declared sub-options.
585
585
586 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
586 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
587 is a dict of defined sub-options where keys and values are strings.
587 is a dict of defined sub-options where keys and values are strings.
588 """
588 """
589 main = self.config(section, name, default, untrusted=untrusted)
589 main = self.config(section, name, default, untrusted=untrusted)
590 data = self._data(untrusted)
590 data = self._data(untrusted)
591 sub = {}
591 sub = {}
592 prefix = '%s:' % name
592 prefix = '%s:' % name
593 for k, v in data.items(section):
593 for k, v in data.items(section):
594 if k.startswith(prefix):
594 if k.startswith(prefix):
595 sub[k[len(prefix):]] = v
595 sub[k[len(prefix):]] = v
596
596
597 if self.debugflag and not untrusted and self._reportuntrusted:
597 if self.debugflag and not untrusted and self._reportuntrusted:
598 for k, v in sub.items():
598 for k, v in sub.items():
599 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
599 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
600 if uvalue is not None and uvalue != v:
600 if uvalue is not None and uvalue != v:
601 self.debug('ignoring untrusted configuration option '
601 self.debug('ignoring untrusted configuration option '
602 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
602 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
603
603
604 return main, sub
604 return main, sub
605
605
606 def configpath(self, section, name, default=_unset, untrusted=False):
606 def configpath(self, section, name, default=_unset, untrusted=False):
607 'get a path config item, expanded relative to repo root or config file'
607 'get a path config item, expanded relative to repo root or config file'
608 v = self.config(section, name, default, untrusted)
608 v = self.config(section, name, default, untrusted)
609 if v is None:
609 if v is None:
610 return None
610 return None
611 if not os.path.isabs(v) or "://" not in v:
611 if not os.path.isabs(v) or "://" not in v:
612 src = self.configsource(section, name, untrusted)
612 src = self.configsource(section, name, untrusted)
613 if ':' in src:
613 if ':' in src:
614 base = os.path.dirname(src.rsplit(':')[0])
614 base = os.path.dirname(src.rsplit(':')[0])
615 v = os.path.join(base, os.path.expanduser(v))
615 v = os.path.join(base, os.path.expanduser(v))
616 return v
616 return v
617
617
618 def configbool(self, section, name, default=_unset, untrusted=False):
618 def configbool(self, section, name, default=_unset, untrusted=False):
619 """parse a configuration element as a boolean
619 """parse a configuration element as a boolean
620
620
621 >>> u = ui(); s = b'foo'
621 >>> u = ui(); s = b'foo'
622 >>> u.setconfig(s, b'true', b'yes')
622 >>> u.setconfig(s, b'true', b'yes')
623 >>> u.configbool(s, b'true')
623 >>> u.configbool(s, b'true')
624 True
624 True
625 >>> u.setconfig(s, b'false', b'no')
625 >>> u.setconfig(s, b'false', b'no')
626 >>> u.configbool(s, b'false')
626 >>> u.configbool(s, b'false')
627 False
627 False
628 >>> u.configbool(s, b'unknown')
628 >>> u.configbool(s, b'unknown')
629 False
629 False
630 >>> u.configbool(s, b'unknown', True)
630 >>> u.configbool(s, b'unknown', True)
631 True
631 True
632 >>> u.setconfig(s, b'invalid', b'somevalue')
632 >>> u.setconfig(s, b'invalid', b'somevalue')
633 >>> u.configbool(s, b'invalid')
633 >>> u.configbool(s, b'invalid')
634 Traceback (most recent call last):
634 Traceback (most recent call last):
635 ...
635 ...
636 ConfigError: foo.invalid is not a boolean ('somevalue')
636 ConfigError: foo.invalid is not a boolean ('somevalue')
637 """
637 """
638
638
639 v = self._config(section, name, default, untrusted=untrusted)
639 v = self._config(section, name, default, untrusted=untrusted)
640 if v is None:
640 if v is None:
641 return v
641 return v
642 if v is _unset:
642 if v is _unset:
643 if default is _unset:
643 if default is _unset:
644 return False
644 return False
645 return default
645 return default
646 if isinstance(v, bool):
646 if isinstance(v, bool):
647 return v
647 return v
648 b = stringutil.parsebool(v)
648 b = stringutil.parsebool(v)
649 if b is None:
649 if b is None:
650 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
650 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
651 % (section, name, v))
651 % (section, name, v))
652 return b
652 return b
653
653
654 def configwith(self, convert, section, name, default=_unset,
654 def configwith(self, convert, section, name, default=_unset,
655 desc=None, untrusted=False):
655 desc=None, untrusted=False):
656 """parse a configuration element with a conversion function
656 """parse a configuration element with a conversion function
657
657
658 >>> u = ui(); s = b'foo'
658 >>> u = ui(); s = b'foo'
659 >>> u.setconfig(s, b'float1', b'42')
659 >>> u.setconfig(s, b'float1', b'42')
660 >>> u.configwith(float, s, b'float1')
660 >>> u.configwith(float, s, b'float1')
661 42.0
661 42.0
662 >>> u.setconfig(s, b'float2', b'-4.25')
662 >>> u.setconfig(s, b'float2', b'-4.25')
663 >>> u.configwith(float, s, b'float2')
663 >>> u.configwith(float, s, b'float2')
664 -4.25
664 -4.25
665 >>> u.configwith(float, s, b'unknown', 7)
665 >>> u.configwith(float, s, b'unknown', 7)
666 7.0
666 7.0
667 >>> u.setconfig(s, b'invalid', b'somevalue')
667 >>> u.setconfig(s, b'invalid', b'somevalue')
668 >>> u.configwith(float, s, b'invalid')
668 >>> u.configwith(float, s, b'invalid')
669 Traceback (most recent call last):
669 Traceback (most recent call last):
670 ...
670 ...
671 ConfigError: foo.invalid is not a valid float ('somevalue')
671 ConfigError: foo.invalid is not a valid float ('somevalue')
672 >>> u.configwith(float, s, b'invalid', desc=b'womble')
672 >>> u.configwith(float, s, b'invalid', desc=b'womble')
673 Traceback (most recent call last):
673 Traceback (most recent call last):
674 ...
674 ...
675 ConfigError: foo.invalid is not a valid womble ('somevalue')
675 ConfigError: foo.invalid is not a valid womble ('somevalue')
676 """
676 """
677
677
678 v = self.config(section, name, default, untrusted)
678 v = self.config(section, name, default, untrusted)
679 if v is None:
679 if v is None:
680 return v # do not attempt to convert None
680 return v # do not attempt to convert None
681 try:
681 try:
682 return convert(v)
682 return convert(v)
683 except (ValueError, error.ParseError):
683 except (ValueError, error.ParseError):
684 if desc is None:
684 if desc is None:
685 desc = pycompat.sysbytes(convert.__name__)
685 desc = pycompat.sysbytes(convert.__name__)
686 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
686 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
687 % (section, name, desc, v))
687 % (section, name, desc, v))
688
688
689 def configint(self, section, name, default=_unset, untrusted=False):
689 def configint(self, section, name, default=_unset, untrusted=False):
690 """parse a configuration element as an integer
690 """parse a configuration element as an integer
691
691
692 >>> u = ui(); s = b'foo'
692 >>> u = ui(); s = b'foo'
693 >>> u.setconfig(s, b'int1', b'42')
693 >>> u.setconfig(s, b'int1', b'42')
694 >>> u.configint(s, b'int1')
694 >>> u.configint(s, b'int1')
695 42
695 42
696 >>> u.setconfig(s, b'int2', b'-42')
696 >>> u.setconfig(s, b'int2', b'-42')
697 >>> u.configint(s, b'int2')
697 >>> u.configint(s, b'int2')
698 -42
698 -42
699 >>> u.configint(s, b'unknown', 7)
699 >>> u.configint(s, b'unknown', 7)
700 7
700 7
701 >>> u.setconfig(s, b'invalid', b'somevalue')
701 >>> u.setconfig(s, b'invalid', b'somevalue')
702 >>> u.configint(s, b'invalid')
702 >>> u.configint(s, b'invalid')
703 Traceback (most recent call last):
703 Traceback (most recent call last):
704 ...
704 ...
705 ConfigError: foo.invalid is not a valid integer ('somevalue')
705 ConfigError: foo.invalid is not a valid integer ('somevalue')
706 """
706 """
707
707
708 return self.configwith(int, section, name, default, 'integer',
708 return self.configwith(int, section, name, default, 'integer',
709 untrusted)
709 untrusted)
710
710
711 def configbytes(self, section, name, default=_unset, untrusted=False):
711 def configbytes(self, section, name, default=_unset, untrusted=False):
712 """parse a configuration element as a quantity in bytes
712 """parse a configuration element as a quantity in bytes
713
713
714 Units can be specified as b (bytes), k or kb (kilobytes), m or
714 Units can be specified as b (bytes), k or kb (kilobytes), m or
715 mb (megabytes), g or gb (gigabytes).
715 mb (megabytes), g or gb (gigabytes).
716
716
717 >>> u = ui(); s = b'foo'
717 >>> u = ui(); s = b'foo'
718 >>> u.setconfig(s, b'val1', b'42')
718 >>> u.setconfig(s, b'val1', b'42')
719 >>> u.configbytes(s, b'val1')
719 >>> u.configbytes(s, b'val1')
720 42
720 42
721 >>> u.setconfig(s, b'val2', b'42.5 kb')
721 >>> u.setconfig(s, b'val2', b'42.5 kb')
722 >>> u.configbytes(s, b'val2')
722 >>> u.configbytes(s, b'val2')
723 43520
723 43520
724 >>> u.configbytes(s, b'unknown', b'7 MB')
724 >>> u.configbytes(s, b'unknown', b'7 MB')
725 7340032
725 7340032
726 >>> u.setconfig(s, b'invalid', b'somevalue')
726 >>> u.setconfig(s, b'invalid', b'somevalue')
727 >>> u.configbytes(s, b'invalid')
727 >>> u.configbytes(s, b'invalid')
728 Traceback (most recent call last):
728 Traceback (most recent call last):
729 ...
729 ...
730 ConfigError: foo.invalid is not a byte quantity ('somevalue')
730 ConfigError: foo.invalid is not a byte quantity ('somevalue')
731 """
731 """
732
732
733 value = self._config(section, name, default, untrusted)
733 value = self._config(section, name, default, untrusted)
734 if value is _unset:
734 if value is _unset:
735 if default is _unset:
735 if default is _unset:
736 default = 0
736 default = 0
737 value = default
737 value = default
738 if not isinstance(value, bytes):
738 if not isinstance(value, bytes):
739 return value
739 return value
740 try:
740 try:
741 return util.sizetoint(value)
741 return util.sizetoint(value)
742 except error.ParseError:
742 except error.ParseError:
743 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
743 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
744 % (section, name, value))
744 % (section, name, value))
745
745
746 def configlist(self, section, name, default=_unset, untrusted=False):
746 def configlist(self, section, name, default=_unset, untrusted=False):
747 """parse a configuration element as a list of comma/space separated
747 """parse a configuration element as a list of comma/space separated
748 strings
748 strings
749
749
750 >>> u = ui(); s = b'foo'
750 >>> u = ui(); s = b'foo'
751 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
751 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
752 >>> u.configlist(s, b'list1')
752 >>> u.configlist(s, b'list1')
753 ['this', 'is', 'a small', 'test']
753 ['this', 'is', 'a small', 'test']
754 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
754 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
755 >>> u.configlist(s, b'list2')
755 >>> u.configlist(s, b'list2')
756 ['this', 'is', 'a small', 'test']
756 ['this', 'is', 'a small', 'test']
757 """
757 """
758 # default is not always a list
758 # default is not always a list
759 v = self.configwith(config.parselist, section, name, default,
759 v = self.configwith(config.parselist, section, name, default,
760 'list', untrusted)
760 'list', untrusted)
761 if isinstance(v, bytes):
761 if isinstance(v, bytes):
762 return config.parselist(v)
762 return config.parselist(v)
763 elif v is None:
763 elif v is None:
764 return []
764 return []
765 return v
765 return v
766
766
767 def configdate(self, section, name, default=_unset, untrusted=False):
767 def configdate(self, section, name, default=_unset, untrusted=False):
768 """parse a configuration element as a tuple of ints
768 """parse a configuration element as a tuple of ints
769
769
770 >>> u = ui(); s = b'foo'
770 >>> u = ui(); s = b'foo'
771 >>> u.setconfig(s, b'date', b'0 0')
771 >>> u.setconfig(s, b'date', b'0 0')
772 >>> u.configdate(s, b'date')
772 >>> u.configdate(s, b'date')
773 (0, 0)
773 (0, 0)
774 """
774 """
775 if self.config(section, name, default, untrusted):
775 if self.config(section, name, default, untrusted):
776 return self.configwith(dateutil.parsedate, section, name, default,
776 return self.configwith(dateutil.parsedate, section, name, default,
777 'date', untrusted)
777 'date', untrusted)
778 if default is _unset:
778 if default is _unset:
779 return None
779 return None
780 return default
780 return default
781
781
782 def hasconfig(self, section, name, untrusted=False):
782 def hasconfig(self, section, name, untrusted=False):
783 return self._data(untrusted).hasitem(section, name)
783 return self._data(untrusted).hasitem(section, name)
784
784
785 def has_section(self, section, untrusted=False):
785 def has_section(self, section, untrusted=False):
786 '''tell whether section exists in config.'''
786 '''tell whether section exists in config.'''
787 return section in self._data(untrusted)
787 return section in self._data(untrusted)
788
788
789 def configitems(self, section, untrusted=False, ignoresub=False):
789 def configitems(self, section, untrusted=False, ignoresub=False):
790 items = self._data(untrusted).items(section)
790 items = self._data(untrusted).items(section)
791 if ignoresub:
791 if ignoresub:
792 items = [i for i in items if ':' not in i[0]]
792 items = [i for i in items if ':' not in i[0]]
793 if self.debugflag and not untrusted and self._reportuntrusted:
793 if self.debugflag and not untrusted and self._reportuntrusted:
794 for k, v in self._ucfg.items(section):
794 for k, v in self._ucfg.items(section):
795 if self._tcfg.get(section, k) != v:
795 if self._tcfg.get(section, k) != v:
796 self.debug("ignoring untrusted configuration option "
796 self.debug("ignoring untrusted configuration option "
797 "%s.%s = %s\n" % (section, k, v))
797 "%s.%s = %s\n" % (section, k, v))
798 return items
798 return items
799
799
800 def walkconfig(self, untrusted=False):
800 def walkconfig(self, untrusted=False):
801 cfg = self._data(untrusted)
801 cfg = self._data(untrusted)
802 for section in cfg.sections():
802 for section in cfg.sections():
803 for name, value in self.configitems(section, untrusted):
803 for name, value in self.configitems(section, untrusted):
804 yield section, name, value
804 yield section, name, value
805
805
806 def plain(self, feature=None):
806 def plain(self, feature=None):
807 '''is plain mode active?
807 '''is plain mode active?
808
808
809 Plain mode means that all configuration variables which affect
809 Plain mode means that all configuration variables which affect
810 the behavior and output of Mercurial should be
810 the behavior and output of Mercurial should be
811 ignored. Additionally, the output should be stable,
811 ignored. Additionally, the output should be stable,
812 reproducible and suitable for use in scripts or applications.
812 reproducible and suitable for use in scripts or applications.
813
813
814 The only way to trigger plain mode is by setting either the
814 The only way to trigger plain mode is by setting either the
815 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
815 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
816
816
817 The return value can either be
817 The return value can either be
818 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
818 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
819 - False if feature is disabled by default and not included in HGPLAIN
819 - False if feature is disabled by default and not included in HGPLAIN
820 - True otherwise
820 - True otherwise
821 '''
821 '''
822 if ('HGPLAIN' not in encoding.environ and
822 if ('HGPLAIN' not in encoding.environ and
823 'HGPLAINEXCEPT' not in encoding.environ):
823 'HGPLAINEXCEPT' not in encoding.environ):
824 return False
824 return False
825 exceptions = encoding.environ.get('HGPLAINEXCEPT',
825 exceptions = encoding.environ.get('HGPLAINEXCEPT',
826 '').strip().split(',')
826 '').strip().split(',')
827 # TODO: add support for HGPLAIN=+feature,-feature syntax
827 # TODO: add support for HGPLAIN=+feature,-feature syntax
828 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
828 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
829 exceptions.append('strictflags')
829 exceptions.append('strictflags')
830 if feature and exceptions:
830 if feature and exceptions:
831 return feature not in exceptions
831 return feature not in exceptions
832 return True
832 return True
833
833
834 def username(self, acceptempty=False):
834 def username(self, acceptempty=False):
835 """Return default username to be used in commits.
835 """Return default username to be used in commits.
836
836
837 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
837 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
838 and stop searching if one of these is set.
838 and stop searching if one of these is set.
839 If not found and acceptempty is True, returns None.
839 If not found and acceptempty is True, returns None.
840 If not found and ui.askusername is True, ask the user, else use
840 If not found and ui.askusername is True, ask the user, else use
841 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
841 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
842 If no username could be found, raise an Abort error.
842 If no username could be found, raise an Abort error.
843 """
843 """
844 user = encoding.environ.get("HGUSER")
844 user = encoding.environ.get("HGUSER")
845 if user is None:
845 if user is None:
846 user = self.config("ui", "username")
846 user = self.config("ui", "username")
847 if user is not None:
847 if user is not None:
848 user = os.path.expandvars(user)
848 user = os.path.expandvars(user)
849 if user is None:
849 if user is None:
850 user = encoding.environ.get("EMAIL")
850 user = encoding.environ.get("EMAIL")
851 if user is None and acceptempty:
851 if user is None and acceptempty:
852 return user
852 return user
853 if user is None and self.configbool("ui", "askusername"):
853 if user is None and self.configbool("ui", "askusername"):
854 user = self.prompt(_("enter a commit username:"), default=None)
854 user = self.prompt(_("enter a commit username:"), default=None)
855 if user is None and not self.interactive():
855 if user is None and not self.interactive():
856 try:
856 try:
857 user = '%s@%s' % (procutil.getuser(),
857 user = '%s@%s' % (procutil.getuser(),
858 encoding.strtolocal(socket.getfqdn()))
858 encoding.strtolocal(socket.getfqdn()))
859 self.warn(_("no username found, using '%s' instead\n") % user)
859 self.warn(_("no username found, using '%s' instead\n") % user)
860 except KeyError:
860 except KeyError:
861 pass
861 pass
862 if not user:
862 if not user:
863 raise error.Abort(_('no username supplied'),
863 raise error.Abort(_('no username supplied'),
864 hint=_("use 'hg config --edit' "
864 hint=_("use 'hg config --edit' "
865 'to set your username'))
865 'to set your username'))
866 if "\n" in user:
866 if "\n" in user:
867 raise error.Abort(_("username %r contains a newline\n")
867 raise error.Abort(_("username %r contains a newline\n")
868 % pycompat.bytestr(user))
868 % pycompat.bytestr(user))
869 return user
869 return user
870
870
871 def shortuser(self, user):
871 def shortuser(self, user):
872 """Return a short representation of a user name or email address."""
872 """Return a short representation of a user name or email address."""
873 if not self.verbose:
873 if not self.verbose:
874 user = stringutil.shortuser(user)
874 user = stringutil.shortuser(user)
875 return user
875 return user
876
876
877 def expandpath(self, loc, default=None):
877 def expandpath(self, loc, default=None):
878 """Return repository location relative to cwd or from [paths]"""
878 """Return repository location relative to cwd or from [paths]"""
879 try:
879 try:
880 p = self.paths.getpath(loc)
880 p = self.paths.getpath(loc)
881 if p:
881 if p:
882 return p.rawloc
882 return p.rawloc
883 except error.RepoError:
883 except error.RepoError:
884 pass
884 pass
885
885
886 if default:
886 if default:
887 try:
887 try:
888 p = self.paths.getpath(default)
888 p = self.paths.getpath(default)
889 if p:
889 if p:
890 return p.rawloc
890 return p.rawloc
891 except error.RepoError:
891 except error.RepoError:
892 pass
892 pass
893
893
894 return loc
894 return loc
895
895
896 @util.propertycache
896 @util.propertycache
897 def paths(self):
897 def paths(self):
898 return paths(self)
898 return paths(self)
899
899
900 @property
900 @property
901 def fout(self):
901 def fout(self):
902 return self._fout
902 return self._fout
903
903
904 @fout.setter
904 @fout.setter
905 def fout(self, f):
905 def fout(self, f):
906 self._fout = f
906 self._fout = f
907 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
907 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
908
908
909 @property
909 @property
910 def ferr(self):
910 def ferr(self):
911 return self._ferr
911 return self._ferr
912
912
913 @ferr.setter
913 @ferr.setter
914 def ferr(self, f):
914 def ferr(self, f):
915 self._ferr = f
915 self._ferr = f
916 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
916 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
917
917
918 @property
918 @property
919 def fin(self):
919 def fin(self):
920 return self._fin
920 return self._fin
921
921
922 @fin.setter
922 @fin.setter
923 def fin(self, f):
923 def fin(self, f):
924 self._fin = f
924 self._fin = f
925
925
926 @property
926 @property
927 def fmsg(self):
927 def fmsg(self):
928 """Stream dedicated for status/error messages; may be None if
928 """Stream dedicated for status/error messages; may be None if
929 fout/ferr are used"""
929 fout/ferr are used"""
930 return self._fmsg
930 return self._fmsg
931
931
932 @fmsg.setter
932 @fmsg.setter
933 def fmsg(self, f):
933 def fmsg(self, f):
934 self._fmsg = f
934 self._fmsg = f
935 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
935 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
936
936
937 def pushbuffer(self, error=False, subproc=False, labeled=False):
937 def pushbuffer(self, error=False, subproc=False, labeled=False):
938 """install a buffer to capture standard output of the ui object
938 """install a buffer to capture standard output of the ui object
939
939
940 If error is True, the error output will be captured too.
940 If error is True, the error output will be captured too.
941
941
942 If subproc is True, output from subprocesses (typically hooks) will be
942 If subproc is True, output from subprocesses (typically hooks) will be
943 captured too.
943 captured too.
944
944
945 If labeled is True, any labels associated with buffered
945 If labeled is True, any labels associated with buffered
946 output will be handled. By default, this has no effect
946 output will be handled. By default, this has no effect
947 on the output returned, but extensions and GUI tools may
947 on the output returned, but extensions and GUI tools may
948 handle this argument and returned styled output. If output
948 handle this argument and returned styled output. If output
949 is being buffered so it can be captured and parsed or
949 is being buffered so it can be captured and parsed or
950 processed, labeled should not be set to True.
950 processed, labeled should not be set to True.
951 """
951 """
952 self._buffers.append([])
952 self._buffers.append([])
953 self._bufferstates.append((error, subproc, labeled))
953 self._bufferstates.append((error, subproc, labeled))
954 self._bufferapplylabels = labeled
954 self._bufferapplylabels = labeled
955
955
956 def popbuffer(self):
956 def popbuffer(self):
957 '''pop the last buffer and return the buffered output'''
957 '''pop the last buffer and return the buffered output'''
958 self._bufferstates.pop()
958 self._bufferstates.pop()
959 if self._bufferstates:
959 if self._bufferstates:
960 self._bufferapplylabels = self._bufferstates[-1][2]
960 self._bufferapplylabels = self._bufferstates[-1][2]
961 else:
961 else:
962 self._bufferapplylabels = None
962 self._bufferapplylabels = None
963
963
964 return "".join(self._buffers.pop())
964 return "".join(self._buffers.pop())
965
965
966 def _isbuffered(self, dest):
966 def _isbuffered(self, dest):
967 if dest is self._fout:
967 if dest is self._fout:
968 return bool(self._buffers)
968 return bool(self._buffers)
969 if dest is self._ferr:
969 if dest is self._ferr:
970 return bool(self._bufferstates and self._bufferstates[-1][0])
970 return bool(self._bufferstates and self._bufferstates[-1][0])
971 return False
971 return False
972
972
973 def canwritewithoutlabels(self):
973 def canwritewithoutlabels(self):
974 '''check if write skips the label'''
974 '''check if write skips the label'''
975 if self._buffers and not self._bufferapplylabels:
975 if self._buffers and not self._bufferapplylabels:
976 return True
976 return True
977 return self._colormode is None
977 return self._colormode is None
978
978
979 def canbatchlabeledwrites(self):
979 def canbatchlabeledwrites(self):
980 '''check if write calls with labels are batchable'''
980 '''check if write calls with labels are batchable'''
981 # Windows color printing is special, see ``write``.
981 # Windows color printing is special, see ``write``.
982 return self._colormode != 'win32'
982 return self._colormode != 'win32'
983
983
984 def write(self, *args, **opts):
984 def write(self, *args, **opts):
985 '''write args to output
985 '''write args to output
986
986
987 By default, this method simply writes to the buffer or stdout.
987 By default, this method simply writes to the buffer or stdout.
988 Color mode can be set on the UI class to have the output decorated
988 Color mode can be set on the UI class to have the output decorated
989 with color modifier before being written to stdout.
989 with color modifier before being written to stdout.
990
990
991 The color used is controlled by an optional keyword argument, "label".
991 The color used is controlled by an optional keyword argument, "label".
992 This should be a string containing label names separated by space.
992 This should be a string containing label names separated by space.
993 Label names take the form of "topic.type". For example, ui.debug()
993 Label names take the form of "topic.type". For example, ui.debug()
994 issues a label of "ui.debug".
994 issues a label of "ui.debug".
995
995
996 When labeling output for a specific command, a label of
996 When labeling output for a specific command, a label of
997 "cmdname.type" is recommended. For example, status issues
997 "cmdname.type" is recommended. For example, status issues
998 a label of "status.modified" for modified files.
998 a label of "status.modified" for modified files.
999 '''
999 '''
1000 dest = self._fout
1000 dest = self._fout
1001
1001
1002 # inlined _write() for speed
1002 # inlined _write() for speed
1003 if self._buffers:
1003 if self._buffers:
1004 label = opts.get(r'label', '')
1004 label = opts.get(r'label', '')
1005 if label and self._bufferapplylabels:
1005 if label and self._bufferapplylabels:
1006 self._buffers[-1].extend(self.label(a, label) for a in args)
1006 self._buffers[-1].extend(self.label(a, label) for a in args)
1007 else:
1007 else:
1008 self._buffers[-1].extend(args)
1008 self._buffers[-1].extend(args)
1009 return
1009 return
1010
1010
1011 # inliend _writenobuf() for speed
1011 # inliend _writenobuf() for speed
1012 self._progclear()
1012 self._progclear()
1013 msg = b''.join(args)
1013 msg = b''.join(args)
1014
1014
1015 # opencode timeblockedsection because this is a critical path
1015 # opencode timeblockedsection because this is a critical path
1016 starttime = util.timer()
1016 starttime = util.timer()
1017 try:
1017 try:
1018 if self._colormode == 'win32':
1018 if self._colormode == 'win32':
1019 # windows color printing is its own can of crab, defer to
1019 # windows color printing is its own can of crab, defer to
1020 # the color module and that is it.
1020 # the color module and that is it.
1021 color.win32print(self, dest.write, msg, **opts)
1021 color.win32print(self, dest.write, msg, **opts)
1022 else:
1022 else:
1023 if self._colormode is not None:
1023 if self._colormode is not None:
1024 label = opts.get(r'label', '')
1024 label = opts.get(r'label', '')
1025 msg = self.label(msg, label)
1025 msg = self.label(msg, label)
1026 dest.write(msg)
1026 dest.write(msg)
1027 except IOError as err:
1027 except IOError as err:
1028 raise error.StdioError(err)
1028 raise error.StdioError(err)
1029 finally:
1029 finally:
1030 self._blockedtimes['stdio_blocked'] += \
1030 self._blockedtimes['stdio_blocked'] += \
1031 (util.timer() - starttime) * 1000
1031 (util.timer() - starttime) * 1000
1032
1032
1033 def write_err(self, *args, **opts):
1033 def write_err(self, *args, **opts):
1034 self._write(self._ferr, *args, **opts)
1034 self._write(self._ferr, *args, **opts)
1035
1035
1036 def _write(self, dest, *args, **opts):
1036 def _write(self, dest, *args, **opts):
1037 # update write() as well if you touch this code
1037 # update write() as well if you touch this code
1038 if self._isbuffered(dest):
1038 if self._isbuffered(dest):
1039 label = opts.get(r'label', '')
1039 label = opts.get(r'label', '')
1040 if label and self._bufferapplylabels:
1040 if label and self._bufferapplylabels:
1041 self._buffers[-1].extend(self.label(a, label) for a in args)
1041 self._buffers[-1].extend(self.label(a, label) for a in args)
1042 else:
1042 else:
1043 self._buffers[-1].extend(args)
1043 self._buffers[-1].extend(args)
1044 else:
1044 else:
1045 self._writenobuf(dest, *args, **opts)
1045 self._writenobuf(dest, *args, **opts)
1046
1046
1047 def _writenobuf(self, dest, *args, **opts):
1047 def _writenobuf(self, dest, *args, **opts):
1048 # update write() as well if you touch this code
1048 # update write() as well if you touch this code
1049 self._progclear()
1049 self._progclear()
1050 msg = b''.join(args)
1050 msg = b''.join(args)
1051
1051
1052 # opencode timeblockedsection because this is a critical path
1052 # opencode timeblockedsection because this is a critical path
1053 starttime = util.timer()
1053 starttime = util.timer()
1054 try:
1054 try:
1055 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1055 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1056 self._fout.flush()
1056 self._fout.flush()
1057 if getattr(dest, 'structured', False):
1057 if getattr(dest, 'structured', False):
1058 # channel for machine-readable output with metadata, where
1058 # channel for machine-readable output with metadata, where
1059 # no extra colorization is necessary.
1059 # no extra colorization is necessary.
1060 dest.write(msg, **opts)
1060 dest.write(msg, **opts)
1061 elif self._colormode == 'win32':
1061 elif self._colormode == 'win32':
1062 # windows color printing is its own can of crab, defer to
1062 # windows color printing is its own can of crab, defer to
1063 # the color module and that is it.
1063 # the color module and that is it.
1064 color.win32print(self, dest.write, msg, **opts)
1064 color.win32print(self, dest.write, msg, **opts)
1065 else:
1065 else:
1066 if self._colormode is not None:
1066 if self._colormode is not None:
1067 label = opts.get(r'label', '')
1067 label = opts.get(r'label', '')
1068 msg = self.label(msg, label)
1068 msg = self.label(msg, label)
1069 dest.write(msg)
1069 dest.write(msg)
1070 # stderr may be buffered under win32 when redirected to files,
1070 # stderr may be buffered under win32 when redirected to files,
1071 # including stdout.
1071 # including stdout.
1072 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1072 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1073 dest.flush()
1073 dest.flush()
1074 except IOError as err:
1074 except IOError as err:
1075 if (dest is self._ferr
1075 if (dest is self._ferr
1076 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1076 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1077 # no way to report the error, so ignore it
1077 # no way to report the error, so ignore it
1078 return
1078 return
1079 raise error.StdioError(err)
1079 raise error.StdioError(err)
1080 finally:
1080 finally:
1081 self._blockedtimes['stdio_blocked'] += \
1081 self._blockedtimes['stdio_blocked'] += \
1082 (util.timer() - starttime) * 1000
1082 (util.timer() - starttime) * 1000
1083
1083
1084 def _writemsg(self, dest, *args, **opts):
1084 def _writemsg(self, dest, *args, **opts):
1085 _writemsgwith(self._write, dest, *args, **opts)
1085 _writemsgwith(self._write, dest, *args, **opts)
1086
1086
1087 def _writemsgnobuf(self, dest, *args, **opts):
1087 def _writemsgnobuf(self, dest, *args, **opts):
1088 _writemsgwith(self._writenobuf, dest, *args, **opts)
1088 _writemsgwith(self._writenobuf, dest, *args, **opts)
1089
1089
1090 def flush(self):
1090 def flush(self):
1091 # opencode timeblockedsection because this is a critical path
1091 # opencode timeblockedsection because this is a critical path
1092 starttime = util.timer()
1092 starttime = util.timer()
1093 try:
1093 try:
1094 try:
1094 try:
1095 self._fout.flush()
1095 self._fout.flush()
1096 except IOError as err:
1096 except IOError as err:
1097 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1097 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1098 raise error.StdioError(err)
1098 raise error.StdioError(err)
1099 finally:
1099 finally:
1100 try:
1100 try:
1101 self._ferr.flush()
1101 self._ferr.flush()
1102 except IOError as err:
1102 except IOError as err:
1103 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1103 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1104 raise error.StdioError(err)
1104 raise error.StdioError(err)
1105 finally:
1105 finally:
1106 self._blockedtimes['stdio_blocked'] += \
1106 self._blockedtimes['stdio_blocked'] += \
1107 (util.timer() - starttime) * 1000
1107 (util.timer() - starttime) * 1000
1108
1108
1109 def _isatty(self, fh):
1109 def _isatty(self, fh):
1110 if self.configbool('ui', 'nontty'):
1110 if self.configbool('ui', 'nontty'):
1111 return False
1111 return False
1112 return procutil.isatty(fh)
1112 return procutil.isatty(fh)
1113
1113
1114 def protectfinout(self):
1114 def protectfinout(self):
1115 """Duplicate ui streams and redirect original if they are stdio
1115 """Duplicate ui streams and redirect original if they are stdio
1116
1116
1117 Returns (fin, fout) which point to the original ui fds, but may be
1117 Returns (fin, fout) which point to the original ui fds, but may be
1118 copy of them. The returned streams can be considered "owned" in that
1118 copy of them. The returned streams can be considered "owned" in that
1119 print(), exec(), etc. never reach to them.
1119 print(), exec(), etc. never reach to them.
1120 """
1120 """
1121 if self._finoutredirected:
1121 if self._finoutredirected:
1122 # if already redirected, protectstdio() would just create another
1122 # if already redirected, protectstdio() would just create another
1123 # nullfd pair, which is equivalent to returning self._fin/_fout.
1123 # nullfd pair, which is equivalent to returning self._fin/_fout.
1124 return self._fin, self._fout
1124 return self._fin, self._fout
1125 fin, fout = procutil.protectstdio(self._fin, self._fout)
1125 fin, fout = procutil.protectstdio(self._fin, self._fout)
1126 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1126 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1127 return fin, fout
1127 return fin, fout
1128
1128
1129 def restorefinout(self, fin, fout):
1129 def restorefinout(self, fin, fout):
1130 """Restore ui streams from possibly duplicated (fin, fout)"""
1130 """Restore ui streams from possibly duplicated (fin, fout)"""
1131 if (fin, fout) == (self._fin, self._fout):
1131 if (fin, fout) == (self._fin, self._fout):
1132 return
1132 return
1133 procutil.restorestdio(self._fin, self._fout, fin, fout)
1133 procutil.restorestdio(self._fin, self._fout, fin, fout)
1134 # protectfinout() won't create more than one duplicated streams,
1134 # protectfinout() won't create more than one duplicated streams,
1135 # so we can just turn the redirection flag off.
1135 # so we can just turn the redirection flag off.
1136 self._finoutredirected = False
1136 self._finoutredirected = False
1137
1137
1138 @contextlib.contextmanager
1138 @contextlib.contextmanager
1139 def protectedfinout(self):
1139 def protectedfinout(self):
1140 """Run code block with protected standard streams"""
1140 """Run code block with protected standard streams"""
1141 fin, fout = self.protectfinout()
1141 fin, fout = self.protectfinout()
1142 try:
1142 try:
1143 yield fin, fout
1143 yield fin, fout
1144 finally:
1144 finally:
1145 self.restorefinout(fin, fout)
1145 self.restorefinout(fin, fout)
1146
1146
1147 def disablepager(self):
1147 def disablepager(self):
1148 self._disablepager = True
1148 self._disablepager = True
1149
1149
1150 def pager(self, command):
1150 def pager(self, command):
1151 """Start a pager for subsequent command output.
1151 """Start a pager for subsequent command output.
1152
1152
1153 Commands which produce a long stream of output should call
1153 Commands which produce a long stream of output should call
1154 this function to activate the user's preferred pagination
1154 this function to activate the user's preferred pagination
1155 mechanism (which may be no pager). Calling this function
1155 mechanism (which may be no pager). Calling this function
1156 precludes any future use of interactive functionality, such as
1156 precludes any future use of interactive functionality, such as
1157 prompting the user or activating curses.
1157 prompting the user or activating curses.
1158
1158
1159 Args:
1159 Args:
1160 command: The full, non-aliased name of the command. That is, "log"
1160 command: The full, non-aliased name of the command. That is, "log"
1161 not "history, "summary" not "summ", etc.
1161 not "history, "summary" not "summ", etc.
1162 """
1162 """
1163 if (self._disablepager
1163 if (self._disablepager
1164 or self.pageractive):
1164 or self.pageractive):
1165 # how pager should do is already determined
1165 # how pager should do is already determined
1166 return
1166 return
1167
1167
1168 if not command.startswith('internal-always-') and (
1168 if not command.startswith('internal-always-') and (
1169 # explicit --pager=on (= 'internal-always-' prefix) should
1169 # explicit --pager=on (= 'internal-always-' prefix) should
1170 # take precedence over disabling factors below
1170 # take precedence over disabling factors below
1171 command in self.configlist('pager', 'ignore')
1171 command in self.configlist('pager', 'ignore')
1172 or not self.configbool('ui', 'paginate')
1172 or not self.configbool('ui', 'paginate')
1173 or not self.configbool('pager', 'attend-' + command, True)
1173 or not self.configbool('pager', 'attend-' + command, True)
1174 or encoding.environ.get('TERM') == 'dumb'
1174 or encoding.environ.get('TERM') == 'dumb'
1175 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1175 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1176 # formatted() will need some adjustment.
1176 # formatted() will need some adjustment.
1177 or not self.formatted()
1177 or not self.formatted()
1178 or self.plain()
1178 or self.plain()
1179 or self._buffers
1179 or self._buffers
1180 # TODO: expose debugger-enabled on the UI object
1180 # TODO: expose debugger-enabled on the UI object
1181 or '--debugger' in pycompat.sysargv):
1181 or '--debugger' in pycompat.sysargv):
1182 # We only want to paginate if the ui appears to be
1182 # We only want to paginate if the ui appears to be
1183 # interactive, the user didn't say HGPLAIN or
1183 # interactive, the user didn't say HGPLAIN or
1184 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1184 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1185 return
1185 return
1186
1186
1187 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1187 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1188 if not pagercmd:
1188 if not pagercmd:
1189 return
1189 return
1190
1190
1191 pagerenv = {}
1191 pagerenv = {}
1192 for name, value in rcutil.defaultpagerenv().items():
1192 for name, value in rcutil.defaultpagerenv().items():
1193 if name not in encoding.environ:
1193 if name not in encoding.environ:
1194 pagerenv[name] = value
1194 pagerenv[name] = value
1195
1195
1196 self.debug('starting pager for command %s\n' %
1196 self.debug('starting pager for command %s\n' %
1197 stringutil.pprint(command))
1197 stringutil.pprint(command))
1198 self.flush()
1198 self.flush()
1199
1199
1200 wasformatted = self.formatted()
1200 wasformatted = self.formatted()
1201 if util.safehasattr(signal, "SIGPIPE"):
1201 if util.safehasattr(signal, "SIGPIPE"):
1202 signal.signal(signal.SIGPIPE, _catchterm)
1202 signal.signal(signal.SIGPIPE, _catchterm)
1203 if self._runpager(pagercmd, pagerenv):
1203 if self._runpager(pagercmd, pagerenv):
1204 self.pageractive = True
1204 self.pageractive = True
1205 # Preserve the formatted-ness of the UI. This is important
1205 # Preserve the formatted-ness of the UI. This is important
1206 # because we mess with stdout, which might confuse
1206 # because we mess with stdout, which might confuse
1207 # auto-detection of things being formatted.
1207 # auto-detection of things being formatted.
1208 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1208 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1209 self.setconfig('ui', 'interactive', False, 'pager')
1209 self.setconfig('ui', 'interactive', False, 'pager')
1210
1210
1211 # If pagermode differs from color.mode, reconfigure color now that
1211 # If pagermode differs from color.mode, reconfigure color now that
1212 # pageractive is set.
1212 # pageractive is set.
1213 cm = self._colormode
1213 cm = self._colormode
1214 if cm != self.config('color', 'pagermode', cm):
1214 if cm != self.config('color', 'pagermode', cm):
1215 color.setup(self)
1215 color.setup(self)
1216 else:
1216 else:
1217 # If the pager can't be spawned in dispatch when --pager=on is
1217 # If the pager can't be spawned in dispatch when --pager=on is
1218 # given, don't try again when the command runs, to avoid a duplicate
1218 # given, don't try again when the command runs, to avoid a duplicate
1219 # warning about a missing pager command.
1219 # warning about a missing pager command.
1220 self.disablepager()
1220 self.disablepager()
1221
1221
1222 def _runpager(self, command, env=None):
1222 def _runpager(self, command, env=None):
1223 """Actually start the pager and set up file descriptors.
1223 """Actually start the pager and set up file descriptors.
1224
1224
1225 This is separate in part so that extensions (like chg) can
1225 This is separate in part so that extensions (like chg) can
1226 override how a pager is invoked.
1226 override how a pager is invoked.
1227 """
1227 """
1228 if command == 'cat':
1228 if command == 'cat':
1229 # Save ourselves some work.
1229 # Save ourselves some work.
1230 return False
1230 return False
1231 # If the command doesn't contain any of these characters, we
1231 # If the command doesn't contain any of these characters, we
1232 # assume it's a binary and exec it directly. This means for
1232 # assume it's a binary and exec it directly. This means for
1233 # simple pager command configurations, we can degrade
1233 # simple pager command configurations, we can degrade
1234 # gracefully and tell the user about their broken pager.
1234 # gracefully and tell the user about their broken pager.
1235 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1235 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1236
1236
1237 if pycompat.iswindows and not shell:
1237 if pycompat.iswindows and not shell:
1238 # Window's built-in `more` cannot be invoked with shell=False, but
1238 # Window's built-in `more` cannot be invoked with shell=False, but
1239 # its `more.com` can. Hide this implementation detail from the
1239 # its `more.com` can. Hide this implementation detail from the
1240 # user so we can also get sane bad PAGER behavior. MSYS has
1240 # user so we can also get sane bad PAGER behavior. MSYS has
1241 # `more.exe`, so do a cmd.exe style resolution of the executable to
1241 # `more.exe`, so do a cmd.exe style resolution of the executable to
1242 # determine which one to use.
1242 # determine which one to use.
1243 fullcmd = procutil.findexe(command)
1243 fullcmd = procutil.findexe(command)
1244 if not fullcmd:
1244 if not fullcmd:
1245 self.warn(_("missing pager command '%s', skipping pager\n")
1245 self.warn(_("missing pager command '%s', skipping pager\n")
1246 % command)
1246 % command)
1247 return False
1247 return False
1248
1248
1249 command = fullcmd
1249 command = fullcmd
1250
1250
1251 try:
1251 try:
1252 pager = subprocess.Popen(
1252 pager = subprocess.Popen(
1253 procutil.tonativestr(command), shell=shell, bufsize=-1,
1253 procutil.tonativestr(command), shell=shell, bufsize=-1,
1254 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1254 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1255 stdout=procutil.stdout, stderr=procutil.stderr,
1255 stdout=procutil.stdout, stderr=procutil.stderr,
1256 env=procutil.tonativeenv(procutil.shellenviron(env)))
1256 env=procutil.tonativeenv(procutil.shellenviron(env)))
1257 except OSError as e:
1257 except OSError as e:
1258 if e.errno == errno.ENOENT and not shell:
1258 if e.errno == errno.ENOENT and not shell:
1259 self.warn(_("missing pager command '%s', skipping pager\n")
1259 self.warn(_("missing pager command '%s', skipping pager\n")
1260 % command)
1260 % command)
1261 return False
1261 return False
1262 raise
1262 raise
1263
1263
1264 # back up original file descriptors
1264 # back up original file descriptors
1265 stdoutfd = os.dup(procutil.stdout.fileno())
1265 stdoutfd = os.dup(procutil.stdout.fileno())
1266 stderrfd = os.dup(procutil.stderr.fileno())
1266 stderrfd = os.dup(procutil.stderr.fileno())
1267
1267
1268 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1268 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1269 if self._isatty(procutil.stderr):
1269 if self._isatty(procutil.stderr):
1270 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1270 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1271
1271
1272 @self.atexit
1272 @self.atexit
1273 def killpager():
1273 def killpager():
1274 if util.safehasattr(signal, "SIGINT"):
1274 if util.safehasattr(signal, "SIGINT"):
1275 signal.signal(signal.SIGINT, signal.SIG_IGN)
1275 signal.signal(signal.SIGINT, signal.SIG_IGN)
1276 # restore original fds, closing pager.stdin copies in the process
1276 # restore original fds, closing pager.stdin copies in the process
1277 os.dup2(stdoutfd, procutil.stdout.fileno())
1277 os.dup2(stdoutfd, procutil.stdout.fileno())
1278 os.dup2(stderrfd, procutil.stderr.fileno())
1278 os.dup2(stderrfd, procutil.stderr.fileno())
1279 pager.stdin.close()
1279 pager.stdin.close()
1280 pager.wait()
1280 pager.wait()
1281
1281
1282 return True
1282 return True
1283
1283
1284 @property
1284 @property
1285 def _exithandlers(self):
1285 def _exithandlers(self):
1286 return _reqexithandlers
1286 return _reqexithandlers
1287
1287
1288 def atexit(self, func, *args, **kwargs):
1288 def atexit(self, func, *args, **kwargs):
1289 '''register a function to run after dispatching a request
1289 '''register a function to run after dispatching a request
1290
1290
1291 Handlers do not stay registered across request boundaries.'''
1291 Handlers do not stay registered across request boundaries.'''
1292 self._exithandlers.append((func, args, kwargs))
1292 self._exithandlers.append((func, args, kwargs))
1293 return func
1293 return func
1294
1294
1295 def interface(self, feature):
1295 def interface(self, feature):
1296 """what interface to use for interactive console features?
1296 """what interface to use for interactive console features?
1297
1297
1298 The interface is controlled by the value of `ui.interface` but also by
1298 The interface is controlled by the value of `ui.interface` but also by
1299 the value of feature-specific configuration. For example:
1299 the value of feature-specific configuration. For example:
1300
1300
1301 ui.interface.histedit = text
1301 ui.interface.histedit = text
1302 ui.interface.chunkselector = curses
1302 ui.interface.chunkselector = curses
1303
1303
1304 Here the features are "histedit" and "chunkselector".
1304 Here the features are "histedit" and "chunkselector".
1305
1305
1306 The configuration above means that the default interfaces for commands
1306 The configuration above means that the default interfaces for commands
1307 is curses, the interface for histedit is text and the interface for
1307 is curses, the interface for histedit is text and the interface for
1308 selecting chunk is crecord (the best curses interface available).
1308 selecting chunk is crecord (the best curses interface available).
1309
1309
1310 Consider the following example:
1310 Consider the following example:
1311 ui.interface = curses
1311 ui.interface = curses
1312 ui.interface.histedit = text
1312 ui.interface.histedit = text
1313
1313
1314 Then histedit will use the text interface and chunkselector will use
1314 Then histedit will use the text interface and chunkselector will use
1315 the default curses interface (crecord at the moment).
1315 the default curses interface (crecord at the moment).
1316 """
1316 """
1317 alldefaults = frozenset(["text", "curses"])
1317 alldefaults = frozenset(["text", "curses"])
1318
1318
1319 featureinterfaces = {
1319 featureinterfaces = {
1320 "chunkselector": [
1320 "chunkselector": [
1321 "text",
1321 "text",
1322 "curses",
1322 "curses",
1323 ],
1323 ],
1324 "histedit": [
1324 "histedit": [
1325 "text",
1325 "text",
1326 "curses",
1326 "curses",
1327 ],
1327 ],
1328 }
1328 }
1329
1329
1330 # Feature-specific interface
1330 # Feature-specific interface
1331 if feature not in featureinterfaces.keys():
1331 if feature not in featureinterfaces.keys():
1332 # Programming error, not user error
1332 # Programming error, not user error
1333 raise ValueError("Unknown feature requested %s" % feature)
1333 raise ValueError("Unknown feature requested %s" % feature)
1334
1334
1335 availableinterfaces = frozenset(featureinterfaces[feature])
1335 availableinterfaces = frozenset(featureinterfaces[feature])
1336 if alldefaults > availableinterfaces:
1336 if alldefaults > availableinterfaces:
1337 # Programming error, not user error. We need a use case to
1337 # Programming error, not user error. We need a use case to
1338 # define the right thing to do here.
1338 # define the right thing to do here.
1339 raise ValueError(
1339 raise ValueError(
1340 "Feature %s does not handle all default interfaces" %
1340 "Feature %s does not handle all default interfaces" %
1341 feature)
1341 feature)
1342
1342
1343 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1343 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1344 return "text"
1344 return "text"
1345
1345
1346 # Default interface for all the features
1346 # Default interface for all the features
1347 defaultinterface = "text"
1347 defaultinterface = "text"
1348 i = self.config("ui", "interface")
1348 i = self.config("ui", "interface")
1349 if i in alldefaults:
1349 if i in alldefaults:
1350 defaultinterface = i
1350 defaultinterface = i
1351
1351
1352 choseninterface = defaultinterface
1352 choseninterface = defaultinterface
1353 f = self.config("ui", "interface.%s" % feature)
1353 f = self.config("ui", "interface.%s" % feature)
1354 if f in availableinterfaces:
1354 if f in availableinterfaces:
1355 choseninterface = f
1355 choseninterface = f
1356
1356
1357 if i is not None and defaultinterface != i:
1357 if i is not None and defaultinterface != i:
1358 if f is not None:
1358 if f is not None:
1359 self.warn(_("invalid value for ui.interface: %s\n") %
1359 self.warn(_("invalid value for ui.interface: %s\n") %
1360 (i,))
1360 (i,))
1361 else:
1361 else:
1362 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1362 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1363 (i, choseninterface))
1363 (i, choseninterface))
1364 if f is not None and choseninterface != f:
1364 if f is not None and choseninterface != f:
1365 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1365 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1366 (feature, f, choseninterface))
1366 (feature, f, choseninterface))
1367
1367
1368 return choseninterface
1368 return choseninterface
1369
1369
1370 def interactive(self):
1370 def interactive(self):
1371 '''is interactive input allowed?
1371 '''is interactive input allowed?
1372
1372
1373 An interactive session is a session where input can be reasonably read
1373 An interactive session is a session where input can be reasonably read
1374 from `sys.stdin'. If this function returns false, any attempt to read
1374 from `sys.stdin'. If this function returns false, any attempt to read
1375 from stdin should fail with an error, unless a sensible default has been
1375 from stdin should fail with an error, unless a sensible default has been
1376 specified.
1376 specified.
1377
1377
1378 Interactiveness is triggered by the value of the `ui.interactive'
1378 Interactiveness is triggered by the value of the `ui.interactive'
1379 configuration variable or - if it is unset - when `sys.stdin' points
1379 configuration variable or - if it is unset - when `sys.stdin' points
1380 to a terminal device.
1380 to a terminal device.
1381
1381
1382 This function refers to input only; for output, see `ui.formatted()'.
1382 This function refers to input only; for output, see `ui.formatted()'.
1383 '''
1383 '''
1384 i = self.configbool("ui", "interactive")
1384 i = self.configbool("ui", "interactive")
1385 if i is None:
1385 if i is None:
1386 # some environments replace stdin without implementing isatty
1386 # some environments replace stdin without implementing isatty
1387 # usually those are non-interactive
1387 # usually those are non-interactive
1388 return self._isatty(self._fin)
1388 return self._isatty(self._fin)
1389
1389
1390 return i
1390 return i
1391
1391
1392 def termwidth(self):
1392 def termwidth(self):
1393 '''how wide is the terminal in columns?
1393 '''how wide is the terminal in columns?
1394 '''
1394 '''
1395 if 'COLUMNS' in encoding.environ:
1395 if 'COLUMNS' in encoding.environ:
1396 try:
1396 try:
1397 return int(encoding.environ['COLUMNS'])
1397 return int(encoding.environ['COLUMNS'])
1398 except ValueError:
1398 except ValueError:
1399 pass
1399 pass
1400 return scmutil.termsize(self)[0]
1400 return scmutil.termsize(self)[0]
1401
1401
1402 def formatted(self):
1402 def formatted(self):
1403 '''should formatted output be used?
1403 '''should formatted output be used?
1404
1404
1405 It is often desirable to format the output to suite the output medium.
1405 It is often desirable to format the output to suite the output medium.
1406 Examples of this are truncating long lines or colorizing messages.
1406 Examples of this are truncating long lines or colorizing messages.
1407 However, this is not often not desirable when piping output into other
1407 However, this is not often not desirable when piping output into other
1408 utilities, e.g. `grep'.
1408 utilities, e.g. `grep'.
1409
1409
1410 Formatted output is triggered by the value of the `ui.formatted'
1410 Formatted output is triggered by the value of the `ui.formatted'
1411 configuration variable or - if it is unset - when `sys.stdout' points
1411 configuration variable or - if it is unset - when `sys.stdout' points
1412 to a terminal device. Please note that `ui.formatted' should be
1412 to a terminal device. Please note that `ui.formatted' should be
1413 considered an implementation detail; it is not intended for use outside
1413 considered an implementation detail; it is not intended for use outside
1414 Mercurial or its extensions.
1414 Mercurial or its extensions.
1415
1415
1416 This function refers to output only; for input, see `ui.interactive()'.
1416 This function refers to output only; for input, see `ui.interactive()'.
1417 This function always returns false when in plain mode, see `ui.plain()'.
1417 This function always returns false when in plain mode, see `ui.plain()'.
1418 '''
1418 '''
1419 if self.plain():
1419 if self.plain():
1420 return False
1420 return False
1421
1421
1422 i = self.configbool("ui", "formatted")
1422 i = self.configbool("ui", "formatted")
1423 if i is None:
1423 if i is None:
1424 # some environments replace stdout without implementing isatty
1424 # some environments replace stdout without implementing isatty
1425 # usually those are non-interactive
1425 # usually those are non-interactive
1426 return self._isatty(self._fout)
1426 return self._isatty(self._fout)
1427
1427
1428 return i
1428 return i
1429
1429
1430 def _readline(self):
1430 def _readline(self):
1431 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1431 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1432 # because they have to be text streams with *no buffering*. Instead,
1432 # because they have to be text streams with *no buffering*. Instead,
1433 # we use rawinput() only if call_readline() will be invoked by
1433 # we use rawinput() only if call_readline() will be invoked by
1434 # PyOS_Readline(), so no I/O will be made at Python layer.
1434 # PyOS_Readline(), so no I/O will be made at Python layer.
1435 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1435 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1436 and procutil.isstdin(self._fin)
1436 and procutil.isstdin(self._fin)
1437 and procutil.isstdout(self._fout))
1437 and procutil.isstdout(self._fout))
1438 if usereadline:
1438 if usereadline:
1439 try:
1439 try:
1440 # magically add command line editing support, where
1440 # magically add command line editing support, where
1441 # available
1441 # available
1442 import readline
1442 import readline
1443 # force demandimport to really load the module
1443 # force demandimport to really load the module
1444 readline.read_history_file
1444 readline.read_history_file
1445 # windows sometimes raises something other than ImportError
1445 # windows sometimes raises something other than ImportError
1446 except Exception:
1446 except Exception:
1447 usereadline = False
1447 usereadline = False
1448
1448
1449 # prompt ' ' must exist; otherwise readline may delete entire line
1449 # prompt ' ' must exist; otherwise readline may delete entire line
1450 # - http://bugs.python.org/issue12833
1450 # - http://bugs.python.org/issue12833
1451 with self.timeblockedsection('stdio'):
1451 with self.timeblockedsection('stdio'):
1452 if usereadline:
1452 if usereadline:
1453 line = encoding.strtolocal(pycompat.rawinput(r' '))
1453 line = encoding.strtolocal(pycompat.rawinput(r' '))
1454 # When stdin is in binary mode on Windows, it can cause
1454 # When stdin is in binary mode on Windows, it can cause
1455 # raw_input() to emit an extra trailing carriage return
1455 # raw_input() to emit an extra trailing carriage return
1456 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1456 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1457 line = line[:-1]
1457 line = line[:-1]
1458 else:
1458 else:
1459 self._fout.write(b' ')
1459 self._fout.write(b' ')
1460 self._fout.flush()
1460 self._fout.flush()
1461 line = self._fin.readline()
1461 line = self._fin.readline()
1462 if not line:
1462 if not line:
1463 raise EOFError
1463 raise EOFError
1464 line = line.rstrip(pycompat.oslinesep)
1464 line = line.rstrip(pycompat.oslinesep)
1465
1465
1466 return line
1466 return line
1467
1467
1468 def prompt(self, msg, default="y"):
1468 def prompt(self, msg, default="y"):
1469 """Prompt user with msg, read response.
1469 """Prompt user with msg, read response.
1470 If ui is not interactive, the default is returned.
1470 If ui is not interactive, the default is returned.
1471 """
1471 """
1472 return self._prompt(msg, default=default)
1472 return self._prompt(msg, default=default)
1473
1473
1474 def _prompt(self, msg, **opts):
1474 def _prompt(self, msg, **opts):
1475 default = opts[r'default']
1475 default = opts[r'default']
1476 if not self.interactive():
1476 if not self.interactive():
1477 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1477 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1478 self._writemsg(self._fmsgout, default or '', "\n",
1478 self._writemsg(self._fmsgout, default or '', "\n",
1479 type='promptecho')
1479 type='promptecho')
1480 return default
1480 return default
1481 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1481 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1482 self.flush()
1482 self.flush()
1483 try:
1483 try:
1484 r = self._readline()
1484 r = self._readline()
1485 if not r:
1485 if not r:
1486 r = default
1486 r = default
1487 if self.configbool('ui', 'promptecho'):
1487 if self.configbool('ui', 'promptecho'):
1488 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1488 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1489 return r
1489 return r
1490 except EOFError:
1490 except EOFError:
1491 raise error.ResponseExpected()
1491 raise error.ResponseExpected()
1492
1492
1493 @staticmethod
1493 @staticmethod
1494 def extractchoices(prompt):
1494 def extractchoices(prompt):
1495 """Extract prompt message and list of choices from specified prompt.
1495 """Extract prompt message and list of choices from specified prompt.
1496
1496
1497 This returns tuple "(message, choices)", and "choices" is the
1497 This returns tuple "(message, choices)", and "choices" is the
1498 list of tuple "(response character, text without &)".
1498 list of tuple "(response character, text without &)".
1499
1499
1500 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1500 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1501 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1501 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1502 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1502 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1503 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1503 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1504 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1504 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1505 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1505 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1506 """
1506 """
1507
1507
1508 # Sadly, the prompt string may have been built with a filename
1508 # Sadly, the prompt string may have been built with a filename
1509 # containing "$$" so let's try to find the first valid-looking
1509 # containing "$$" so let's try to find the first valid-looking
1510 # prompt to start parsing. Sadly, we also can't rely on
1510 # prompt to start parsing. Sadly, we also can't rely on
1511 # choices containing spaces, ASCII, or basically anything
1511 # choices containing spaces, ASCII, or basically anything
1512 # except an ampersand followed by a character.
1512 # except an ampersand followed by a character.
1513 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1513 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1514 msg = m.group(1)
1514 msg = m.group(1)
1515 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1515 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1516 def choicetuple(s):
1516 def choicetuple(s):
1517 ampidx = s.index('&')
1517 ampidx = s.index('&')
1518 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1518 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1519 return (msg, [choicetuple(s) for s in choices])
1519 return (msg, [choicetuple(s) for s in choices])
1520
1520
1521 def promptchoice(self, prompt, default=0):
1521 def promptchoice(self, prompt, default=0):
1522 """Prompt user with a message, read response, and ensure it matches
1522 """Prompt user with a message, read response, and ensure it matches
1523 one of the provided choices. The prompt is formatted as follows:
1523 one of the provided choices. The prompt is formatted as follows:
1524
1524
1525 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1525 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1526
1526
1527 The index of the choice is returned. Responses are case
1527 The index of the choice is returned. Responses are case
1528 insensitive. If ui is not interactive, the default is
1528 insensitive. If ui is not interactive, the default is
1529 returned.
1529 returned.
1530 """
1530 """
1531
1531
1532 msg, choices = self.extractchoices(prompt)
1532 msg, choices = self.extractchoices(prompt)
1533 resps = [r for r, t in choices]
1533 resps = [r for r, t in choices]
1534 while True:
1534 while True:
1535 r = self._prompt(msg, default=resps[default], choices=choices)
1535 r = self._prompt(msg, default=resps[default], choices=choices)
1536 if r.lower() in resps:
1536 if r.lower() in resps:
1537 return resps.index(r.lower())
1537 return resps.index(r.lower())
1538 # TODO: shouldn't it be a warning?
1538 # TODO: shouldn't it be a warning?
1539 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1539 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1540
1540
1541 def getpass(self, prompt=None, default=None):
1541 def getpass(self, prompt=None, default=None):
1542 if not self.interactive():
1542 if not self.interactive():
1543 return default
1543 return default
1544 try:
1544 try:
1545 self._writemsg(self._fmsgerr, prompt or _('password: '),
1545 self._writemsg(self._fmsgerr, prompt or _('password: '),
1546 type='prompt', password=True)
1546 type='prompt', password=True)
1547 # disable getpass() only if explicitly specified. it's still valid
1547 # disable getpass() only if explicitly specified. it's still valid
1548 # to interact with tty even if fin is not a tty.
1548 # to interact with tty even if fin is not a tty.
1549 with self.timeblockedsection('stdio'):
1549 with self.timeblockedsection('stdio'):
1550 if self.configbool('ui', 'nontty'):
1550 if self.configbool('ui', 'nontty'):
1551 l = self._fin.readline()
1551 l = self._fin.readline()
1552 if not l:
1552 if not l:
1553 raise EOFError
1553 raise EOFError
1554 return l.rstrip('\n')
1554 return l.rstrip('\n')
1555 else:
1555 else:
1556 return getpass.getpass('')
1556 return getpass.getpass('')
1557 except EOFError:
1557 except EOFError:
1558 raise error.ResponseExpected()
1558 raise error.ResponseExpected()
1559
1559
1560 def status(self, *msg, **opts):
1560 def status(self, *msg, **opts):
1561 '''write status message to output (if ui.quiet is False)
1561 '''write status message to output (if ui.quiet is False)
1562
1562
1563 This adds an output label of "ui.status".
1563 This adds an output label of "ui.status".
1564 '''
1564 '''
1565 if not self.quiet:
1565 if not self.quiet:
1566 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1566 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1567
1567
1568 def warn(self, *msg, **opts):
1568 def warn(self, *msg, **opts):
1569 '''write warning message to output (stderr)
1569 '''write warning message to output (stderr)
1570
1570
1571 This adds an output label of "ui.warning".
1571 This adds an output label of "ui.warning".
1572 '''
1572 '''
1573 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1573 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1574
1574
1575 def error(self, *msg, **opts):
1575 def error(self, *msg, **opts):
1576 '''write error message to output (stderr)
1576 '''write error message to output (stderr)
1577
1577
1578 This adds an output label of "ui.error".
1578 This adds an output label of "ui.error".
1579 '''
1579 '''
1580 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1580 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1581
1581
1582 def note(self, *msg, **opts):
1582 def note(self, *msg, **opts):
1583 '''write note to output (if ui.verbose is True)
1583 '''write note to output (if ui.verbose is True)
1584
1584
1585 This adds an output label of "ui.note".
1585 This adds an output label of "ui.note".
1586 '''
1586 '''
1587 if self.verbose:
1587 if self.verbose:
1588 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1588 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1589
1589
1590 def debug(self, *msg, **opts):
1590 def debug(self, *msg, **opts):
1591 '''write debug message to output (if ui.debugflag is True)
1591 '''write debug message to output (if ui.debugflag is True)
1592
1592
1593 This adds an output label of "ui.debug".
1593 This adds an output label of "ui.debug".
1594 '''
1594 '''
1595 if self.debugflag:
1595 if self.debugflag:
1596 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1596 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1597 self.log(b'debug', b'%s', b''.join(msg))
1597 self.log(b'debug', b'%s', b''.join(msg))
1598
1598
1599 def edit(self, text, user, extra=None, editform=None, pending=None,
1599 def edit(self, text, user, extra=None, editform=None, pending=None,
1600 repopath=None, action=None):
1600 repopath=None, action=None):
1601 if action is None:
1601 if action is None:
1602 self.develwarn('action is None but will soon be a required '
1602 self.develwarn('action is None but will soon be a required '
1603 'parameter to ui.edit()')
1603 'parameter to ui.edit()')
1604 extra_defaults = {
1604 extra_defaults = {
1605 'prefix': 'editor',
1605 'prefix': 'editor',
1606 'suffix': '.txt',
1606 'suffix': '.txt',
1607 }
1607 }
1608 if extra is not None:
1608 if extra is not None:
1609 if extra.get('suffix') is not None:
1609 if extra.get('suffix') is not None:
1610 self.develwarn('extra.suffix is not None but will soon be '
1610 self.develwarn('extra.suffix is not None but will soon be '
1611 'ignored by ui.edit()')
1611 'ignored by ui.edit()')
1612 extra_defaults.update(extra)
1612 extra_defaults.update(extra)
1613 extra = extra_defaults
1613 extra = extra_defaults
1614
1614
1615 if action == 'diff':
1615 if action == 'diff':
1616 suffix = '.diff'
1616 suffix = '.diff'
1617 elif action:
1617 elif action:
1618 suffix = '.%s.hg.txt' % action
1618 suffix = '.%s.hg.txt' % action
1619 else:
1619 else:
1620 suffix = extra['suffix']
1620 suffix = extra['suffix']
1621
1621
1622 rdir = None
1622 rdir = None
1623 if self.configbool('experimental', 'editortmpinhg'):
1623 if self.configbool('experimental', 'editortmpinhg'):
1624 rdir = repopath
1624 rdir = repopath
1625 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1625 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1626 suffix=suffix,
1626 suffix=suffix,
1627 dir=rdir)
1627 dir=rdir)
1628 try:
1628 try:
1629 f = os.fdopen(fd, r'wb')
1629 f = os.fdopen(fd, r'wb')
1630 f.write(util.tonativeeol(text))
1630 f.write(util.tonativeeol(text))
1631 f.close()
1631 f.close()
1632
1632
1633 environ = {'HGUSER': user}
1633 environ = {'HGUSER': user}
1634 if 'transplant_source' in extra:
1634 if 'transplant_source' in extra:
1635 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1635 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1636 for label in ('intermediate-source', 'source', 'rebase_source'):
1636 for label in ('intermediate-source', 'source', 'rebase_source'):
1637 if label in extra:
1637 if label in extra:
1638 environ.update({'HGREVISION': extra[label]})
1638 environ.update({'HGREVISION': extra[label]})
1639 break
1639 break
1640 if editform:
1640 if editform:
1641 environ.update({'HGEDITFORM': editform})
1641 environ.update({'HGEDITFORM': editform})
1642 if pending:
1642 if pending:
1643 environ.update({'HG_PENDING': pending})
1643 environ.update({'HG_PENDING': pending})
1644
1644
1645 editor = self.geteditor()
1645 editor = self.geteditor()
1646
1646
1647 self.system("%s \"%s\"" % (editor, name),
1647 self.system("%s \"%s\"" % (editor, name),
1648 environ=environ,
1648 environ=environ,
1649 onerr=error.Abort, errprefix=_("edit failed"),
1649 onerr=error.Abort, errprefix=_("edit failed"),
1650 blockedtag='editor')
1650 blockedtag='editor')
1651
1651
1652 f = open(name, r'rb')
1652 f = open(name, r'rb')
1653 t = util.fromnativeeol(f.read())
1653 t = util.fromnativeeol(f.read())
1654 f.close()
1654 f.close()
1655 finally:
1655 finally:
1656 os.unlink(name)
1656 os.unlink(name)
1657
1657
1658 return t
1658 return t
1659
1659
1660 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1660 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1661 blockedtag=None):
1661 blockedtag=None):
1662 '''execute shell command with appropriate output stream. command
1662 '''execute shell command with appropriate output stream. command
1663 output will be redirected if fout is not stdout.
1663 output will be redirected if fout is not stdout.
1664
1664
1665 if command fails and onerr is None, return status, else raise onerr
1665 if command fails and onerr is None, return status, else raise onerr
1666 object as exception.
1666 object as exception.
1667 '''
1667 '''
1668 if blockedtag is None:
1668 if blockedtag is None:
1669 # Long cmds tend to be because of an absolute path on cmd. Keep
1669 # Long cmds tend to be because of an absolute path on cmd. Keep
1670 # the tail end instead
1670 # the tail end instead
1671 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1671 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1672 blockedtag = 'unknown_system_' + cmdsuffix
1672 blockedtag = 'unknown_system_' + cmdsuffix
1673 out = self._fout
1673 out = self._fout
1674 if any(s[1] for s in self._bufferstates):
1674 if any(s[1] for s in self._bufferstates):
1675 out = self
1675 out = self
1676 with self.timeblockedsection(blockedtag):
1676 with self.timeblockedsection(blockedtag):
1677 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1677 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1678 if rc and onerr:
1678 if rc and onerr:
1679 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1679 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1680 procutil.explainexit(rc))
1680 procutil.explainexit(rc))
1681 if errprefix:
1681 if errprefix:
1682 errmsg = '%s: %s' % (errprefix, errmsg)
1682 errmsg = '%s: %s' % (errprefix, errmsg)
1683 raise onerr(errmsg)
1683 raise onerr(errmsg)
1684 return rc
1684 return rc
1685
1685
1686 def _runsystem(self, cmd, environ, cwd, out):
1686 def _runsystem(self, cmd, environ, cwd, out):
1687 """actually execute the given shell command (can be overridden by
1687 """actually execute the given shell command (can be overridden by
1688 extensions like chg)"""
1688 extensions like chg)"""
1689 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1689 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1690
1690
1691 def traceback(self, exc=None, force=False):
1691 def traceback(self, exc=None, force=False):
1692 '''print exception traceback if traceback printing enabled or forced.
1692 '''print exception traceback if traceback printing enabled or forced.
1693 only to call in exception handler. returns true if traceback
1693 only to call in exception handler. returns true if traceback
1694 printed.'''
1694 printed.'''
1695 if self.tracebackflag or force:
1695 if self.tracebackflag or force:
1696 if exc is None:
1696 if exc is None:
1697 exc = sys.exc_info()
1697 exc = sys.exc_info()
1698 cause = getattr(exc[1], 'cause', None)
1698 cause = getattr(exc[1], 'cause', None)
1699
1699
1700 if cause is not None:
1700 if cause is not None:
1701 causetb = traceback.format_tb(cause[2])
1701 causetb = traceback.format_tb(cause[2])
1702 exctb = traceback.format_tb(exc[2])
1702 exctb = traceback.format_tb(exc[2])
1703 exconly = traceback.format_exception_only(cause[0], cause[1])
1703 exconly = traceback.format_exception_only(cause[0], cause[1])
1704
1704
1705 # exclude frame where 'exc' was chained and rethrown from exctb
1705 # exclude frame where 'exc' was chained and rethrown from exctb
1706 self.write_err('Traceback (most recent call last):\n',
1706 self.write_err('Traceback (most recent call last):\n',
1707 ''.join(exctb[:-1]),
1707 ''.join(exctb[:-1]),
1708 ''.join(causetb),
1708 ''.join(causetb),
1709 ''.join(exconly))
1709 ''.join(exconly))
1710 else:
1710 else:
1711 output = traceback.format_exception(exc[0], exc[1], exc[2])
1711 output = traceback.format_exception(exc[0], exc[1], exc[2])
1712 self.write_err(encoding.strtolocal(r''.join(output)))
1712 self.write_err(encoding.strtolocal(r''.join(output)))
1713 return self.tracebackflag or force
1713 return self.tracebackflag or force
1714
1714
1715 def geteditor(self):
1715 def geteditor(self):
1716 '''return editor to use'''
1716 '''return editor to use'''
1717 if pycompat.sysplatform == 'plan9':
1717 if pycompat.sysplatform == 'plan9':
1718 # vi is the MIPS instruction simulator on Plan 9. We
1718 # vi is the MIPS instruction simulator on Plan 9. We
1719 # instead default to E to plumb commit messages to
1719 # instead default to E to plumb commit messages to
1720 # avoid confusion.
1720 # avoid confusion.
1721 editor = 'E'
1721 editor = 'E'
1722 else:
1722 else:
1723 editor = 'vi'
1723 editor = 'vi'
1724 return (encoding.environ.get("HGEDITOR") or
1724 return (encoding.environ.get("HGEDITOR") or
1725 self.config("ui", "editor", editor))
1725 self.config("ui", "editor", editor))
1726
1726
1727 @util.propertycache
1727 @util.propertycache
1728 def _progbar(self):
1728 def _progbar(self):
1729 """setup the progbar singleton to the ui object"""
1729 """setup the progbar singleton to the ui object"""
1730 if (self.quiet or self.debugflag
1730 if (self.quiet or self.debugflag
1731 or self.configbool('progress', 'disable')
1731 or self.configbool('progress', 'disable')
1732 or not progress.shouldprint(self)):
1732 or not progress.shouldprint(self)):
1733 return None
1733 return None
1734 return getprogbar(self)
1734 return getprogbar(self)
1735
1735
1736 def _progclear(self):
1736 def _progclear(self):
1737 """clear progress bar output if any. use it before any output"""
1737 """clear progress bar output if any. use it before any output"""
1738 if not haveprogbar(): # nothing loaded yet
1738 if not haveprogbar(): # nothing loaded yet
1739 return
1739 return
1740 if self._progbar is not None and self._progbar.printed:
1740 if self._progbar is not None and self._progbar.printed:
1741 self._progbar.clear()
1741 self._progbar.clear()
1742
1742
1743 def progress(self, topic, pos, item="", unit="", total=None):
1743 def progress(self, topic, pos, item="", unit="", total=None):
1744 '''show a progress message
1744 '''show a progress message
1745
1745
1746 By default a textual progress bar will be displayed if an operation
1746 By default a textual progress bar will be displayed if an operation
1747 takes too long. 'topic' is the current operation, 'item' is a
1747 takes too long. 'topic' is the current operation, 'item' is a
1748 non-numeric marker of the current position (i.e. the currently
1748 non-numeric marker of the current position (i.e. the currently
1749 in-process file), 'pos' is the current numeric position (i.e.
1749 in-process file), 'pos' is the current numeric position (i.e.
1750 revision, bytes, etc.), unit is a corresponding unit label,
1750 revision, bytes, etc.), unit is a corresponding unit label,
1751 and total is the highest expected pos.
1751 and total is the highest expected pos.
1752
1752
1753 Multiple nested topics may be active at a time.
1753 Multiple nested topics may be active at a time.
1754
1754
1755 All topics should be marked closed by setting pos to None at
1755 All topics should be marked closed by setting pos to None at
1756 termination.
1756 termination.
1757 '''
1757 '''
1758 self.deprecwarn("use ui.makeprogress() instead of ui.progress()",
1758 self.deprecwarn("use ui.makeprogress() instead of ui.progress()",
1759 "5.1")
1759 "5.1")
1760 progress = self.makeprogress(topic, unit, total)
1760 progress = self.makeprogress(topic, unit, total)
1761 if pos is not None:
1761 if pos is not None:
1762 progress.update(pos, item=item)
1762 progress.update(pos, item=item)
1763 else:
1763 else:
1764 progress.complete()
1764 progress.complete()
1765
1765
1766 def makeprogress(self, topic, unit="", total=None):
1766 def makeprogress(self, topic, unit="", total=None):
1767 """Create a progress helper for the specified topic"""
1767 """Create a progress helper for the specified topic"""
1768 if getattr(self._fmsgerr, 'structured', False):
1768 if getattr(self._fmsgerr, 'structured', False):
1769 # channel for machine-readable output with metadata, just send
1769 # channel for machine-readable output with metadata, just send
1770 # raw information
1770 # raw information
1771 # TODO: consider porting some useful information (e.g. estimated
1771 # TODO: consider porting some useful information (e.g. estimated
1772 # time) from progbar. we might want to support update delay to
1772 # time) from progbar. we might want to support update delay to
1773 # reduce the cost of transferring progress messages.
1773 # reduce the cost of transferring progress messages.
1774 def updatebar(topic, pos, item, unit, total):
1774 def updatebar(topic, pos, item, unit, total):
1775 self._fmsgerr.write(None, type=b'progress', topic=topic,
1775 self._fmsgerr.write(None, type=b'progress', topic=topic,
1776 pos=pos, item=item, unit=unit, total=total)
1776 pos=pos, item=item, unit=unit, total=total)
1777 elif self._progbar is not None:
1777 elif self._progbar is not None:
1778 updatebar = self._progbar.progress
1778 updatebar = self._progbar.progress
1779 else:
1779 else:
1780 def updatebar(topic, pos, item, unit, total):
1780 def updatebar(topic, pos, item, unit, total):
1781 pass
1781 pass
1782 return scmutil.progress(self, updatebar, topic, unit, total)
1782 return scmutil.progress(self, updatebar, topic, unit, total)
1783
1783
1784 def getlogger(self, name):
1784 def getlogger(self, name):
1785 """Returns a logger of the given name; or None if not registered"""
1785 """Returns a logger of the given name; or None if not registered"""
1786 return self._loggers.get(name)
1786 return self._loggers.get(name)
1787
1787
1788 def setlogger(self, name, logger):
1788 def setlogger(self, name, logger):
1789 """Install logger which can be identified later by the given name
1789 """Install logger which can be identified later by the given name
1790
1790
1791 More than one loggers can be registered. Use extension or module
1791 More than one loggers can be registered. Use extension or module
1792 name to uniquely identify the logger instance.
1792 name to uniquely identify the logger instance.
1793 """
1793 """
1794 self._loggers[name] = logger
1794 self._loggers[name] = logger
1795
1795
1796 def log(self, event, msgfmt, *msgargs, **opts):
1796 def log(self, event, msgfmt, *msgargs, **opts):
1797 '''hook for logging facility extensions
1797 '''hook for logging facility extensions
1798
1798
1799 event should be a readily-identifiable subsystem, which will
1799 event should be a readily-identifiable subsystem, which will
1800 allow filtering.
1800 allow filtering.
1801
1801
1802 msgfmt should be a newline-terminated format string to log, and
1802 msgfmt should be a newline-terminated format string to log, and
1803 *msgargs are %-formatted into it.
1803 *msgargs are %-formatted into it.
1804
1804
1805 **opts currently has no defined meanings.
1805 **opts currently has no defined meanings.
1806 '''
1806 '''
1807 if not self._loggers:
1807 if not self._loggers:
1808 return
1808 return
1809 activeloggers = [l for l in self._loggers.itervalues()
1809 activeloggers = [l for l in self._loggers.itervalues()
1810 if l.tracked(event)]
1810 if l.tracked(event)]
1811 if not activeloggers:
1811 if not activeloggers:
1812 return
1812 return
1813 msg = msgfmt % msgargs
1813 msg = msgfmt % msgargs
1814 opts = pycompat.byteskwargs(opts)
1814 opts = pycompat.byteskwargs(opts)
1815 # guard against recursion from e.g. ui.debug()
1815 # guard against recursion from e.g. ui.debug()
1816 registeredloggers = self._loggers
1816 registeredloggers = self._loggers
1817 self._loggers = {}
1817 self._loggers = {}
1818 try:
1818 try:
1819 for logger in activeloggers:
1819 for logger in activeloggers:
1820 logger.log(self, event, msg, opts)
1820 logger.log(self, event, msg, opts)
1821 finally:
1821 finally:
1822 self._loggers = registeredloggers
1822 self._loggers = registeredloggers
1823
1823
1824 def label(self, msg, label):
1824 def label(self, msg, label):
1825 '''style msg based on supplied label
1825 '''style msg based on supplied label
1826
1826
1827 If some color mode is enabled, this will add the necessary control
1827 If some color mode is enabled, this will add the necessary control
1828 characters to apply such color. In addition, 'debug' color mode adds
1828 characters to apply such color. In addition, 'debug' color mode adds
1829 markup showing which label affects a piece of text.
1829 markup showing which label affects a piece of text.
1830
1830
1831 ui.write(s, 'label') is equivalent to
1831 ui.write(s, 'label') is equivalent to
1832 ui.write(ui.label(s, 'label')).
1832 ui.write(ui.label(s, 'label')).
1833 '''
1833 '''
1834 if self._colormode is not None:
1834 if self._colormode is not None:
1835 return color.colorlabel(self, msg, label)
1835 return color.colorlabel(self, msg, label)
1836 return msg
1836 return msg
1837
1837
1838 def develwarn(self, msg, stacklevel=1, config=None):
1838 def develwarn(self, msg, stacklevel=1, config=None):
1839 """issue a developer warning message
1839 """issue a developer warning message
1840
1840
1841 Use 'stacklevel' to report the offender some layers further up in the
1841 Use 'stacklevel' to report the offender some layers further up in the
1842 stack.
1842 stack.
1843 """
1843 """
1844 if not self.configbool('devel', 'all-warnings'):
1844 if not self.configbool('devel', 'all-warnings'):
1845 if config is None or not self.configbool('devel', config):
1845 if config is None or not self.configbool('devel', config):
1846 return
1846 return
1847 msg = 'devel-warn: ' + msg
1847 msg = 'devel-warn: ' + msg
1848 stacklevel += 1 # get in develwarn
1848 stacklevel += 1 # get in develwarn
1849 if self.tracebackflag:
1849 if self.tracebackflag:
1850 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1850 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1851 self.log('develwarn', '%s at:\n%s' %
1851 self.log('develwarn', '%s at:\n%s' %
1852 (msg, ''.join(util.getstackframes(stacklevel))))
1852 (msg, ''.join(util.getstackframes(stacklevel))))
1853 else:
1853 else:
1854 curframe = inspect.currentframe()
1854 curframe = inspect.currentframe()
1855 calframe = inspect.getouterframes(curframe, 2)
1855 calframe = inspect.getouterframes(curframe, 2)
1856 fname, lineno, fmsg = calframe[stacklevel][1:4]
1856 fname, lineno, fmsg = calframe[stacklevel][1:4]
1857 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1857 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1858 self.write_err('%s at: %s:%d (%s)\n'
1858 self.write_err('%s at: %s:%d (%s)\n'
1859 % (msg, fname, lineno, fmsg))
1859 % (msg, fname, lineno, fmsg))
1860 self.log('develwarn', '%s at: %s:%d (%s)\n',
1860 self.log('develwarn', '%s at: %s:%d (%s)\n',
1861 msg, fname, lineno, fmsg)
1861 msg, fname, lineno, fmsg)
1862 curframe = calframe = None # avoid cycles
1862 curframe = calframe = None # avoid cycles
1863
1863
1864 def deprecwarn(self, msg, version, stacklevel=2):
1864 def deprecwarn(self, msg, version, stacklevel=2):
1865 """issue a deprecation warning
1865 """issue a deprecation warning
1866
1866
1867 - msg: message explaining what is deprecated and how to upgrade,
1867 - msg: message explaining what is deprecated and how to upgrade,
1868 - version: last version where the API will be supported,
1868 - version: last version where the API will be supported,
1869 """
1869 """
1870 if not (self.configbool('devel', 'all-warnings')
1870 if not (self.configbool('devel', 'all-warnings')
1871 or self.configbool('devel', 'deprec-warn')):
1871 or self.configbool('devel', 'deprec-warn')):
1872 return
1872 return
1873 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1873 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1874 " update your code.)") % version
1874 " update your code.)") % version
1875 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1875 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1876
1876
1877 def exportableenviron(self):
1877 def exportableenviron(self):
1878 """The environment variables that are safe to export, e.g. through
1878 """The environment variables that are safe to export, e.g. through
1879 hgweb.
1879 hgweb.
1880 """
1880 """
1881 return self._exportableenviron
1881 return self._exportableenviron
1882
1882
1883 @contextlib.contextmanager
1883 @contextlib.contextmanager
1884 def configoverride(self, overrides, source=""):
1884 def configoverride(self, overrides, source=""):
1885 """Context manager for temporary config overrides
1885 """Context manager for temporary config overrides
1886 `overrides` must be a dict of the following structure:
1886 `overrides` must be a dict of the following structure:
1887 {(section, name) : value}"""
1887 {(section, name) : value}"""
1888 backups = {}
1888 backups = {}
1889 try:
1889 try:
1890 for (section, name), value in overrides.items():
1890 for (section, name), value in overrides.items():
1891 backups[(section, name)] = self.backupconfig(section, name)
1891 backups[(section, name)] = self.backupconfig(section, name)
1892 self.setconfig(section, name, value, source)
1892 self.setconfig(section, name, value, source)
1893 yield
1893 yield
1894 finally:
1894 finally:
1895 for __, backup in backups.items():
1895 for __, backup in backups.items():
1896 self.restoreconfig(backup)
1896 self.restoreconfig(backup)
1897 # just restoring ui.quiet config to the previous value is not enough
1897 # just restoring ui.quiet config to the previous value is not enough
1898 # as it does not update ui.quiet class member
1898 # as it does not update ui.quiet class member
1899 if ('ui', 'quiet') in overrides:
1899 if ('ui', 'quiet') in overrides:
1900 self.fixconfig(section='ui')
1900 self.fixconfig(section='ui')
1901
1901
1902 class paths(dict):
1902 class paths(dict):
1903 """Represents a collection of paths and their configs.
1903 """Represents a collection of paths and their configs.
1904
1904
1905 Data is initially derived from ui instances and the config files they have
1905 Data is initially derived from ui instances and the config files they have
1906 loaded.
1906 loaded.
1907 """
1907 """
1908 def __init__(self, ui):
1908 def __init__(self, ui):
1909 dict.__init__(self)
1909 dict.__init__(self)
1910
1910
1911 for name, loc in ui.configitems('paths', ignoresub=True):
1911 for name, loc in ui.configitems('paths', ignoresub=True):
1912 # No location is the same as not existing.
1912 # No location is the same as not existing.
1913 if not loc:
1913 if not loc:
1914 continue
1914 continue
1915 loc, sub = ui.configsuboptions('paths', name)
1915 loc, sub = ui.configsuboptions('paths', name)
1916 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1916 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1917
1917
1918 def getpath(self, name, default=None):
1918 def getpath(self, name, default=None):
1919 """Return a ``path`` from a string, falling back to default.
1919 """Return a ``path`` from a string, falling back to default.
1920
1920
1921 ``name`` can be a named path or locations. Locations are filesystem
1921 ``name`` can be a named path or locations. Locations are filesystem
1922 paths or URIs.
1922 paths or URIs.
1923
1923
1924 Returns None if ``name`` is not a registered path, a URI, or a local
1924 Returns None if ``name`` is not a registered path, a URI, or a local
1925 path to a repo.
1925 path to a repo.
1926 """
1926 """
1927 # Only fall back to default if no path was requested.
1927 # Only fall back to default if no path was requested.
1928 if name is None:
1928 if name is None:
1929 if not default:
1929 if not default:
1930 default = ()
1930 default = ()
1931 elif not isinstance(default, (tuple, list)):
1931 elif not isinstance(default, (tuple, list)):
1932 default = (default,)
1932 default = (default,)
1933 for k in default:
1933 for k in default:
1934 try:
1934 try:
1935 return self[k]
1935 return self[k]
1936 except KeyError:
1936 except KeyError:
1937 continue
1937 continue
1938 return None
1938 return None
1939
1939
1940 # Most likely empty string.
1940 # Most likely empty string.
1941 # This may need to raise in the future.
1941 # This may need to raise in the future.
1942 if not name:
1942 if not name:
1943 return None
1943 return None
1944
1944
1945 try:
1945 try:
1946 return self[name]
1946 return self[name]
1947 except KeyError:
1947 except KeyError:
1948 # Try to resolve as a local path or URI.
1948 # Try to resolve as a local path or URI.
1949 try:
1949 try:
1950 # We don't pass sub-options in, so no need to pass ui instance.
1950 # We don't pass sub-options in, so no need to pass ui instance.
1951 return path(None, None, rawloc=name)
1951 return path(None, None, rawloc=name)
1952 except ValueError:
1952 except ValueError:
1953 raise error.RepoError(_('repository %s does not exist') %
1953 raise error.RepoError(_('repository %s does not exist') %
1954 name)
1954 name)
1955
1955
1956 _pathsuboptions = {}
1956 _pathsuboptions = {}
1957
1957
1958 def pathsuboption(option, attr):
1958 def pathsuboption(option, attr):
1959 """Decorator used to declare a path sub-option.
1959 """Decorator used to declare a path sub-option.
1960
1960
1961 Arguments are the sub-option name and the attribute it should set on
1961 Arguments are the sub-option name and the attribute it should set on
1962 ``path`` instances.
1962 ``path`` instances.
1963
1963
1964 The decorated function will receive as arguments a ``ui`` instance,
1964 The decorated function will receive as arguments a ``ui`` instance,
1965 ``path`` instance, and the string value of this option from the config.
1965 ``path`` instance, and the string value of this option from the config.
1966 The function should return the value that will be set on the ``path``
1966 The function should return the value that will be set on the ``path``
1967 instance.
1967 instance.
1968
1968
1969 This decorator can be used to perform additional verification of
1969 This decorator can be used to perform additional verification of
1970 sub-options and to change the type of sub-options.
1970 sub-options and to change the type of sub-options.
1971 """
1971 """
1972 def register(func):
1972 def register(func):
1973 _pathsuboptions[option] = (attr, func)
1973 _pathsuboptions[option] = (attr, func)
1974 return func
1974 return func
1975 return register
1975 return register
1976
1976
1977 @pathsuboption('pushurl', 'pushloc')
1977 @pathsuboption('pushurl', 'pushloc')
1978 def pushurlpathoption(ui, path, value):
1978 def pushurlpathoption(ui, path, value):
1979 u = util.url(value)
1979 u = util.url(value)
1980 # Actually require a URL.
1980 # Actually require a URL.
1981 if not u.scheme:
1981 if not u.scheme:
1982 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1982 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1983 return None
1983 return None
1984
1984
1985 # Don't support the #foo syntax in the push URL to declare branch to
1985 # Don't support the #foo syntax in the push URL to declare branch to
1986 # push.
1986 # push.
1987 if u.fragment:
1987 if u.fragment:
1988 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1988 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1989 'ignoring)\n') % path.name)
1989 'ignoring)\n') % path.name)
1990 u.fragment = None
1990 u.fragment = None
1991
1991
1992 return bytes(u)
1992 return bytes(u)
1993
1993
1994 @pathsuboption('pushrev', 'pushrev')
1994 @pathsuboption('pushrev', 'pushrev')
1995 def pushrevpathoption(ui, path, value):
1995 def pushrevpathoption(ui, path, value):
1996 return value
1996 return value
1997
1997
1998 class path(object):
1998 class path(object):
1999 """Represents an individual path and its configuration."""
1999 """Represents an individual path and its configuration."""
2000
2000
2001 def __init__(self, ui, name, rawloc=None, suboptions=None):
2001 def __init__(self, ui, name, rawloc=None, suboptions=None):
2002 """Construct a path from its config options.
2002 """Construct a path from its config options.
2003
2003
2004 ``ui`` is the ``ui`` instance the path is coming from.
2004 ``ui`` is the ``ui`` instance the path is coming from.
2005 ``name`` is the symbolic name of the path.
2005 ``name`` is the symbolic name of the path.
2006 ``rawloc`` is the raw location, as defined in the config.
2006 ``rawloc`` is the raw location, as defined in the config.
2007 ``pushloc`` is the raw locations pushes should be made to.
2007 ``pushloc`` is the raw locations pushes should be made to.
2008
2008
2009 If ``name`` is not defined, we require that the location be a) a local
2009 If ``name`` is not defined, we require that the location be a) a local
2010 filesystem path with a .hg directory or b) a URL. If not,
2010 filesystem path with a .hg directory or b) a URL. If not,
2011 ``ValueError`` is raised.
2011 ``ValueError`` is raised.
2012 """
2012 """
2013 if not rawloc:
2013 if not rawloc:
2014 raise ValueError('rawloc must be defined')
2014 raise ValueError('rawloc must be defined')
2015
2015
2016 # Locations may define branches via syntax <base>#<branch>.
2016 # Locations may define branches via syntax <base>#<branch>.
2017 u = util.url(rawloc)
2017 u = util.url(rawloc)
2018 branch = None
2018 branch = None
2019 if u.fragment:
2019 if u.fragment:
2020 branch = u.fragment
2020 branch = u.fragment
2021 u.fragment = None
2021 u.fragment = None
2022
2022
2023 self.url = u
2023 self.url = u
2024 self.branch = branch
2024 self.branch = branch
2025
2025
2026 self.name = name
2026 self.name = name
2027 self.rawloc = rawloc
2027 self.rawloc = rawloc
2028 self.loc = '%s' % u
2028 self.loc = '%s' % u
2029
2029
2030 # When given a raw location but not a symbolic name, validate the
2030 # When given a raw location but not a symbolic name, validate the
2031 # location is valid.
2031 # location is valid.
2032 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2032 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2033 raise ValueError('location is not a URL or path to a local '
2033 raise ValueError('location is not a URL or path to a local '
2034 'repo: %s' % rawloc)
2034 'repo: %s' % rawloc)
2035
2035
2036 suboptions = suboptions or {}
2036 suboptions = suboptions or {}
2037
2037
2038 # Now process the sub-options. If a sub-option is registered, its
2038 # Now process the sub-options. If a sub-option is registered, its
2039 # attribute will always be present. The value will be None if there
2039 # attribute will always be present. The value will be None if there
2040 # was no valid sub-option.
2040 # was no valid sub-option.
2041 for suboption, (attr, func) in _pathsuboptions.iteritems():
2041 for suboption, (attr, func) in _pathsuboptions.iteritems():
2042 if suboption not in suboptions:
2042 if suboption not in suboptions:
2043 setattr(self, attr, None)
2043 setattr(self, attr, None)
2044 continue
2044 continue
2045
2045
2046 value = func(ui, self, suboptions[suboption])
2046 value = func(ui, self, suboptions[suboption])
2047 setattr(self, attr, value)
2047 setattr(self, attr, value)
2048
2048
2049 def _isvalidlocalpath(self, path):
2049 def _isvalidlocalpath(self, path):
2050 """Returns True if the given path is a potentially valid repository.
2050 """Returns True if the given path is a potentially valid repository.
2051 This is its own function so that extensions can change the definition of
2051 This is its own function so that extensions can change the definition of
2052 'valid' in this case (like when pulling from a git repo into a hg
2052 'valid' in this case (like when pulling from a git repo into a hg
2053 one)."""
2053 one)."""
2054 try:
2054 return os.path.isdir(os.path.join(path, '.hg'))
2055 return os.path.isdir(os.path.join(path, '.hg'))
2056 # Python 2 may return TypeError. Python 3, ValueError.
2057 except (TypeError, ValueError):
2058 return False
2055
2059
2056 @property
2060 @property
2057 def suboptions(self):
2061 def suboptions(self):
2058 """Return sub-options and their values for this path.
2062 """Return sub-options and their values for this path.
2059
2063
2060 This is intended to be used for presentation purposes.
2064 This is intended to be used for presentation purposes.
2061 """
2065 """
2062 d = {}
2066 d = {}
2063 for subopt, (attr, _func) in _pathsuboptions.iteritems():
2067 for subopt, (attr, _func) in _pathsuboptions.iteritems():
2064 value = getattr(self, attr)
2068 value = getattr(self, attr)
2065 if value is not None:
2069 if value is not None:
2066 d[subopt] = value
2070 d[subopt] = value
2067 return d
2071 return d
2068
2072
2069 # we instantiate one globally shared progress bar to avoid
2073 # we instantiate one globally shared progress bar to avoid
2070 # competing progress bars when multiple UI objects get created
2074 # competing progress bars when multiple UI objects get created
2071 _progresssingleton = None
2075 _progresssingleton = None
2072
2076
2073 def getprogbar(ui):
2077 def getprogbar(ui):
2074 global _progresssingleton
2078 global _progresssingleton
2075 if _progresssingleton is None:
2079 if _progresssingleton is None:
2076 # passing 'ui' object to the singleton is fishy,
2080 # passing 'ui' object to the singleton is fishy,
2077 # this is how the extension used to work but feel free to rework it.
2081 # this is how the extension used to work but feel free to rework it.
2078 _progresssingleton = progress.progbar(ui)
2082 _progresssingleton = progress.progbar(ui)
2079 return _progresssingleton
2083 return _progresssingleton
2080
2084
2081 def haveprogbar():
2085 def haveprogbar():
2082 return _progresssingleton is not None
2086 return _progresssingleton is not None
2083
2087
2084 def _selectmsgdests(ui):
2088 def _selectmsgdests(ui):
2085 name = ui.config(b'ui', b'message-output')
2089 name = ui.config(b'ui', b'message-output')
2086 if name == b'channel':
2090 if name == b'channel':
2087 if ui.fmsg:
2091 if ui.fmsg:
2088 return ui.fmsg, ui.fmsg
2092 return ui.fmsg, ui.fmsg
2089 else:
2093 else:
2090 # fall back to ferr if channel isn't ready so that status/error
2094 # fall back to ferr if channel isn't ready so that status/error
2091 # messages can be printed
2095 # messages can be printed
2092 return ui.ferr, ui.ferr
2096 return ui.ferr, ui.ferr
2093 if name == b'stdio':
2097 if name == b'stdio':
2094 return ui.fout, ui.ferr
2098 return ui.fout, ui.ferr
2095 if name == b'stderr':
2099 if name == b'stderr':
2096 return ui.ferr, ui.ferr
2100 return ui.ferr, ui.ferr
2097 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2101 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2098
2102
2099 def _writemsgwith(write, dest, *args, **opts):
2103 def _writemsgwith(write, dest, *args, **opts):
2100 """Write ui message with the given ui._write*() function
2104 """Write ui message with the given ui._write*() function
2101
2105
2102 The specified message type is translated to 'ui.<type>' label if the dest
2106 The specified message type is translated to 'ui.<type>' label if the dest
2103 isn't a structured channel, so that the message will be colorized.
2107 isn't a structured channel, so that the message will be colorized.
2104 """
2108 """
2105 # TODO: maybe change 'type' to a mandatory option
2109 # TODO: maybe change 'type' to a mandatory option
2106 if r'type' in opts and not getattr(dest, 'structured', False):
2110 if r'type' in opts and not getattr(dest, 'structured', False):
2107 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2111 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2108 write(dest, *args, **opts)
2112 write(dest, *args, **opts)
@@ -1,112 +1,112
1 #require no-windows
1 #require no-windows
2
2
3 $ . "$TESTDIR/remotefilelog-library.sh"
3 $ . "$TESTDIR/remotefilelog-library.sh"
4
4
5 $ hg init master
5 $ hg init master
6 $ cd master
6 $ cd master
7 $ cat >> .hg/hgrc <<EOF
7 $ cat >> .hg/hgrc <<EOF
8 > [remotefilelog]
8 > [remotefilelog]
9 > server=True
9 > server=True
10 > serverexpiration=-1
10 > serverexpiration=-1
11 > EOF
11 > EOF
12 $ echo x > x
12 $ echo x > x
13 $ hg commit -qAm x
13 $ hg commit -qAm x
14 $ cd ..
14 $ cd ..
15
15
16 $ hgcloneshallow ssh://user@dummy/master shallow -q
16 $ hgcloneshallow ssh://user@dummy/master shallow -q
17 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob)
17 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob)
18
18
19 # Set the prefetchdays config to zero so that all commits are prefetched
19 # Set the prefetchdays config to zero so that all commits are prefetched
20 # no matter what their creation date is.
20 # no matter what their creation date is.
21 $ cd shallow
21 $ cd shallow
22 $ cat >> .hg/hgrc <<EOF
22 $ cat >> .hg/hgrc <<EOF
23 > [remotefilelog]
23 > [remotefilelog]
24 > prefetchdays=0
24 > prefetchdays=0
25 > EOF
25 > EOF
26 $ cd ..
26 $ cd ..
27
27
28 # commit a new version of x so we can gc the old one
28 # commit a new version of x so we can gc the old one
29
29
30 $ cd master
30 $ cd master
31 $ echo y > x
31 $ echo y > x
32 $ hg commit -qAm y
32 $ hg commit -qAm y
33 $ cd ..
33 $ cd ..
34
34
35 $ cd shallow
35 $ cd shallow
36 $ hg pull -q
36 $ hg pull -q
37 $ hg update -q
37 $ hg update -q
38 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob)
38 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob)
39 $ cd ..
39 $ cd ..
40
40
41 # gc client cache
41 # gc client cache
42
42
43 $ lastweek=`$PYTHON -c 'import datetime,time; print(datetime.datetime.fromtimestamp(time.time() - (86400 * 7)).strftime("%y%m%d%H%M"))'`
43 $ lastweek=`$PYTHON -c 'import datetime,time; print(datetime.datetime.fromtimestamp(time.time() - (86400 * 7)).strftime("%y%m%d%H%M"))'`
44 $ find $CACHEDIR -type f -exec touch -t $lastweek {} \;
44 $ find $CACHEDIR -type f -exec touch -t $lastweek {} \;
45
45
46 $ find $CACHEDIR -type f | sort
46 $ find $CACHEDIR -type f | sort
47 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0 (glob)
47 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0 (glob)
48 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
48 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
49 $TESTTMP/hgcache/repos (glob)
49 $TESTTMP/hgcache/repos (glob)
50 $ hg gc
50 $ hg gc
51 finished: removed 1 of 2 files (0.00 GB to 0.00 GB)
51 finished: removed 1 of 2 files (0.00 GB to 0.00 GB)
52 $ find $CACHEDIR -type f | sort
52 $ find $CACHEDIR -type f | sort
53 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
53 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
54 $TESTTMP/hgcache/repos
54 $TESTTMP/hgcache/repos
55
55
56 # gc server cache
56 # gc server cache
57
57
58 $ find master/.hg/remotefilelogcache -type f | sort
58 $ find master/.hg/remotefilelogcache -type f | sort
59 master/.hg/remotefilelogcache/x/1406e74118627694268417491f018a4a883152f0 (glob)
59 master/.hg/remotefilelogcache/x/1406e74118627694268417491f018a4a883152f0 (glob)
60 master/.hg/remotefilelogcache/x/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
60 master/.hg/remotefilelogcache/x/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
61 $ hg gc master
61 $ hg gc master
62 finished: removed 0 of 1 files (0.00 GB to 0.00 GB)
62 finished: removed 0 of 1 files (0.00 GB to 0.00 GB)
63 $ find master/.hg/remotefilelogcache -type f | sort
63 $ find master/.hg/remotefilelogcache -type f | sort
64 master/.hg/remotefilelogcache/x/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
64 master/.hg/remotefilelogcache/x/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
65
65
66 # Test that GC keepset includes pullprefetch revset if it is configured
66 # Test that GC keepset includes pullprefetch revset if it is configured
67
67
68 $ cd shallow
68 $ cd shallow
69 $ cat >> .hg/hgrc <<EOF
69 $ cat >> .hg/hgrc <<EOF
70 > [remotefilelog]
70 > [remotefilelog]
71 > pullprefetch=all()
71 > pullprefetch=all()
72 > EOF
72 > EOF
73 $ hg prefetch
73 $ hg prefetch
74 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob)
74 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob)
75
75
76 $ cd ..
76 $ cd ..
77 $ hg gc
77 $ hg gc
78 finished: removed 0 of 2 files (0.00 GB to 0.00 GB)
78 finished: removed 0 of 2 files (0.00 GB to 0.00 GB)
79
79
80 # Ensure that there are 2 versions of the file in cache
80 # Ensure that there are 2 versions of the file in cache
81 $ find $CACHEDIR -type f | sort
81 $ find $CACHEDIR -type f | sort
82 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0 (glob)
82 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0 (glob)
83 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
83 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
84 $TESTTMP/hgcache/repos (glob)
84 $TESTTMP/hgcache/repos (glob)
85
85
86 # Test that if garbage collection on repack and repack on hg gc flags are set then incremental repack with garbage collector is run
86 # Test that if garbage collection on repack and repack on hg gc flags are set then incremental repack with garbage collector is run
87
87
88 $ hg gc --config remotefilelog.gcrepack=True --config remotefilelog.repackonhggc=True
88 $ hg gc --config remotefilelog.gcrepack=True --config remotefilelog.repackonhggc=True
89
89
90 # Ensure that loose files are repacked
90 # Ensure that loose files are repacked
91 $ find $CACHEDIR -type f | sort
91 $ find $CACHEDIR -type f | sort
92 $TESTTMP/hgcache/master/packs/320dab99b7e3f60512b97f347689625263d22cf5.dataidx
92 $TESTTMP/hgcache/master/packs/320dab99b7e3f60512b97f347689625263d22cf5.dataidx
93 $TESTTMP/hgcache/master/packs/320dab99b7e3f60512b97f347689625263d22cf5.datapack
93 $TESTTMP/hgcache/master/packs/320dab99b7e3f60512b97f347689625263d22cf5.datapack
94 $TESTTMP/hgcache/master/packs/837b83c1ef6485a336eb4421ac5973c0ec130fbb.histidx
94 $TESTTMP/hgcache/master/packs/837b83c1ef6485a336eb4421ac5973c0ec130fbb.histidx
95 $TESTTMP/hgcache/master/packs/837b83c1ef6485a336eb4421ac5973c0ec130fbb.histpack
95 $TESTTMP/hgcache/master/packs/837b83c1ef6485a336eb4421ac5973c0ec130fbb.histpack
96 $TESTTMP/hgcache/master/packs/repacklock
96 $TESTTMP/hgcache/master/packs/repacklock
97 $TESTTMP/hgcache/repos
97 $TESTTMP/hgcache/repos
98
98
99 # Test that warning is displayed when there are no valid repos in repofile
99 # Test that warning is displayed when there are no valid repos in repofile
100
100
101 $ cp $CACHEDIR/repos $CACHEDIR/repos.bak
101 $ cp $CACHEDIR/repos $CACHEDIR/repos.bak
102 $ echo " " > $CACHEDIR/repos
102 $ echo " " > $CACHEDIR/repos
103 $ hg gc
103 $ hg gc
104 warning: no valid repos in repofile
104 warning: no valid repos in repofile
105 $ mv $CACHEDIR/repos.bak $CACHEDIR/repos
105 $ mv $CACHEDIR/repos.bak $CACHEDIR/repos
106
106
107 # Test that warning is displayed when the repo path is malformed
107 # Test that warning is displayed when the repo path is malformed
108
108
109 $ printf "asdas\0das" >> $CACHEDIR/repos
109 $ printf "asdas\0das" >> $CACHEDIR/repos
110 $ hg gc 2>&1 | head -n2
110 $ hg gc
111 warning: malformed path: * (glob)
111 abort: invalid path asdas\x00da: stat() argument 1 must be encoded string without null bytes, not str (esc)
112 Traceback (most recent call last):
112 [255]
General Comments 0
You need to be logged in to leave comments. Login now