##// END OF EJS Templates
templatekw: move getrenamedfn() to scmutil (API)...
Martin von Zweigbergk -
r41947:e9b9ee9a default
parent child Browse files
Show More
@@ -1,1142 +1,1141 b''
1 # __init__.py - remotefilelog extension
1 # __init__.py - remotefilelog extension
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """remotefilelog causes Mercurial to lazilly fetch file contents (EXPERIMENTAL)
7 """remotefilelog causes Mercurial to lazilly fetch file contents (EXPERIMENTAL)
8
8
9 This extension is HIGHLY EXPERIMENTAL. There are NO BACKWARDS COMPATIBILITY
9 This extension is HIGHLY EXPERIMENTAL. There are NO BACKWARDS COMPATIBILITY
10 GUARANTEES. This means that repositories created with this extension may
10 GUARANTEES. This means that repositories created with this extension may
11 only be usable with the exact version of this extension/Mercurial that was
11 only be usable with the exact version of this extension/Mercurial that was
12 used. The extension attempts to enforce this in order to prevent repository
12 used. The extension attempts to enforce this in order to prevent repository
13 corruption.
13 corruption.
14
14
15 remotefilelog works by fetching file contents lazily and storing them
15 remotefilelog works by fetching file contents lazily and storing them
16 in a cache on the client rather than in revlogs. This allows enormous
16 in a cache on the client rather than in revlogs. This allows enormous
17 histories to be transferred only partially, making them easier to
17 histories to be transferred only partially, making them easier to
18 operate on.
18 operate on.
19
19
20 Configs:
20 Configs:
21
21
22 ``packs.maxchainlen`` specifies the maximum delta chain length in pack files
22 ``packs.maxchainlen`` specifies the maximum delta chain length in pack files
23
23
24 ``packs.maxpacksize`` specifies the maximum pack file size
24 ``packs.maxpacksize`` specifies the maximum pack file size
25
25
26 ``packs.maxpackfilecount`` specifies the maximum number of packs in the
26 ``packs.maxpackfilecount`` specifies the maximum number of packs in the
27 shared cache (trees only for now)
27 shared cache (trees only for now)
28
28
29 ``remotefilelog.backgroundprefetch`` runs prefetch in background when True
29 ``remotefilelog.backgroundprefetch`` runs prefetch in background when True
30
30
31 ``remotefilelog.bgprefetchrevs`` specifies revisions to fetch on commit and
31 ``remotefilelog.bgprefetchrevs`` specifies revisions to fetch on commit and
32 update, and on other commands that use them. Different from pullprefetch.
32 update, and on other commands that use them. Different from pullprefetch.
33
33
34 ``remotefilelog.gcrepack`` does garbage collection during repack when True
34 ``remotefilelog.gcrepack`` does garbage collection during repack when True
35
35
36 ``remotefilelog.nodettl`` specifies maximum TTL of a node in seconds before
36 ``remotefilelog.nodettl`` specifies maximum TTL of a node in seconds before
37 it is garbage collected
37 it is garbage collected
38
38
39 ``remotefilelog.repackonhggc`` runs repack on hg gc when True
39 ``remotefilelog.repackonhggc`` runs repack on hg gc when True
40
40
41 ``remotefilelog.prefetchdays`` specifies the maximum age of a commit in
41 ``remotefilelog.prefetchdays`` specifies the maximum age of a commit in
42 days after which it is no longer prefetched.
42 days after which it is no longer prefetched.
43
43
44 ``remotefilelog.prefetchdelay`` specifies delay between background
44 ``remotefilelog.prefetchdelay`` specifies delay between background
45 prefetches in seconds after operations that change the working copy parent
45 prefetches in seconds after operations that change the working copy parent
46
46
47 ``remotefilelog.data.gencountlimit`` constraints the minimum number of data
47 ``remotefilelog.data.gencountlimit`` constraints the minimum number of data
48 pack files required to be considered part of a generation. In particular,
48 pack files required to be considered part of a generation. In particular,
49 minimum number of packs files > gencountlimit.
49 minimum number of packs files > gencountlimit.
50
50
51 ``remotefilelog.data.generations`` list for specifying the lower bound of
51 ``remotefilelog.data.generations`` list for specifying the lower bound of
52 each generation of the data pack files. For example, list ['100MB','1MB']
52 each generation of the data pack files. For example, list ['100MB','1MB']
53 or ['1MB', '100MB'] will lead to three generations: [0, 1MB), [
53 or ['1MB', '100MB'] will lead to three generations: [0, 1MB), [
54 1MB, 100MB) and [100MB, infinity).
54 1MB, 100MB) and [100MB, infinity).
55
55
56 ``remotefilelog.data.maxrepackpacks`` the maximum number of pack files to
56 ``remotefilelog.data.maxrepackpacks`` the maximum number of pack files to
57 include in an incremental data repack.
57 include in an incremental data repack.
58
58
59 ``remotefilelog.data.repackmaxpacksize`` the maximum size of a pack file for
59 ``remotefilelog.data.repackmaxpacksize`` the maximum size of a pack file for
60 it to be considered for an incremental data repack.
60 it to be considered for an incremental data repack.
61
61
62 ``remotefilelog.data.repacksizelimit`` the maximum total size of pack files
62 ``remotefilelog.data.repacksizelimit`` the maximum total size of pack files
63 to include in an incremental data repack.
63 to include in an incremental data repack.
64
64
65 ``remotefilelog.history.gencountlimit`` constraints the minimum number of
65 ``remotefilelog.history.gencountlimit`` constraints the minimum number of
66 history pack files required to be considered part of a generation. In
66 history pack files required to be considered part of a generation. In
67 particular, minimum number of packs files > gencountlimit.
67 particular, minimum number of packs files > gencountlimit.
68
68
69 ``remotefilelog.history.generations`` list for specifying the lower bound of
69 ``remotefilelog.history.generations`` list for specifying the lower bound of
70 each generation of the history pack files. For example, list [
70 each generation of the history pack files. For example, list [
71 '100MB', '1MB'] or ['1MB', '100MB'] will lead to three generations: [
71 '100MB', '1MB'] or ['1MB', '100MB'] will lead to three generations: [
72 0, 1MB), [1MB, 100MB) and [100MB, infinity).
72 0, 1MB), [1MB, 100MB) and [100MB, infinity).
73
73
74 ``remotefilelog.history.maxrepackpacks`` the maximum number of pack files to
74 ``remotefilelog.history.maxrepackpacks`` the maximum number of pack files to
75 include in an incremental history repack.
75 include in an incremental history repack.
76
76
77 ``remotefilelog.history.repackmaxpacksize`` the maximum size of a pack file
77 ``remotefilelog.history.repackmaxpacksize`` the maximum size of a pack file
78 for it to be considered for an incremental history repack.
78 for it to be considered for an incremental history repack.
79
79
80 ``remotefilelog.history.repacksizelimit`` the maximum total size of pack
80 ``remotefilelog.history.repacksizelimit`` the maximum total size of pack
81 files to include in an incremental history repack.
81 files to include in an incremental history repack.
82
82
83 ``remotefilelog.backgroundrepack`` automatically consolidate packs in the
83 ``remotefilelog.backgroundrepack`` automatically consolidate packs in the
84 background
84 background
85
85
86 ``remotefilelog.cachepath`` path to cache
86 ``remotefilelog.cachepath`` path to cache
87
87
88 ``remotefilelog.cachegroup`` if set, make cache directory sgid to this
88 ``remotefilelog.cachegroup`` if set, make cache directory sgid to this
89 group
89 group
90
90
91 ``remotefilelog.cacheprocess`` binary to invoke for fetching file data
91 ``remotefilelog.cacheprocess`` binary to invoke for fetching file data
92
92
93 ``remotefilelog.debug`` turn on remotefilelog-specific debug output
93 ``remotefilelog.debug`` turn on remotefilelog-specific debug output
94
94
95 ``remotefilelog.excludepattern`` pattern of files to exclude from pulls
95 ``remotefilelog.excludepattern`` pattern of files to exclude from pulls
96
96
97 ``remotefilelog.includepattern`` pattern of files to include in pulls
97 ``remotefilelog.includepattern`` pattern of files to include in pulls
98
98
99 ``remotefilelog.fetchwarning``: message to print when too many
99 ``remotefilelog.fetchwarning``: message to print when too many
100 single-file fetches occur
100 single-file fetches occur
101
101
102 ``remotefilelog.getfilesstep`` number of files to request in a single RPC
102 ``remotefilelog.getfilesstep`` number of files to request in a single RPC
103
103
104 ``remotefilelog.getfilestype`` if set to 'threaded' use threads to fetch
104 ``remotefilelog.getfilestype`` if set to 'threaded' use threads to fetch
105 files, otherwise use optimistic fetching
105 files, otherwise use optimistic fetching
106
106
107 ``remotefilelog.pullprefetch`` revset for selecting files that should be
107 ``remotefilelog.pullprefetch`` revset for selecting files that should be
108 eagerly downloaded rather than lazily
108 eagerly downloaded rather than lazily
109
109
110 ``remotefilelog.reponame`` name of the repo. If set, used to partition
110 ``remotefilelog.reponame`` name of the repo. If set, used to partition
111 data from other repos in a shared store.
111 data from other repos in a shared store.
112
112
113 ``remotefilelog.server`` if true, enable server-side functionality
113 ``remotefilelog.server`` if true, enable server-side functionality
114
114
115 ``remotefilelog.servercachepath`` path for caching blobs on the server
115 ``remotefilelog.servercachepath`` path for caching blobs on the server
116
116
117 ``remotefilelog.serverexpiration`` number of days to keep cached server
117 ``remotefilelog.serverexpiration`` number of days to keep cached server
118 blobs
118 blobs
119
119
120 ``remotefilelog.validatecache`` if set, check cache entries for corruption
120 ``remotefilelog.validatecache`` if set, check cache entries for corruption
121 before returning blobs
121 before returning blobs
122
122
123 ``remotefilelog.validatecachelog`` if set, check cache entries for
123 ``remotefilelog.validatecachelog`` if set, check cache entries for
124 corruption before returning metadata
124 corruption before returning metadata
125
125
126 """
126 """
127 from __future__ import absolute_import
127 from __future__ import absolute_import
128
128
129 import os
129 import os
130 import time
130 import time
131 import traceback
131 import traceback
132
132
133 from mercurial.node import hex
133 from mercurial.node import hex
134 from mercurial.i18n import _
134 from mercurial.i18n import _
135 from mercurial import (
135 from mercurial import (
136 changegroup,
136 changegroup,
137 changelog,
137 changelog,
138 cmdutil,
138 cmdutil,
139 commands,
139 commands,
140 configitems,
140 configitems,
141 context,
141 context,
142 copies,
142 copies,
143 debugcommands as hgdebugcommands,
143 debugcommands as hgdebugcommands,
144 dispatch,
144 dispatch,
145 error,
145 error,
146 exchange,
146 exchange,
147 extensions,
147 extensions,
148 hg,
148 hg,
149 localrepo,
149 localrepo,
150 match,
150 match,
151 merge,
151 merge,
152 node as nodemod,
152 node as nodemod,
153 patch,
153 patch,
154 pycompat,
154 pycompat,
155 registrar,
155 registrar,
156 repair,
156 repair,
157 repoview,
157 repoview,
158 revset,
158 revset,
159 scmutil,
159 scmutil,
160 smartset,
160 smartset,
161 streamclone,
161 streamclone,
162 templatekw,
163 util,
162 util,
164 )
163 )
165 from . import (
164 from . import (
166 constants,
165 constants,
167 debugcommands,
166 debugcommands,
168 fileserverclient,
167 fileserverclient,
169 remotefilectx,
168 remotefilectx,
170 remotefilelog,
169 remotefilelog,
171 remotefilelogserver,
170 remotefilelogserver,
172 repack as repackmod,
171 repack as repackmod,
173 shallowbundle,
172 shallowbundle,
174 shallowrepo,
173 shallowrepo,
175 shallowstore,
174 shallowstore,
176 shallowutil,
175 shallowutil,
177 shallowverifier,
176 shallowverifier,
178 )
177 )
179
178
180 # ensures debug commands are registered
179 # ensures debug commands are registered
181 hgdebugcommands.command
180 hgdebugcommands.command
182
181
183 cmdtable = {}
182 cmdtable = {}
184 command = registrar.command(cmdtable)
183 command = registrar.command(cmdtable)
185
184
186 configtable = {}
185 configtable = {}
187 configitem = registrar.configitem(configtable)
186 configitem = registrar.configitem(configtable)
188
187
189 configitem('remotefilelog', 'debug', default=False)
188 configitem('remotefilelog', 'debug', default=False)
190
189
191 configitem('remotefilelog', 'reponame', default='')
190 configitem('remotefilelog', 'reponame', default='')
192 configitem('remotefilelog', 'cachepath', default=None)
191 configitem('remotefilelog', 'cachepath', default=None)
193 configitem('remotefilelog', 'cachegroup', default=None)
192 configitem('remotefilelog', 'cachegroup', default=None)
194 configitem('remotefilelog', 'cacheprocess', default=None)
193 configitem('remotefilelog', 'cacheprocess', default=None)
195 configitem('remotefilelog', 'cacheprocess.includepath', default=None)
194 configitem('remotefilelog', 'cacheprocess.includepath', default=None)
196 configitem("remotefilelog", "cachelimit", default="1000 GB")
195 configitem("remotefilelog", "cachelimit", default="1000 GB")
197
196
198 configitem('remotefilelog', 'fallbackpath', default=configitems.dynamicdefault,
197 configitem('remotefilelog', 'fallbackpath', default=configitems.dynamicdefault,
199 alias=[('remotefilelog', 'fallbackrepo')])
198 alias=[('remotefilelog', 'fallbackrepo')])
200
199
201 configitem('remotefilelog', 'validatecachelog', default=None)
200 configitem('remotefilelog', 'validatecachelog', default=None)
202 configitem('remotefilelog', 'validatecache', default='on')
201 configitem('remotefilelog', 'validatecache', default='on')
203 configitem('remotefilelog', 'server', default=None)
202 configitem('remotefilelog', 'server', default=None)
204 configitem('remotefilelog', 'servercachepath', default=None)
203 configitem('remotefilelog', 'servercachepath', default=None)
205 configitem("remotefilelog", "serverexpiration", default=30)
204 configitem("remotefilelog", "serverexpiration", default=30)
206 configitem('remotefilelog', 'backgroundrepack', default=False)
205 configitem('remotefilelog', 'backgroundrepack', default=False)
207 configitem('remotefilelog', 'bgprefetchrevs', default=None)
206 configitem('remotefilelog', 'bgprefetchrevs', default=None)
208 configitem('remotefilelog', 'pullprefetch', default=None)
207 configitem('remotefilelog', 'pullprefetch', default=None)
209 configitem('remotefilelog', 'backgroundprefetch', default=False)
208 configitem('remotefilelog', 'backgroundprefetch', default=False)
210 configitem('remotefilelog', 'prefetchdelay', default=120)
209 configitem('remotefilelog', 'prefetchdelay', default=120)
211 configitem('remotefilelog', 'prefetchdays', default=14)
210 configitem('remotefilelog', 'prefetchdays', default=14)
212
211
213 configitem('remotefilelog', 'getfilesstep', default=10000)
212 configitem('remotefilelog', 'getfilesstep', default=10000)
214 configitem('remotefilelog', 'getfilestype', default='optimistic')
213 configitem('remotefilelog', 'getfilestype', default='optimistic')
215 configitem('remotefilelog', 'batchsize', configitems.dynamicdefault)
214 configitem('remotefilelog', 'batchsize', configitems.dynamicdefault)
216 configitem('remotefilelog', 'fetchwarning', default='')
215 configitem('remotefilelog', 'fetchwarning', default='')
217
216
218 configitem('remotefilelog', 'includepattern', default=None)
217 configitem('remotefilelog', 'includepattern', default=None)
219 configitem('remotefilelog', 'excludepattern', default=None)
218 configitem('remotefilelog', 'excludepattern', default=None)
220
219
221 configitem('remotefilelog', 'gcrepack', default=False)
220 configitem('remotefilelog', 'gcrepack', default=False)
222 configitem('remotefilelog', 'repackonhggc', default=False)
221 configitem('remotefilelog', 'repackonhggc', default=False)
223 configitem('repack', 'chainorphansbysize', default=True)
222 configitem('repack', 'chainorphansbysize', default=True)
224
223
225 configitem('packs', 'maxpacksize', default=0)
224 configitem('packs', 'maxpacksize', default=0)
226 configitem('packs', 'maxchainlen', default=1000)
225 configitem('packs', 'maxchainlen', default=1000)
227
226
228 # default TTL limit is 30 days
227 # default TTL limit is 30 days
229 _defaultlimit = 60 * 60 * 24 * 30
228 _defaultlimit = 60 * 60 * 24 * 30
230 configitem('remotefilelog', 'nodettl', default=_defaultlimit)
229 configitem('remotefilelog', 'nodettl', default=_defaultlimit)
231
230
232 configitem('remotefilelog', 'data.gencountlimit', default=2),
231 configitem('remotefilelog', 'data.gencountlimit', default=2),
233 configitem('remotefilelog', 'data.generations',
232 configitem('remotefilelog', 'data.generations',
234 default=['1GB', '100MB', '1MB'])
233 default=['1GB', '100MB', '1MB'])
235 configitem('remotefilelog', 'data.maxrepackpacks', default=50)
234 configitem('remotefilelog', 'data.maxrepackpacks', default=50)
236 configitem('remotefilelog', 'data.repackmaxpacksize', default='4GB')
235 configitem('remotefilelog', 'data.repackmaxpacksize', default='4GB')
237 configitem('remotefilelog', 'data.repacksizelimit', default='100MB')
236 configitem('remotefilelog', 'data.repacksizelimit', default='100MB')
238
237
239 configitem('remotefilelog', 'history.gencountlimit', default=2),
238 configitem('remotefilelog', 'history.gencountlimit', default=2),
240 configitem('remotefilelog', 'history.generations', default=['100MB'])
239 configitem('remotefilelog', 'history.generations', default=['100MB'])
241 configitem('remotefilelog', 'history.maxrepackpacks', default=50)
240 configitem('remotefilelog', 'history.maxrepackpacks', default=50)
242 configitem('remotefilelog', 'history.repackmaxpacksize', default='400MB')
241 configitem('remotefilelog', 'history.repackmaxpacksize', default='400MB')
243 configitem('remotefilelog', 'history.repacksizelimit', default='100MB')
242 configitem('remotefilelog', 'history.repacksizelimit', default='100MB')
244
243
245 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
244 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
246 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
245 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
247 # be specifying the version(s) of Mercurial they are tested with, or
246 # be specifying the version(s) of Mercurial they are tested with, or
248 # leave the attribute unspecified.
247 # leave the attribute unspecified.
249 testedwith = 'ships-with-hg-core'
248 testedwith = 'ships-with-hg-core'
250
249
251 repoclass = localrepo.localrepository
250 repoclass = localrepo.localrepository
252 repoclass._basesupported.add(constants.SHALLOWREPO_REQUIREMENT)
251 repoclass._basesupported.add(constants.SHALLOWREPO_REQUIREMENT)
253
252
254 isenabled = shallowutil.isenabled
253 isenabled = shallowutil.isenabled
255
254
256 def uisetup(ui):
255 def uisetup(ui):
257 """Wraps user facing Mercurial commands to swap them out with shallow
256 """Wraps user facing Mercurial commands to swap them out with shallow
258 versions.
257 versions.
259 """
258 """
260 hg.wirepeersetupfuncs.append(fileserverclient.peersetup)
259 hg.wirepeersetupfuncs.append(fileserverclient.peersetup)
261
260
262 entry = extensions.wrapcommand(commands.table, 'clone', cloneshallow)
261 entry = extensions.wrapcommand(commands.table, 'clone', cloneshallow)
263 entry[1].append(('', 'shallow', None,
262 entry[1].append(('', 'shallow', None,
264 _("create a shallow clone which uses remote file "
263 _("create a shallow clone which uses remote file "
265 "history")))
264 "history")))
266
265
267 extensions.wrapcommand(commands.table, 'debugindex',
266 extensions.wrapcommand(commands.table, 'debugindex',
268 debugcommands.debugindex)
267 debugcommands.debugindex)
269 extensions.wrapcommand(commands.table, 'debugindexdot',
268 extensions.wrapcommand(commands.table, 'debugindexdot',
270 debugcommands.debugindexdot)
269 debugcommands.debugindexdot)
271 extensions.wrapcommand(commands.table, 'log', log)
270 extensions.wrapcommand(commands.table, 'log', log)
272 extensions.wrapcommand(commands.table, 'pull', pull)
271 extensions.wrapcommand(commands.table, 'pull', pull)
273
272
274 # Prevent 'hg manifest --all'
273 # Prevent 'hg manifest --all'
275 def _manifest(orig, ui, repo, *args, **opts):
274 def _manifest(orig, ui, repo, *args, **opts):
276 if (isenabled(repo) and opts.get(r'all')):
275 if (isenabled(repo) and opts.get(r'all')):
277 raise error.Abort(_("--all is not supported in a shallow repo"))
276 raise error.Abort(_("--all is not supported in a shallow repo"))
278
277
279 return orig(ui, repo, *args, **opts)
278 return orig(ui, repo, *args, **opts)
280 extensions.wrapcommand(commands.table, "manifest", _manifest)
279 extensions.wrapcommand(commands.table, "manifest", _manifest)
281
280
282 # Wrap remotefilelog with lfs code
281 # Wrap remotefilelog with lfs code
283 def _lfsloaded(loaded=False):
282 def _lfsloaded(loaded=False):
284 lfsmod = None
283 lfsmod = None
285 try:
284 try:
286 lfsmod = extensions.find('lfs')
285 lfsmod = extensions.find('lfs')
287 except KeyError:
286 except KeyError:
288 pass
287 pass
289 if lfsmod:
288 if lfsmod:
290 lfsmod.wrapfilelog(remotefilelog.remotefilelog)
289 lfsmod.wrapfilelog(remotefilelog.remotefilelog)
291 fileserverclient._lfsmod = lfsmod
290 fileserverclient._lfsmod = lfsmod
292 extensions.afterloaded('lfs', _lfsloaded)
291 extensions.afterloaded('lfs', _lfsloaded)
293
292
294 # debugdata needs remotefilelog.len to work
293 # debugdata needs remotefilelog.len to work
295 extensions.wrapcommand(commands.table, 'debugdata', debugdatashallow)
294 extensions.wrapcommand(commands.table, 'debugdata', debugdatashallow)
296
295
297 def cloneshallow(orig, ui, repo, *args, **opts):
296 def cloneshallow(orig, ui, repo, *args, **opts):
298 if opts.get(r'shallow'):
297 if opts.get(r'shallow'):
299 repos = []
298 repos = []
300 def pull_shallow(orig, self, *args, **kwargs):
299 def pull_shallow(orig, self, *args, **kwargs):
301 if not isenabled(self):
300 if not isenabled(self):
302 repos.append(self.unfiltered())
301 repos.append(self.unfiltered())
303 # set up the client hooks so the post-clone update works
302 # set up the client hooks so the post-clone update works
304 setupclient(self.ui, self.unfiltered())
303 setupclient(self.ui, self.unfiltered())
305
304
306 # setupclient fixed the class on the repo itself
305 # setupclient fixed the class on the repo itself
307 # but we also need to fix it on the repoview
306 # but we also need to fix it on the repoview
308 if isinstance(self, repoview.repoview):
307 if isinstance(self, repoview.repoview):
309 self.__class__.__bases__ = (self.__class__.__bases__[0],
308 self.__class__.__bases__ = (self.__class__.__bases__[0],
310 self.unfiltered().__class__)
309 self.unfiltered().__class__)
311 self.requirements.add(constants.SHALLOWREPO_REQUIREMENT)
310 self.requirements.add(constants.SHALLOWREPO_REQUIREMENT)
312 self._writerequirements()
311 self._writerequirements()
313
312
314 # Since setupclient hadn't been called, exchange.pull was not
313 # Since setupclient hadn't been called, exchange.pull was not
315 # wrapped. So we need to manually invoke our version of it.
314 # wrapped. So we need to manually invoke our version of it.
316 return exchangepull(orig, self, *args, **kwargs)
315 return exchangepull(orig, self, *args, **kwargs)
317 else:
316 else:
318 return orig(self, *args, **kwargs)
317 return orig(self, *args, **kwargs)
319 extensions.wrapfunction(exchange, 'pull', pull_shallow)
318 extensions.wrapfunction(exchange, 'pull', pull_shallow)
320
319
321 # Wrap the stream logic to add requirements and to pass include/exclude
320 # Wrap the stream logic to add requirements and to pass include/exclude
322 # patterns around.
321 # patterns around.
323 def setup_streamout(repo, remote):
322 def setup_streamout(repo, remote):
324 # Replace remote.stream_out with a version that sends file
323 # Replace remote.stream_out with a version that sends file
325 # patterns.
324 # patterns.
326 def stream_out_shallow(orig):
325 def stream_out_shallow(orig):
327 caps = remote.capabilities()
326 caps = remote.capabilities()
328 if constants.NETWORK_CAP_LEGACY_SSH_GETFILES in caps:
327 if constants.NETWORK_CAP_LEGACY_SSH_GETFILES in caps:
329 opts = {}
328 opts = {}
330 if repo.includepattern:
329 if repo.includepattern:
331 opts[r'includepattern'] = '\0'.join(repo.includepattern)
330 opts[r'includepattern'] = '\0'.join(repo.includepattern)
332 if repo.excludepattern:
331 if repo.excludepattern:
333 opts[r'excludepattern'] = '\0'.join(repo.excludepattern)
332 opts[r'excludepattern'] = '\0'.join(repo.excludepattern)
334 return remote._callstream('stream_out_shallow', **opts)
333 return remote._callstream('stream_out_shallow', **opts)
335 else:
334 else:
336 return orig()
335 return orig()
337 extensions.wrapfunction(remote, 'stream_out', stream_out_shallow)
336 extensions.wrapfunction(remote, 'stream_out', stream_out_shallow)
338 def stream_wrap(orig, op):
337 def stream_wrap(orig, op):
339 setup_streamout(op.repo, op.remote)
338 setup_streamout(op.repo, op.remote)
340 return orig(op)
339 return orig(op)
341 extensions.wrapfunction(
340 extensions.wrapfunction(
342 streamclone, 'maybeperformlegacystreamclone', stream_wrap)
341 streamclone, 'maybeperformlegacystreamclone', stream_wrap)
343
342
344 def canperformstreamclone(orig, pullop, bundle2=False):
343 def canperformstreamclone(orig, pullop, bundle2=False):
345 # remotefilelog is currently incompatible with the
344 # remotefilelog is currently incompatible with the
346 # bundle2 flavor of streamclones, so force us to use
345 # bundle2 flavor of streamclones, so force us to use
347 # v1 instead.
346 # v1 instead.
348 if 'v2' in pullop.remotebundle2caps.get('stream', []):
347 if 'v2' in pullop.remotebundle2caps.get('stream', []):
349 pullop.remotebundle2caps['stream'] = [
348 pullop.remotebundle2caps['stream'] = [
350 c for c in pullop.remotebundle2caps['stream']
349 c for c in pullop.remotebundle2caps['stream']
351 if c != 'v2']
350 if c != 'v2']
352 if bundle2:
351 if bundle2:
353 return False, None
352 return False, None
354 supported, requirements = orig(pullop, bundle2=bundle2)
353 supported, requirements = orig(pullop, bundle2=bundle2)
355 if requirements is not None:
354 if requirements is not None:
356 requirements.add(constants.SHALLOWREPO_REQUIREMENT)
355 requirements.add(constants.SHALLOWREPO_REQUIREMENT)
357 return supported, requirements
356 return supported, requirements
358 extensions.wrapfunction(
357 extensions.wrapfunction(
359 streamclone, 'canperformstreamclone', canperformstreamclone)
358 streamclone, 'canperformstreamclone', canperformstreamclone)
360
359
361 try:
360 try:
362 orig(ui, repo, *args, **opts)
361 orig(ui, repo, *args, **opts)
363 finally:
362 finally:
364 if opts.get(r'shallow'):
363 if opts.get(r'shallow'):
365 for r in repos:
364 for r in repos:
366 if util.safehasattr(r, 'fileservice'):
365 if util.safehasattr(r, 'fileservice'):
367 r.fileservice.close()
366 r.fileservice.close()
368
367
369 def debugdatashallow(orig, *args, **kwds):
368 def debugdatashallow(orig, *args, **kwds):
370 oldlen = remotefilelog.remotefilelog.__len__
369 oldlen = remotefilelog.remotefilelog.__len__
371 try:
370 try:
372 remotefilelog.remotefilelog.__len__ = lambda x: 1
371 remotefilelog.remotefilelog.__len__ = lambda x: 1
373 return orig(*args, **kwds)
372 return orig(*args, **kwds)
374 finally:
373 finally:
375 remotefilelog.remotefilelog.__len__ = oldlen
374 remotefilelog.remotefilelog.__len__ = oldlen
376
375
377 def reposetup(ui, repo):
376 def reposetup(ui, repo):
378 if not repo.local():
377 if not repo.local():
379 return
378 return
380
379
381 # put here intentionally bc doesnt work in uisetup
380 # put here intentionally bc doesnt work in uisetup
382 ui.setconfig('hooks', 'update.prefetch', wcpprefetch)
381 ui.setconfig('hooks', 'update.prefetch', wcpprefetch)
383 ui.setconfig('hooks', 'commit.prefetch', wcpprefetch)
382 ui.setconfig('hooks', 'commit.prefetch', wcpprefetch)
384
383
385 isserverenabled = ui.configbool('remotefilelog', 'server')
384 isserverenabled = ui.configbool('remotefilelog', 'server')
386 isshallowclient = isenabled(repo)
385 isshallowclient = isenabled(repo)
387
386
388 if isserverenabled and isshallowclient:
387 if isserverenabled and isshallowclient:
389 raise RuntimeError("Cannot be both a server and shallow client.")
388 raise RuntimeError("Cannot be both a server and shallow client.")
390
389
391 if isshallowclient:
390 if isshallowclient:
392 setupclient(ui, repo)
391 setupclient(ui, repo)
393
392
394 if isserverenabled:
393 if isserverenabled:
395 remotefilelogserver.setupserver(ui, repo)
394 remotefilelogserver.setupserver(ui, repo)
396
395
397 def setupclient(ui, repo):
396 def setupclient(ui, repo):
398 if not isinstance(repo, localrepo.localrepository):
397 if not isinstance(repo, localrepo.localrepository):
399 return
398 return
400
399
401 # Even clients get the server setup since they need to have the
400 # Even clients get the server setup since they need to have the
402 # wireprotocol endpoints registered.
401 # wireprotocol endpoints registered.
403 remotefilelogserver.onetimesetup(ui)
402 remotefilelogserver.onetimesetup(ui)
404 onetimeclientsetup(ui)
403 onetimeclientsetup(ui)
405
404
406 shallowrepo.wraprepo(repo)
405 shallowrepo.wraprepo(repo)
407 repo.store = shallowstore.wrapstore(repo.store)
406 repo.store = shallowstore.wrapstore(repo.store)
408
407
409 clientonetime = False
408 clientonetime = False
410 def onetimeclientsetup(ui):
409 def onetimeclientsetup(ui):
411 global clientonetime
410 global clientonetime
412 if clientonetime:
411 if clientonetime:
413 return
412 return
414 clientonetime = True
413 clientonetime = True
415
414
416 changegroup.cgpacker = shallowbundle.shallowcg1packer
415 changegroup.cgpacker = shallowbundle.shallowcg1packer
417
416
418 extensions.wrapfunction(changegroup, '_addchangegroupfiles',
417 extensions.wrapfunction(changegroup, '_addchangegroupfiles',
419 shallowbundle.addchangegroupfiles)
418 shallowbundle.addchangegroupfiles)
420 extensions.wrapfunction(
419 extensions.wrapfunction(
421 changegroup, 'makechangegroup', shallowbundle.makechangegroup)
420 changegroup, 'makechangegroup', shallowbundle.makechangegroup)
422
421
423 def storewrapper(orig, requirements, path, vfstype):
422 def storewrapper(orig, requirements, path, vfstype):
424 s = orig(requirements, path, vfstype)
423 s = orig(requirements, path, vfstype)
425 if constants.SHALLOWREPO_REQUIREMENT in requirements:
424 if constants.SHALLOWREPO_REQUIREMENT in requirements:
426 s = shallowstore.wrapstore(s)
425 s = shallowstore.wrapstore(s)
427
426
428 return s
427 return s
429 extensions.wrapfunction(localrepo, 'makestore', storewrapper)
428 extensions.wrapfunction(localrepo, 'makestore', storewrapper)
430
429
431 extensions.wrapfunction(exchange, 'pull', exchangepull)
430 extensions.wrapfunction(exchange, 'pull', exchangepull)
432
431
433 # prefetch files before update
432 # prefetch files before update
434 def applyupdates(orig, repo, actions, wctx, mctx, overwrite, labels=None):
433 def applyupdates(orig, repo, actions, wctx, mctx, overwrite, labels=None):
435 if isenabled(repo):
434 if isenabled(repo):
436 manifest = mctx.manifest()
435 manifest = mctx.manifest()
437 files = []
436 files = []
438 for f, args, msg in actions['g']:
437 for f, args, msg in actions['g']:
439 files.append((f, hex(manifest[f])))
438 files.append((f, hex(manifest[f])))
440 # batch fetch the needed files from the server
439 # batch fetch the needed files from the server
441 repo.fileservice.prefetch(files)
440 repo.fileservice.prefetch(files)
442 return orig(repo, actions, wctx, mctx, overwrite, labels=labels)
441 return orig(repo, actions, wctx, mctx, overwrite, labels=labels)
443 extensions.wrapfunction(merge, 'applyupdates', applyupdates)
442 extensions.wrapfunction(merge, 'applyupdates', applyupdates)
444
443
445 # Prefetch merge checkunknownfiles
444 # Prefetch merge checkunknownfiles
446 def checkunknownfiles(orig, repo, wctx, mctx, force, actions,
445 def checkunknownfiles(orig, repo, wctx, mctx, force, actions,
447 *args, **kwargs):
446 *args, **kwargs):
448 if isenabled(repo):
447 if isenabled(repo):
449 files = []
448 files = []
450 sparsematch = repo.maybesparsematch(mctx.rev())
449 sparsematch = repo.maybesparsematch(mctx.rev())
451 for f, (m, actionargs, msg) in actions.iteritems():
450 for f, (m, actionargs, msg) in actions.iteritems():
452 if sparsematch and not sparsematch(f):
451 if sparsematch and not sparsematch(f):
453 continue
452 continue
454 if m in ('c', 'dc', 'cm'):
453 if m in ('c', 'dc', 'cm'):
455 files.append((f, hex(mctx.filenode(f))))
454 files.append((f, hex(mctx.filenode(f))))
456 elif m == 'dg':
455 elif m == 'dg':
457 f2 = actionargs[0]
456 f2 = actionargs[0]
458 files.append((f2, hex(mctx.filenode(f2))))
457 files.append((f2, hex(mctx.filenode(f2))))
459 # batch fetch the needed files from the server
458 # batch fetch the needed files from the server
460 repo.fileservice.prefetch(files)
459 repo.fileservice.prefetch(files)
461 return orig(repo, wctx, mctx, force, actions, *args, **kwargs)
460 return orig(repo, wctx, mctx, force, actions, *args, **kwargs)
462 extensions.wrapfunction(merge, '_checkunknownfiles', checkunknownfiles)
461 extensions.wrapfunction(merge, '_checkunknownfiles', checkunknownfiles)
463
462
464 # Prefetch files before status attempts to look at their size and contents
463 # Prefetch files before status attempts to look at their size and contents
465 def checklookup(orig, self, files):
464 def checklookup(orig, self, files):
466 repo = self._repo
465 repo = self._repo
467 if isenabled(repo):
466 if isenabled(repo):
468 prefetchfiles = []
467 prefetchfiles = []
469 for parent in self._parents:
468 for parent in self._parents:
470 for f in files:
469 for f in files:
471 if f in parent:
470 if f in parent:
472 prefetchfiles.append((f, hex(parent.filenode(f))))
471 prefetchfiles.append((f, hex(parent.filenode(f))))
473 # batch fetch the needed files from the server
472 # batch fetch the needed files from the server
474 repo.fileservice.prefetch(prefetchfiles)
473 repo.fileservice.prefetch(prefetchfiles)
475 return orig(self, files)
474 return orig(self, files)
476 extensions.wrapfunction(context.workingctx, '_checklookup', checklookup)
475 extensions.wrapfunction(context.workingctx, '_checklookup', checklookup)
477
476
478 # Prefetch the logic that compares added and removed files for renames
477 # Prefetch the logic that compares added and removed files for renames
479 def findrenames(orig, repo, matcher, added, removed, *args, **kwargs):
478 def findrenames(orig, repo, matcher, added, removed, *args, **kwargs):
480 if isenabled(repo):
479 if isenabled(repo):
481 files = []
480 files = []
482 parentctx = repo['.']
481 parentctx = repo['.']
483 for f in removed:
482 for f in removed:
484 files.append((f, hex(parentctx.filenode(f))))
483 files.append((f, hex(parentctx.filenode(f))))
485 # batch fetch the needed files from the server
484 # batch fetch the needed files from the server
486 repo.fileservice.prefetch(files)
485 repo.fileservice.prefetch(files)
487 return orig(repo, matcher, added, removed, *args, **kwargs)
486 return orig(repo, matcher, added, removed, *args, **kwargs)
488 extensions.wrapfunction(scmutil, '_findrenames', findrenames)
487 extensions.wrapfunction(scmutil, '_findrenames', findrenames)
489
488
490 # prefetch files before mergecopies check
489 # prefetch files before mergecopies check
491 def computenonoverlap(orig, repo, c1, c2, *args, **kwargs):
490 def computenonoverlap(orig, repo, c1, c2, *args, **kwargs):
492 u1, u2 = orig(repo, c1, c2, *args, **kwargs)
491 u1, u2 = orig(repo, c1, c2, *args, **kwargs)
493 if isenabled(repo):
492 if isenabled(repo):
494 m1 = c1.manifest()
493 m1 = c1.manifest()
495 m2 = c2.manifest()
494 m2 = c2.manifest()
496 files = []
495 files = []
497
496
498 sparsematch1 = repo.maybesparsematch(c1.rev())
497 sparsematch1 = repo.maybesparsematch(c1.rev())
499 if sparsematch1:
498 if sparsematch1:
500 sparseu1 = []
499 sparseu1 = []
501 for f in u1:
500 for f in u1:
502 if sparsematch1(f):
501 if sparsematch1(f):
503 files.append((f, hex(m1[f])))
502 files.append((f, hex(m1[f])))
504 sparseu1.append(f)
503 sparseu1.append(f)
505 u1 = sparseu1
504 u1 = sparseu1
506
505
507 sparsematch2 = repo.maybesparsematch(c2.rev())
506 sparsematch2 = repo.maybesparsematch(c2.rev())
508 if sparsematch2:
507 if sparsematch2:
509 sparseu2 = []
508 sparseu2 = []
510 for f in u2:
509 for f in u2:
511 if sparsematch2(f):
510 if sparsematch2(f):
512 files.append((f, hex(m2[f])))
511 files.append((f, hex(m2[f])))
513 sparseu2.append(f)
512 sparseu2.append(f)
514 u2 = sparseu2
513 u2 = sparseu2
515
514
516 # batch fetch the needed files from the server
515 # batch fetch the needed files from the server
517 repo.fileservice.prefetch(files)
516 repo.fileservice.prefetch(files)
518 return u1, u2
517 return u1, u2
519 extensions.wrapfunction(copies, '_computenonoverlap', computenonoverlap)
518 extensions.wrapfunction(copies, '_computenonoverlap', computenonoverlap)
520
519
521 # prefetch files before pathcopies check
520 # prefetch files before pathcopies check
522 def computeforwardmissing(orig, a, b, match=None):
521 def computeforwardmissing(orig, a, b, match=None):
523 missing = list(orig(a, b, match=match))
522 missing = list(orig(a, b, match=match))
524 repo = a._repo
523 repo = a._repo
525 if isenabled(repo):
524 if isenabled(repo):
526 mb = b.manifest()
525 mb = b.manifest()
527
526
528 files = []
527 files = []
529 sparsematch = repo.maybesparsematch(b.rev())
528 sparsematch = repo.maybesparsematch(b.rev())
530 if sparsematch:
529 if sparsematch:
531 sparsemissing = []
530 sparsemissing = []
532 for f in missing:
531 for f in missing:
533 if sparsematch(f):
532 if sparsematch(f):
534 files.append((f, hex(mb[f])))
533 files.append((f, hex(mb[f])))
535 sparsemissing.append(f)
534 sparsemissing.append(f)
536 missing = sparsemissing
535 missing = sparsemissing
537
536
538 # batch fetch the needed files from the server
537 # batch fetch the needed files from the server
539 repo.fileservice.prefetch(files)
538 repo.fileservice.prefetch(files)
540 return missing
539 return missing
541 extensions.wrapfunction(copies, '_computeforwardmissing',
540 extensions.wrapfunction(copies, '_computeforwardmissing',
542 computeforwardmissing)
541 computeforwardmissing)
543
542
544 # close cache miss server connection after the command has finished
543 # close cache miss server connection after the command has finished
545 def runcommand(orig, lui, repo, *args, **kwargs):
544 def runcommand(orig, lui, repo, *args, **kwargs):
546 fileservice = None
545 fileservice = None
547 # repo can be None when running in chg:
546 # repo can be None when running in chg:
548 # - at startup, reposetup was called because serve is not norepo
547 # - at startup, reposetup was called because serve is not norepo
549 # - a norepo command like "help" is called
548 # - a norepo command like "help" is called
550 if repo and isenabled(repo):
549 if repo and isenabled(repo):
551 fileservice = repo.fileservice
550 fileservice = repo.fileservice
552 try:
551 try:
553 return orig(lui, repo, *args, **kwargs)
552 return orig(lui, repo, *args, **kwargs)
554 finally:
553 finally:
555 if fileservice:
554 if fileservice:
556 fileservice.close()
555 fileservice.close()
557 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
556 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
558
557
559 # disappointing hacks below
558 # disappointing hacks below
560 templatekw.getrenamedfn = getrenamedfn
559 scmutil.getrenamedfn = getrenamedfn
561 extensions.wrapfunction(revset, 'filelog', filelogrevset)
560 extensions.wrapfunction(revset, 'filelog', filelogrevset)
562 revset.symbols['filelog'] = revset.filelog
561 revset.symbols['filelog'] = revset.filelog
563 extensions.wrapfunction(cmdutil, 'walkfilerevs', walkfilerevs)
562 extensions.wrapfunction(cmdutil, 'walkfilerevs', walkfilerevs)
564
563
565 # prevent strip from stripping remotefilelogs
564 # prevent strip from stripping remotefilelogs
566 def _collectbrokencsets(orig, repo, files, striprev):
565 def _collectbrokencsets(orig, repo, files, striprev):
567 if isenabled(repo):
566 if isenabled(repo):
568 files = list([f for f in files if not repo.shallowmatch(f)])
567 files = list([f for f in files if not repo.shallowmatch(f)])
569 return orig(repo, files, striprev)
568 return orig(repo, files, striprev)
570 extensions.wrapfunction(repair, '_collectbrokencsets', _collectbrokencsets)
569 extensions.wrapfunction(repair, '_collectbrokencsets', _collectbrokencsets)
571
570
572 # Don't commit filelogs until we know the commit hash, since the hash
571 # Don't commit filelogs until we know the commit hash, since the hash
573 # is present in the filelog blob.
572 # is present in the filelog blob.
574 # This violates Mercurial's filelog->manifest->changelog write order,
573 # This violates Mercurial's filelog->manifest->changelog write order,
575 # but is generally fine for client repos.
574 # but is generally fine for client repos.
576 pendingfilecommits = []
575 pendingfilecommits = []
577 def addrawrevision(orig, self, rawtext, transaction, link, p1, p2, node,
576 def addrawrevision(orig, self, rawtext, transaction, link, p1, p2, node,
578 flags, cachedelta=None, _metatuple=None):
577 flags, cachedelta=None, _metatuple=None):
579 if isinstance(link, int):
578 if isinstance(link, int):
580 pendingfilecommits.append(
579 pendingfilecommits.append(
581 (self, rawtext, transaction, link, p1, p2, node, flags,
580 (self, rawtext, transaction, link, p1, p2, node, flags,
582 cachedelta, _metatuple))
581 cachedelta, _metatuple))
583 return node
582 return node
584 else:
583 else:
585 return orig(self, rawtext, transaction, link, p1, p2, node, flags,
584 return orig(self, rawtext, transaction, link, p1, p2, node, flags,
586 cachedelta, _metatuple=_metatuple)
585 cachedelta, _metatuple=_metatuple)
587 extensions.wrapfunction(
586 extensions.wrapfunction(
588 remotefilelog.remotefilelog, 'addrawrevision', addrawrevision)
587 remotefilelog.remotefilelog, 'addrawrevision', addrawrevision)
589
588
590 def changelogadd(orig, self, *args):
589 def changelogadd(orig, self, *args):
591 oldlen = len(self)
590 oldlen = len(self)
592 node = orig(self, *args)
591 node = orig(self, *args)
593 newlen = len(self)
592 newlen = len(self)
594 if oldlen != newlen:
593 if oldlen != newlen:
595 for oldargs in pendingfilecommits:
594 for oldargs in pendingfilecommits:
596 log, rt, tr, link, p1, p2, n, fl, c, m = oldargs
595 log, rt, tr, link, p1, p2, n, fl, c, m = oldargs
597 linknode = self.node(link)
596 linknode = self.node(link)
598 if linknode == node:
597 if linknode == node:
599 log.addrawrevision(rt, tr, linknode, p1, p2, n, fl, c, m)
598 log.addrawrevision(rt, tr, linknode, p1, p2, n, fl, c, m)
600 else:
599 else:
601 raise error.ProgrammingError(
600 raise error.ProgrammingError(
602 'pending multiple integer revisions are not supported')
601 'pending multiple integer revisions are not supported')
603 else:
602 else:
604 # "link" is actually wrong here (it is set to len(changelog))
603 # "link" is actually wrong here (it is set to len(changelog))
605 # if changelog remains unchanged, skip writing file revisions
604 # if changelog remains unchanged, skip writing file revisions
606 # but still do a sanity check about pending multiple revisions
605 # but still do a sanity check about pending multiple revisions
607 if len(set(x[3] for x in pendingfilecommits)) > 1:
606 if len(set(x[3] for x in pendingfilecommits)) > 1:
608 raise error.ProgrammingError(
607 raise error.ProgrammingError(
609 'pending multiple integer revisions are not supported')
608 'pending multiple integer revisions are not supported')
610 del pendingfilecommits[:]
609 del pendingfilecommits[:]
611 return node
610 return node
612 extensions.wrapfunction(changelog.changelog, 'add', changelogadd)
611 extensions.wrapfunction(changelog.changelog, 'add', changelogadd)
613
612
614 # changectx wrappers
613 # changectx wrappers
615 def filectx(orig, self, path, fileid=None, filelog=None):
614 def filectx(orig, self, path, fileid=None, filelog=None):
616 if fileid is None:
615 if fileid is None:
617 fileid = self.filenode(path)
616 fileid = self.filenode(path)
618 if (isenabled(self._repo) and self._repo.shallowmatch(path)):
617 if (isenabled(self._repo) and self._repo.shallowmatch(path)):
619 return remotefilectx.remotefilectx(self._repo, path,
618 return remotefilectx.remotefilectx(self._repo, path,
620 fileid=fileid, changectx=self, filelog=filelog)
619 fileid=fileid, changectx=self, filelog=filelog)
621 return orig(self, path, fileid=fileid, filelog=filelog)
620 return orig(self, path, fileid=fileid, filelog=filelog)
622 extensions.wrapfunction(context.changectx, 'filectx', filectx)
621 extensions.wrapfunction(context.changectx, 'filectx', filectx)
623
622
624 def workingfilectx(orig, self, path, filelog=None):
623 def workingfilectx(orig, self, path, filelog=None):
625 if (isenabled(self._repo) and self._repo.shallowmatch(path)):
624 if (isenabled(self._repo) and self._repo.shallowmatch(path)):
626 return remotefilectx.remoteworkingfilectx(self._repo,
625 return remotefilectx.remoteworkingfilectx(self._repo,
627 path, workingctx=self, filelog=filelog)
626 path, workingctx=self, filelog=filelog)
628 return orig(self, path, filelog=filelog)
627 return orig(self, path, filelog=filelog)
629 extensions.wrapfunction(context.workingctx, 'filectx', workingfilectx)
628 extensions.wrapfunction(context.workingctx, 'filectx', workingfilectx)
630
629
631 # prefetch required revisions before a diff
630 # prefetch required revisions before a diff
632 def trydiff(orig, repo, revs, ctx1, ctx2, modified, added, removed,
631 def trydiff(orig, repo, revs, ctx1, ctx2, modified, added, removed,
633 copy, getfilectx, *args, **kwargs):
632 copy, getfilectx, *args, **kwargs):
634 if isenabled(repo):
633 if isenabled(repo):
635 prefetch = []
634 prefetch = []
636 mf1 = ctx1.manifest()
635 mf1 = ctx1.manifest()
637 for fname in modified + added + removed:
636 for fname in modified + added + removed:
638 if fname in mf1:
637 if fname in mf1:
639 fnode = getfilectx(fname, ctx1).filenode()
638 fnode = getfilectx(fname, ctx1).filenode()
640 # fnode can be None if it's a edited working ctx file
639 # fnode can be None if it's a edited working ctx file
641 if fnode:
640 if fnode:
642 prefetch.append((fname, hex(fnode)))
641 prefetch.append((fname, hex(fnode)))
643 if fname not in removed:
642 if fname not in removed:
644 fnode = getfilectx(fname, ctx2).filenode()
643 fnode = getfilectx(fname, ctx2).filenode()
645 if fnode:
644 if fnode:
646 prefetch.append((fname, hex(fnode)))
645 prefetch.append((fname, hex(fnode)))
647
646
648 repo.fileservice.prefetch(prefetch)
647 repo.fileservice.prefetch(prefetch)
649
648
650 return orig(repo, revs, ctx1, ctx2, modified, added, removed,
649 return orig(repo, revs, ctx1, ctx2, modified, added, removed,
651 copy, getfilectx, *args, **kwargs)
650 copy, getfilectx, *args, **kwargs)
652 extensions.wrapfunction(patch, 'trydiff', trydiff)
651 extensions.wrapfunction(patch, 'trydiff', trydiff)
653
652
654 # Prevent verify from processing files
653 # Prevent verify from processing files
655 # a stub for mercurial.hg.verify()
654 # a stub for mercurial.hg.verify()
656 def _verify(orig, repo):
655 def _verify(orig, repo):
657 lock = repo.lock()
656 lock = repo.lock()
658 try:
657 try:
659 return shallowverifier.shallowverifier(repo).verify()
658 return shallowverifier.shallowverifier(repo).verify()
660 finally:
659 finally:
661 lock.release()
660 lock.release()
662
661
663 extensions.wrapfunction(hg, 'verify', _verify)
662 extensions.wrapfunction(hg, 'verify', _verify)
664
663
665 scmutil.fileprefetchhooks.add('remotefilelog', _fileprefetchhook)
664 scmutil.fileprefetchhooks.add('remotefilelog', _fileprefetchhook)
666
665
667 def getrenamedfn(repo, endrev=None):
666 def getrenamedfn(repo, endrev=None):
668 rcache = {}
667 rcache = {}
669
668
670 def getrenamed(fn, rev):
669 def getrenamed(fn, rev):
671 '''looks up all renames for a file (up to endrev) the first
670 '''looks up all renames for a file (up to endrev) the first
672 time the file is given. It indexes on the changerev and only
671 time the file is given. It indexes on the changerev and only
673 parses the manifest if linkrev != changerev.
672 parses the manifest if linkrev != changerev.
674 Returns rename info for fn at changerev rev.'''
673 Returns rename info for fn at changerev rev.'''
675 if rev in rcache.setdefault(fn, {}):
674 if rev in rcache.setdefault(fn, {}):
676 return rcache[fn][rev]
675 return rcache[fn][rev]
677
676
678 try:
677 try:
679 fctx = repo[rev].filectx(fn)
678 fctx = repo[rev].filectx(fn)
680 for ancestor in fctx.ancestors():
679 for ancestor in fctx.ancestors():
681 if ancestor.path() == fn:
680 if ancestor.path() == fn:
682 renamed = ancestor.renamed()
681 renamed = ancestor.renamed()
683 rcache[fn][ancestor.rev()] = renamed and renamed[0]
682 rcache[fn][ancestor.rev()] = renamed and renamed[0]
684
683
685 renamed = fctx.renamed()
684 renamed = fctx.renamed()
686 return renamed and renamed[0]
685 return renamed and renamed[0]
687 except error.LookupError:
686 except error.LookupError:
688 return None
687 return None
689
688
690 return getrenamed
689 return getrenamed
691
690
692 def walkfilerevs(orig, repo, match, follow, revs, fncache):
691 def walkfilerevs(orig, repo, match, follow, revs, fncache):
693 if not isenabled(repo):
692 if not isenabled(repo):
694 return orig(repo, match, follow, revs, fncache)
693 return orig(repo, match, follow, revs, fncache)
695
694
696 # remotefilelog's can't be walked in rev order, so throw.
695 # remotefilelog's can't be walked in rev order, so throw.
697 # The caller will see the exception and walk the commit tree instead.
696 # The caller will see the exception and walk the commit tree instead.
698 if not follow:
697 if not follow:
699 raise cmdutil.FileWalkError("Cannot walk via filelog")
698 raise cmdutil.FileWalkError("Cannot walk via filelog")
700
699
701 wanted = set()
700 wanted = set()
702 minrev, maxrev = min(revs), max(revs)
701 minrev, maxrev = min(revs), max(revs)
703
702
704 pctx = repo['.']
703 pctx = repo['.']
705 for filename in match.files():
704 for filename in match.files():
706 if filename not in pctx:
705 if filename not in pctx:
707 raise error.Abort(_('cannot follow file not in parent '
706 raise error.Abort(_('cannot follow file not in parent '
708 'revision: "%s"') % filename)
707 'revision: "%s"') % filename)
709 fctx = pctx[filename]
708 fctx = pctx[filename]
710
709
711 linkrev = fctx.linkrev()
710 linkrev = fctx.linkrev()
712 if linkrev >= minrev and linkrev <= maxrev:
711 if linkrev >= minrev and linkrev <= maxrev:
713 fncache.setdefault(linkrev, []).append(filename)
712 fncache.setdefault(linkrev, []).append(filename)
714 wanted.add(linkrev)
713 wanted.add(linkrev)
715
714
716 for ancestor in fctx.ancestors():
715 for ancestor in fctx.ancestors():
717 linkrev = ancestor.linkrev()
716 linkrev = ancestor.linkrev()
718 if linkrev >= minrev and linkrev <= maxrev:
717 if linkrev >= minrev and linkrev <= maxrev:
719 fncache.setdefault(linkrev, []).append(ancestor.path())
718 fncache.setdefault(linkrev, []).append(ancestor.path())
720 wanted.add(linkrev)
719 wanted.add(linkrev)
721
720
722 return wanted
721 return wanted
723
722
724 def filelogrevset(orig, repo, subset, x):
723 def filelogrevset(orig, repo, subset, x):
725 """``filelog(pattern)``
724 """``filelog(pattern)``
726 Changesets connected to the specified filelog.
725 Changesets connected to the specified filelog.
727
726
728 For performance reasons, ``filelog()`` does not show every changeset
727 For performance reasons, ``filelog()`` does not show every changeset
729 that affects the requested file(s). See :hg:`help log` for details. For
728 that affects the requested file(s). See :hg:`help log` for details. For
730 a slower, more accurate result, use ``file()``.
729 a slower, more accurate result, use ``file()``.
731 """
730 """
732
731
733 if not isenabled(repo):
732 if not isenabled(repo):
734 return orig(repo, subset, x)
733 return orig(repo, subset, x)
735
734
736 # i18n: "filelog" is a keyword
735 # i18n: "filelog" is a keyword
737 pat = revset.getstring(x, _("filelog requires a pattern"))
736 pat = revset.getstring(x, _("filelog requires a pattern"))
738 m = match.match(repo.root, repo.getcwd(), [pat], default='relpath',
737 m = match.match(repo.root, repo.getcwd(), [pat], default='relpath',
739 ctx=repo[None])
738 ctx=repo[None])
740 s = set()
739 s = set()
741
740
742 if not match.patkind(pat):
741 if not match.patkind(pat):
743 # slow
742 # slow
744 for r in subset:
743 for r in subset:
745 ctx = repo[r]
744 ctx = repo[r]
746 cfiles = ctx.files()
745 cfiles = ctx.files()
747 for f in m.files():
746 for f in m.files():
748 if f in cfiles:
747 if f in cfiles:
749 s.add(ctx.rev())
748 s.add(ctx.rev())
750 break
749 break
751 else:
750 else:
752 # partial
751 # partial
753 files = (f for f in repo[None] if m(f))
752 files = (f for f in repo[None] if m(f))
754 for f in files:
753 for f in files:
755 fctx = repo[None].filectx(f)
754 fctx = repo[None].filectx(f)
756 s.add(fctx.linkrev())
755 s.add(fctx.linkrev())
757 for actx in fctx.ancestors():
756 for actx in fctx.ancestors():
758 s.add(actx.linkrev())
757 s.add(actx.linkrev())
759
758
760 return smartset.baseset([r for r in subset if r in s])
759 return smartset.baseset([r for r in subset if r in s])
761
760
762 @command('gc', [], _('hg gc [REPO...]'), norepo=True)
761 @command('gc', [], _('hg gc [REPO...]'), norepo=True)
763 def gc(ui, *args, **opts):
762 def gc(ui, *args, **opts):
764 '''garbage collect the client and server filelog caches
763 '''garbage collect the client and server filelog caches
765 '''
764 '''
766 cachepaths = set()
765 cachepaths = set()
767
766
768 # get the system client cache
767 # get the system client cache
769 systemcache = shallowutil.getcachepath(ui, allowempty=True)
768 systemcache = shallowutil.getcachepath(ui, allowempty=True)
770 if systemcache:
769 if systemcache:
771 cachepaths.add(systemcache)
770 cachepaths.add(systemcache)
772
771
773 # get repo client and server cache
772 # get repo client and server cache
774 repopaths = []
773 repopaths = []
775 pwd = ui.environ.get('PWD')
774 pwd = ui.environ.get('PWD')
776 if pwd:
775 if pwd:
777 repopaths.append(pwd)
776 repopaths.append(pwd)
778
777
779 repopaths.extend(args)
778 repopaths.extend(args)
780 repos = []
779 repos = []
781 for repopath in repopaths:
780 for repopath in repopaths:
782 try:
781 try:
783 repo = hg.peer(ui, {}, repopath)
782 repo = hg.peer(ui, {}, repopath)
784 repos.append(repo)
783 repos.append(repo)
785
784
786 repocache = shallowutil.getcachepath(repo.ui, allowempty=True)
785 repocache = shallowutil.getcachepath(repo.ui, allowempty=True)
787 if repocache:
786 if repocache:
788 cachepaths.add(repocache)
787 cachepaths.add(repocache)
789 except error.RepoError:
788 except error.RepoError:
790 pass
789 pass
791
790
792 # gc client cache
791 # gc client cache
793 for cachepath in cachepaths:
792 for cachepath in cachepaths:
794 gcclient(ui, cachepath)
793 gcclient(ui, cachepath)
795
794
796 # gc server cache
795 # gc server cache
797 for repo in repos:
796 for repo in repos:
798 remotefilelogserver.gcserver(ui, repo._repo)
797 remotefilelogserver.gcserver(ui, repo._repo)
799
798
800 def gcclient(ui, cachepath):
799 def gcclient(ui, cachepath):
801 # get list of repos that use this cache
800 # get list of repos that use this cache
802 repospath = os.path.join(cachepath, 'repos')
801 repospath = os.path.join(cachepath, 'repos')
803 if not os.path.exists(repospath):
802 if not os.path.exists(repospath):
804 ui.warn(_("no known cache at %s\n") % cachepath)
803 ui.warn(_("no known cache at %s\n") % cachepath)
805 return
804 return
806
805
807 reposfile = open(repospath, 'rb')
806 reposfile = open(repospath, 'rb')
808 repos = set([r[:-1] for r in reposfile.readlines()])
807 repos = set([r[:-1] for r in reposfile.readlines()])
809 reposfile.close()
808 reposfile.close()
810
809
811 # build list of useful files
810 # build list of useful files
812 validrepos = []
811 validrepos = []
813 keepkeys = set()
812 keepkeys = set()
814
813
815 sharedcache = None
814 sharedcache = None
816 filesrepacked = False
815 filesrepacked = False
817
816
818 count = 0
817 count = 0
819 progress = ui.makeprogress(_("analyzing repositories"), unit="repos",
818 progress = ui.makeprogress(_("analyzing repositories"), unit="repos",
820 total=len(repos))
819 total=len(repos))
821 for path in repos:
820 for path in repos:
822 progress.update(count)
821 progress.update(count)
823 count += 1
822 count += 1
824 try:
823 try:
825 path = ui.expandpath(os.path.normpath(path))
824 path = ui.expandpath(os.path.normpath(path))
826 except TypeError as e:
825 except TypeError as e:
827 ui.warn(_("warning: malformed path: %r:%s\n") % (path, e))
826 ui.warn(_("warning: malformed path: %r:%s\n") % (path, e))
828 traceback.print_exc()
827 traceback.print_exc()
829 continue
828 continue
830 try:
829 try:
831 peer = hg.peer(ui, {}, path)
830 peer = hg.peer(ui, {}, path)
832 repo = peer._repo
831 repo = peer._repo
833 except error.RepoError:
832 except error.RepoError:
834 continue
833 continue
835
834
836 validrepos.append(path)
835 validrepos.append(path)
837
836
838 # Protect against any repo or config changes that have happened since
837 # Protect against any repo or config changes that have happened since
839 # this repo was added to the repos file. We'd rather this loop succeed
838 # this repo was added to the repos file. We'd rather this loop succeed
840 # and too much be deleted, than the loop fail and nothing gets deleted.
839 # and too much be deleted, than the loop fail and nothing gets deleted.
841 if not isenabled(repo):
840 if not isenabled(repo):
842 continue
841 continue
843
842
844 if not util.safehasattr(repo, 'name'):
843 if not util.safehasattr(repo, 'name'):
845 ui.warn(_("repo %s is a misconfigured remotefilelog repo\n") % path)
844 ui.warn(_("repo %s is a misconfigured remotefilelog repo\n") % path)
846 continue
845 continue
847
846
848 # If garbage collection on repack and repack on hg gc are enabled
847 # If garbage collection on repack and repack on hg gc are enabled
849 # then loose files are repacked and garbage collected.
848 # then loose files are repacked and garbage collected.
850 # Otherwise regular garbage collection is performed.
849 # Otherwise regular garbage collection is performed.
851 repackonhggc = repo.ui.configbool('remotefilelog', 'repackonhggc')
850 repackonhggc = repo.ui.configbool('remotefilelog', 'repackonhggc')
852 gcrepack = repo.ui.configbool('remotefilelog', 'gcrepack')
851 gcrepack = repo.ui.configbool('remotefilelog', 'gcrepack')
853 if repackonhggc and gcrepack:
852 if repackonhggc and gcrepack:
854 try:
853 try:
855 repackmod.incrementalrepack(repo)
854 repackmod.incrementalrepack(repo)
856 filesrepacked = True
855 filesrepacked = True
857 continue
856 continue
858 except (IOError, repackmod.RepackAlreadyRunning):
857 except (IOError, repackmod.RepackAlreadyRunning):
859 # If repack cannot be performed due to not enough disk space
858 # If repack cannot be performed due to not enough disk space
860 # continue doing garbage collection of loose files w/o repack
859 # continue doing garbage collection of loose files w/o repack
861 pass
860 pass
862
861
863 reponame = repo.name
862 reponame = repo.name
864 if not sharedcache:
863 if not sharedcache:
865 sharedcache = repo.sharedstore
864 sharedcache = repo.sharedstore
866
865
867 # Compute a keepset which is not garbage collected
866 # Compute a keepset which is not garbage collected
868 def keyfn(fname, fnode):
867 def keyfn(fname, fnode):
869 return fileserverclient.getcachekey(reponame, fname, hex(fnode))
868 return fileserverclient.getcachekey(reponame, fname, hex(fnode))
870 keepkeys = repackmod.keepset(repo, keyfn=keyfn, lastkeepkeys=keepkeys)
869 keepkeys = repackmod.keepset(repo, keyfn=keyfn, lastkeepkeys=keepkeys)
871
870
872 progress.complete()
871 progress.complete()
873
872
874 # write list of valid repos back
873 # write list of valid repos back
875 oldumask = os.umask(0o002)
874 oldumask = os.umask(0o002)
876 try:
875 try:
877 reposfile = open(repospath, 'wb')
876 reposfile = open(repospath, 'wb')
878 reposfile.writelines([("%s\n" % r) for r in validrepos])
877 reposfile.writelines([("%s\n" % r) for r in validrepos])
879 reposfile.close()
878 reposfile.close()
880 finally:
879 finally:
881 os.umask(oldumask)
880 os.umask(oldumask)
882
881
883 # prune cache
882 # prune cache
884 if sharedcache is not None:
883 if sharedcache is not None:
885 sharedcache.gc(keepkeys)
884 sharedcache.gc(keepkeys)
886 elif not filesrepacked:
885 elif not filesrepacked:
887 ui.warn(_("warning: no valid repos in repofile\n"))
886 ui.warn(_("warning: no valid repos in repofile\n"))
888
887
889 def log(orig, ui, repo, *pats, **opts):
888 def log(orig, ui, repo, *pats, **opts):
890 if not isenabled(repo):
889 if not isenabled(repo):
891 return orig(ui, repo, *pats, **opts)
890 return orig(ui, repo, *pats, **opts)
892
891
893 follow = opts.get(r'follow')
892 follow = opts.get(r'follow')
894 revs = opts.get(r'rev')
893 revs = opts.get(r'rev')
895 if pats:
894 if pats:
896 # Force slowpath for non-follow patterns and follows that start from
895 # Force slowpath for non-follow patterns and follows that start from
897 # non-working-copy-parent revs.
896 # non-working-copy-parent revs.
898 if not follow or revs:
897 if not follow or revs:
899 # This forces the slowpath
898 # This forces the slowpath
900 opts[r'removed'] = True
899 opts[r'removed'] = True
901
900
902 # If this is a non-follow log without any revs specified, recommend that
901 # If this is a non-follow log without any revs specified, recommend that
903 # the user add -f to speed it up.
902 # the user add -f to speed it up.
904 if not follow and not revs:
903 if not follow and not revs:
905 match = scmutil.match(repo['.'], pats, pycompat.byteskwargs(opts))
904 match = scmutil.match(repo['.'], pats, pycompat.byteskwargs(opts))
906 isfile = not match.anypats()
905 isfile = not match.anypats()
907 if isfile:
906 if isfile:
908 for file in match.files():
907 for file in match.files():
909 if not os.path.isfile(repo.wjoin(file)):
908 if not os.path.isfile(repo.wjoin(file)):
910 isfile = False
909 isfile = False
911 break
910 break
912
911
913 if isfile:
912 if isfile:
914 ui.warn(_("warning: file log can be slow on large repos - " +
913 ui.warn(_("warning: file log can be slow on large repos - " +
915 "use -f to speed it up\n"))
914 "use -f to speed it up\n"))
916
915
917 return orig(ui, repo, *pats, **opts)
916 return orig(ui, repo, *pats, **opts)
918
917
919 def revdatelimit(ui, revset):
918 def revdatelimit(ui, revset):
920 """Update revset so that only changesets no older than 'prefetchdays' days
919 """Update revset so that only changesets no older than 'prefetchdays' days
921 are included. The default value is set to 14 days. If 'prefetchdays' is set
920 are included. The default value is set to 14 days. If 'prefetchdays' is set
922 to zero or negative value then date restriction is not applied.
921 to zero or negative value then date restriction is not applied.
923 """
922 """
924 days = ui.configint('remotefilelog', 'prefetchdays')
923 days = ui.configint('remotefilelog', 'prefetchdays')
925 if days > 0:
924 if days > 0:
926 revset = '(%s) & date(-%s)' % (revset, days)
925 revset = '(%s) & date(-%s)' % (revset, days)
927 return revset
926 return revset
928
927
929 def readytofetch(repo):
928 def readytofetch(repo):
930 """Check that enough time has passed since the last background prefetch.
929 """Check that enough time has passed since the last background prefetch.
931 This only relates to prefetches after operations that change the working
930 This only relates to prefetches after operations that change the working
932 copy parent. Default delay between background prefetches is 2 minutes.
931 copy parent. Default delay between background prefetches is 2 minutes.
933 """
932 """
934 timeout = repo.ui.configint('remotefilelog', 'prefetchdelay')
933 timeout = repo.ui.configint('remotefilelog', 'prefetchdelay')
935 fname = repo.vfs.join('lastprefetch')
934 fname = repo.vfs.join('lastprefetch')
936
935
937 ready = False
936 ready = False
938 with open(fname, 'a'):
937 with open(fname, 'a'):
939 # the with construct above is used to avoid race conditions
938 # the with construct above is used to avoid race conditions
940 modtime = os.path.getmtime(fname)
939 modtime = os.path.getmtime(fname)
941 if (time.time() - modtime) > timeout:
940 if (time.time() - modtime) > timeout:
942 os.utime(fname, None)
941 os.utime(fname, None)
943 ready = True
942 ready = True
944
943
945 return ready
944 return ready
946
945
947 def wcpprefetch(ui, repo, **kwargs):
946 def wcpprefetch(ui, repo, **kwargs):
948 """Prefetches in background revisions specified by bgprefetchrevs revset.
947 """Prefetches in background revisions specified by bgprefetchrevs revset.
949 Does background repack if backgroundrepack flag is set in config.
948 Does background repack if backgroundrepack flag is set in config.
950 """
949 """
951 shallow = isenabled(repo)
950 shallow = isenabled(repo)
952 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs')
951 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs')
953 isready = readytofetch(repo)
952 isready = readytofetch(repo)
954
953
955 if not (shallow and bgprefetchrevs and isready):
954 if not (shallow and bgprefetchrevs and isready):
956 return
955 return
957
956
958 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
957 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
959 # update a revset with a date limit
958 # update a revset with a date limit
960 bgprefetchrevs = revdatelimit(ui, bgprefetchrevs)
959 bgprefetchrevs = revdatelimit(ui, bgprefetchrevs)
961
960
962 def anon():
961 def anon():
963 if util.safehasattr(repo, 'ranprefetch') and repo.ranprefetch:
962 if util.safehasattr(repo, 'ranprefetch') and repo.ranprefetch:
964 return
963 return
965 repo.ranprefetch = True
964 repo.ranprefetch = True
966 repo.backgroundprefetch(bgprefetchrevs, repack=bgrepack)
965 repo.backgroundprefetch(bgprefetchrevs, repack=bgrepack)
967
966
968 repo._afterlock(anon)
967 repo._afterlock(anon)
969
968
970 def pull(orig, ui, repo, *pats, **opts):
969 def pull(orig, ui, repo, *pats, **opts):
971 result = orig(ui, repo, *pats, **opts)
970 result = orig(ui, repo, *pats, **opts)
972
971
973 if isenabled(repo):
972 if isenabled(repo):
974 # prefetch if it's configured
973 # prefetch if it's configured
975 prefetchrevset = ui.config('remotefilelog', 'pullprefetch')
974 prefetchrevset = ui.config('remotefilelog', 'pullprefetch')
976 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
975 bgrepack = repo.ui.configbool('remotefilelog', 'backgroundrepack')
977 bgprefetch = repo.ui.configbool('remotefilelog', 'backgroundprefetch')
976 bgprefetch = repo.ui.configbool('remotefilelog', 'backgroundprefetch')
978
977
979 if prefetchrevset:
978 if prefetchrevset:
980 ui.status(_("prefetching file contents\n"))
979 ui.status(_("prefetching file contents\n"))
981 revs = scmutil.revrange(repo, [prefetchrevset])
980 revs = scmutil.revrange(repo, [prefetchrevset])
982 base = repo['.'].rev()
981 base = repo['.'].rev()
983 if bgprefetch:
982 if bgprefetch:
984 repo.backgroundprefetch(prefetchrevset, repack=bgrepack)
983 repo.backgroundprefetch(prefetchrevset, repack=bgrepack)
985 else:
984 else:
986 repo.prefetch(revs, base=base)
985 repo.prefetch(revs, base=base)
987 if bgrepack:
986 if bgrepack:
988 repackmod.backgroundrepack(repo, incremental=True)
987 repackmod.backgroundrepack(repo, incremental=True)
989 elif bgrepack:
988 elif bgrepack:
990 repackmod.backgroundrepack(repo, incremental=True)
989 repackmod.backgroundrepack(repo, incremental=True)
991
990
992 return result
991 return result
993
992
994 def exchangepull(orig, repo, remote, *args, **kwargs):
993 def exchangepull(orig, repo, remote, *args, **kwargs):
995 # Hook into the callstream/getbundle to insert bundle capabilities
994 # Hook into the callstream/getbundle to insert bundle capabilities
996 # during a pull.
995 # during a pull.
997 def localgetbundle(orig, source, heads=None, common=None, bundlecaps=None,
996 def localgetbundle(orig, source, heads=None, common=None, bundlecaps=None,
998 **kwargs):
997 **kwargs):
999 if not bundlecaps:
998 if not bundlecaps:
1000 bundlecaps = set()
999 bundlecaps = set()
1001 bundlecaps.add(constants.BUNDLE2_CAPABLITY)
1000 bundlecaps.add(constants.BUNDLE2_CAPABLITY)
1002 return orig(source, heads=heads, common=common, bundlecaps=bundlecaps,
1001 return orig(source, heads=heads, common=common, bundlecaps=bundlecaps,
1003 **kwargs)
1002 **kwargs)
1004
1003
1005 if util.safehasattr(remote, '_callstream'):
1004 if util.safehasattr(remote, '_callstream'):
1006 remote._localrepo = repo
1005 remote._localrepo = repo
1007 elif util.safehasattr(remote, 'getbundle'):
1006 elif util.safehasattr(remote, 'getbundle'):
1008 extensions.wrapfunction(remote, 'getbundle', localgetbundle)
1007 extensions.wrapfunction(remote, 'getbundle', localgetbundle)
1009
1008
1010 return orig(repo, remote, *args, **kwargs)
1009 return orig(repo, remote, *args, **kwargs)
1011
1010
1012 def _fileprefetchhook(repo, revs, match):
1011 def _fileprefetchhook(repo, revs, match):
1013 if isenabled(repo):
1012 if isenabled(repo):
1014 allfiles = []
1013 allfiles = []
1015 for rev in revs:
1014 for rev in revs:
1016 if rev == nodemod.wdirrev or rev is None:
1015 if rev == nodemod.wdirrev or rev is None:
1017 continue
1016 continue
1018 ctx = repo[rev]
1017 ctx = repo[rev]
1019 mf = ctx.manifest()
1018 mf = ctx.manifest()
1020 sparsematch = repo.maybesparsematch(ctx.rev())
1019 sparsematch = repo.maybesparsematch(ctx.rev())
1021 for path in ctx.walk(match):
1020 for path in ctx.walk(match):
1022 if path.endswith('/'):
1021 if path.endswith('/'):
1023 # Tree manifest that's being excluded as part of narrow
1022 # Tree manifest that's being excluded as part of narrow
1024 continue
1023 continue
1025 if (not sparsematch or sparsematch(path)) and path in mf:
1024 if (not sparsematch or sparsematch(path)) and path in mf:
1026 allfiles.append((path, hex(mf[path])))
1025 allfiles.append((path, hex(mf[path])))
1027 repo.fileservice.prefetch(allfiles)
1026 repo.fileservice.prefetch(allfiles)
1028
1027
1029 @command('debugremotefilelog', [
1028 @command('debugremotefilelog', [
1030 ('d', 'decompress', None, _('decompress the filelog first')),
1029 ('d', 'decompress', None, _('decompress the filelog first')),
1031 ], _('hg debugremotefilelog <path>'), norepo=True)
1030 ], _('hg debugremotefilelog <path>'), norepo=True)
1032 def debugremotefilelog(ui, path, **opts):
1031 def debugremotefilelog(ui, path, **opts):
1033 return debugcommands.debugremotefilelog(ui, path, **opts)
1032 return debugcommands.debugremotefilelog(ui, path, **opts)
1034
1033
1035 @command('verifyremotefilelog', [
1034 @command('verifyremotefilelog', [
1036 ('d', 'decompress', None, _('decompress the filelogs first')),
1035 ('d', 'decompress', None, _('decompress the filelogs first')),
1037 ], _('hg verifyremotefilelogs <directory>'), norepo=True)
1036 ], _('hg verifyremotefilelogs <directory>'), norepo=True)
1038 def verifyremotefilelog(ui, path, **opts):
1037 def verifyremotefilelog(ui, path, **opts):
1039 return debugcommands.verifyremotefilelog(ui, path, **opts)
1038 return debugcommands.verifyremotefilelog(ui, path, **opts)
1040
1039
1041 @command('debugdatapack', [
1040 @command('debugdatapack', [
1042 ('', 'long', None, _('print the long hashes')),
1041 ('', 'long', None, _('print the long hashes')),
1043 ('', 'node', '', _('dump the contents of node'), 'NODE'),
1042 ('', 'node', '', _('dump the contents of node'), 'NODE'),
1044 ], _('hg debugdatapack <paths>'), norepo=True)
1043 ], _('hg debugdatapack <paths>'), norepo=True)
1045 def debugdatapack(ui, *paths, **opts):
1044 def debugdatapack(ui, *paths, **opts):
1046 return debugcommands.debugdatapack(ui, *paths, **opts)
1045 return debugcommands.debugdatapack(ui, *paths, **opts)
1047
1046
1048 @command('debughistorypack', [
1047 @command('debughistorypack', [
1049 ], _('hg debughistorypack <path>'), norepo=True)
1048 ], _('hg debughistorypack <path>'), norepo=True)
1050 def debughistorypack(ui, path, **opts):
1049 def debughistorypack(ui, path, **opts):
1051 return debugcommands.debughistorypack(ui, path)
1050 return debugcommands.debughistorypack(ui, path)
1052
1051
1053 @command('debugkeepset', [
1052 @command('debugkeepset', [
1054 ], _('hg debugkeepset'))
1053 ], _('hg debugkeepset'))
1055 def debugkeepset(ui, repo, **opts):
1054 def debugkeepset(ui, repo, **opts):
1056 # The command is used to measure keepset computation time
1055 # The command is used to measure keepset computation time
1057 def keyfn(fname, fnode):
1056 def keyfn(fname, fnode):
1058 return fileserverclient.getcachekey(repo.name, fname, hex(fnode))
1057 return fileserverclient.getcachekey(repo.name, fname, hex(fnode))
1059 repackmod.keepset(repo, keyfn)
1058 repackmod.keepset(repo, keyfn)
1060 return
1059 return
1061
1060
1062 @command('debugwaitonrepack', [
1061 @command('debugwaitonrepack', [
1063 ], _('hg debugwaitonrepack'))
1062 ], _('hg debugwaitonrepack'))
1064 def debugwaitonrepack(ui, repo, **opts):
1063 def debugwaitonrepack(ui, repo, **opts):
1065 return debugcommands.debugwaitonrepack(repo)
1064 return debugcommands.debugwaitonrepack(repo)
1066
1065
1067 @command('debugwaitonprefetch', [
1066 @command('debugwaitonprefetch', [
1068 ], _('hg debugwaitonprefetch'))
1067 ], _('hg debugwaitonprefetch'))
1069 def debugwaitonprefetch(ui, repo, **opts):
1068 def debugwaitonprefetch(ui, repo, **opts):
1070 return debugcommands.debugwaitonprefetch(repo)
1069 return debugcommands.debugwaitonprefetch(repo)
1071
1070
1072 def resolveprefetchopts(ui, opts):
1071 def resolveprefetchopts(ui, opts):
1073 if not opts.get('rev'):
1072 if not opts.get('rev'):
1074 revset = ['.', 'draft()']
1073 revset = ['.', 'draft()']
1075
1074
1076 prefetchrevset = ui.config('remotefilelog', 'pullprefetch', None)
1075 prefetchrevset = ui.config('remotefilelog', 'pullprefetch', None)
1077 if prefetchrevset:
1076 if prefetchrevset:
1078 revset.append('(%s)' % prefetchrevset)
1077 revset.append('(%s)' % prefetchrevset)
1079 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs', None)
1078 bgprefetchrevs = ui.config('remotefilelog', 'bgprefetchrevs', None)
1080 if bgprefetchrevs:
1079 if bgprefetchrevs:
1081 revset.append('(%s)' % bgprefetchrevs)
1080 revset.append('(%s)' % bgprefetchrevs)
1082 revset = '+'.join(revset)
1081 revset = '+'.join(revset)
1083
1082
1084 # update a revset with a date limit
1083 # update a revset with a date limit
1085 revset = revdatelimit(ui, revset)
1084 revset = revdatelimit(ui, revset)
1086
1085
1087 opts['rev'] = [revset]
1086 opts['rev'] = [revset]
1088
1087
1089 if not opts.get('base'):
1088 if not opts.get('base'):
1090 opts['base'] = None
1089 opts['base'] = None
1091
1090
1092 return opts
1091 return opts
1093
1092
1094 @command('prefetch', [
1093 @command('prefetch', [
1095 ('r', 'rev', [], _('prefetch the specified revisions'), _('REV')),
1094 ('r', 'rev', [], _('prefetch the specified revisions'), _('REV')),
1096 ('', 'repack', False, _('run repack after prefetch')),
1095 ('', 'repack', False, _('run repack after prefetch')),
1097 ('b', 'base', '', _("rev that is assumed to already be local")),
1096 ('b', 'base', '', _("rev that is assumed to already be local")),
1098 ] + commands.walkopts, _('hg prefetch [OPTIONS] [FILE...]'))
1097 ] + commands.walkopts, _('hg prefetch [OPTIONS] [FILE...]'))
1099 def prefetch(ui, repo, *pats, **opts):
1098 def prefetch(ui, repo, *pats, **opts):
1100 """prefetch file revisions from the server
1099 """prefetch file revisions from the server
1101
1100
1102 Prefetchs file revisions for the specified revs and stores them in the
1101 Prefetchs file revisions for the specified revs and stores them in the
1103 local remotefilelog cache. If no rev is specified, the default rev is
1102 local remotefilelog cache. If no rev is specified, the default rev is
1104 used which is the union of dot, draft, pullprefetch and bgprefetchrev.
1103 used which is the union of dot, draft, pullprefetch and bgprefetchrev.
1105 File names or patterns can be used to limit which files are downloaded.
1104 File names or patterns can be used to limit which files are downloaded.
1106
1105
1107 Return 0 on success.
1106 Return 0 on success.
1108 """
1107 """
1109 opts = pycompat.byteskwargs(opts)
1108 opts = pycompat.byteskwargs(opts)
1110 if not isenabled(repo):
1109 if not isenabled(repo):
1111 raise error.Abort(_("repo is not shallow"))
1110 raise error.Abort(_("repo is not shallow"))
1112
1111
1113 opts = resolveprefetchopts(ui, opts)
1112 opts = resolveprefetchopts(ui, opts)
1114 revs = scmutil.revrange(repo, opts.get('rev'))
1113 revs = scmutil.revrange(repo, opts.get('rev'))
1115 repo.prefetch(revs, opts.get('base'), pats, opts)
1114 repo.prefetch(revs, opts.get('base'), pats, opts)
1116
1115
1117 # Run repack in background
1116 # Run repack in background
1118 if opts.get('repack'):
1117 if opts.get('repack'):
1119 repackmod.backgroundrepack(repo, incremental=True)
1118 repackmod.backgroundrepack(repo, incremental=True)
1120
1119
1121 @command('repack', [
1120 @command('repack', [
1122 ('', 'background', None, _('run in a background process'), None),
1121 ('', 'background', None, _('run in a background process'), None),
1123 ('', 'incremental', None, _('do an incremental repack'), None),
1122 ('', 'incremental', None, _('do an incremental repack'), None),
1124 ('', 'packsonly', None, _('only repack packs (skip loose objects)'), None),
1123 ('', 'packsonly', None, _('only repack packs (skip loose objects)'), None),
1125 ], _('hg repack [OPTIONS]'))
1124 ], _('hg repack [OPTIONS]'))
1126 def repack_(ui, repo, *pats, **opts):
1125 def repack_(ui, repo, *pats, **opts):
1127 if opts.get(r'background'):
1126 if opts.get(r'background'):
1128 repackmod.backgroundrepack(repo, incremental=opts.get(r'incremental'),
1127 repackmod.backgroundrepack(repo, incremental=opts.get(r'incremental'),
1129 packsonly=opts.get(r'packsonly', False))
1128 packsonly=opts.get(r'packsonly', False))
1130 return
1129 return
1131
1130
1132 options = {'packsonly': opts.get(r'packsonly')}
1131 options = {'packsonly': opts.get(r'packsonly')}
1133
1132
1134 try:
1133 try:
1135 if opts.get(r'incremental'):
1134 if opts.get(r'incremental'):
1136 repackmod.incrementalrepack(repo, options=options)
1135 repackmod.incrementalrepack(repo, options=options)
1137 else:
1136 else:
1138 repackmod.fullrepack(repo, options=options)
1137 repackmod.fullrepack(repo, options=options)
1139 except repackmod.RepackAlreadyRunning as ex:
1138 except repackmod.RepackAlreadyRunning as ex:
1140 # Don't propogate the exception if the repack is already in
1139 # Don't propogate the exception if the repack is already in
1141 # progress, since we want the command to exit 0.
1140 # progress, since we want the command to exit 0.
1142 repo.ui.warn('%s\n' % ex)
1141 repo.ui.warn('%s\n' % ex)
@@ -1,6240 +1,6239 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from . import (
25 from . import (
26 archival,
26 archival,
27 bookmarks,
27 bookmarks,
28 bundle2,
28 bundle2,
29 changegroup,
29 changegroup,
30 cmdutil,
30 cmdutil,
31 copies,
31 copies,
32 debugcommands as debugcommandsmod,
32 debugcommands as debugcommandsmod,
33 destutil,
33 destutil,
34 dirstateguard,
34 dirstateguard,
35 discovery,
35 discovery,
36 encoding,
36 encoding,
37 error,
37 error,
38 exchange,
38 exchange,
39 extensions,
39 extensions,
40 filemerge,
40 filemerge,
41 formatter,
41 formatter,
42 graphmod,
42 graphmod,
43 hbisect,
43 hbisect,
44 help,
44 help,
45 hg,
45 hg,
46 logcmdutil,
46 logcmdutil,
47 merge as mergemod,
47 merge as mergemod,
48 narrowspec,
48 narrowspec,
49 obsolete,
49 obsolete,
50 obsutil,
50 obsutil,
51 patch,
51 patch,
52 phases,
52 phases,
53 pycompat,
53 pycompat,
54 rcutil,
54 rcutil,
55 registrar,
55 registrar,
56 repair,
56 repair,
57 revsetlang,
57 revsetlang,
58 rewriteutil,
58 rewriteutil,
59 scmutil,
59 scmutil,
60 server,
60 server,
61 state as statemod,
61 state as statemod,
62 streamclone,
62 streamclone,
63 tags as tagsmod,
63 tags as tagsmod,
64 templatekw,
65 ui as uimod,
64 ui as uimod,
66 util,
65 util,
67 wireprotoserver,
66 wireprotoserver,
68 )
67 )
69 from .utils import (
68 from .utils import (
70 dateutil,
69 dateutil,
71 stringutil,
70 stringutil,
72 )
71 )
73
72
74 table = {}
73 table = {}
75 table.update(debugcommandsmod.command._table)
74 table.update(debugcommandsmod.command._table)
76
75
77 command = registrar.command(table)
76 command = registrar.command(table)
78 INTENT_READONLY = registrar.INTENT_READONLY
77 INTENT_READONLY = registrar.INTENT_READONLY
79
78
80 # common command options
79 # common command options
81
80
82 globalopts = [
81 globalopts = [
83 ('R', 'repository', '',
82 ('R', 'repository', '',
84 _('repository root directory or name of overlay bundle file'),
83 _('repository root directory or name of overlay bundle file'),
85 _('REPO')),
84 _('REPO')),
86 ('', 'cwd', '',
85 ('', 'cwd', '',
87 _('change working directory'), _('DIR')),
86 _('change working directory'), _('DIR')),
88 ('y', 'noninteractive', None,
87 ('y', 'noninteractive', None,
89 _('do not prompt, automatically pick the first choice for all prompts')),
88 _('do not prompt, automatically pick the first choice for all prompts')),
90 ('q', 'quiet', None, _('suppress output')),
89 ('q', 'quiet', None, _('suppress output')),
91 ('v', 'verbose', None, _('enable additional output')),
90 ('v', 'verbose', None, _('enable additional output')),
92 ('', 'color', '',
91 ('', 'color', '',
93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
92 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
94 # and should not be translated
93 # and should not be translated
95 _("when to colorize (boolean, always, auto, never, or debug)"),
94 _("when to colorize (boolean, always, auto, never, or debug)"),
96 _('TYPE')),
95 _('TYPE')),
97 ('', 'config', [],
96 ('', 'config', [],
98 _('set/override config option (use \'section.name=value\')'),
97 _('set/override config option (use \'section.name=value\')'),
99 _('CONFIG')),
98 _('CONFIG')),
100 ('', 'debug', None, _('enable debugging output')),
99 ('', 'debug', None, _('enable debugging output')),
101 ('', 'debugger', None, _('start debugger')),
100 ('', 'debugger', None, _('start debugger')),
102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
101 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
103 _('ENCODE')),
102 _('ENCODE')),
104 ('', 'encodingmode', encoding.encodingmode,
103 ('', 'encodingmode', encoding.encodingmode,
105 _('set the charset encoding mode'), _('MODE')),
104 _('set the charset encoding mode'), _('MODE')),
106 ('', 'traceback', None, _('always print a traceback on exception')),
105 ('', 'traceback', None, _('always print a traceback on exception')),
107 ('', 'time', None, _('time how long the command takes')),
106 ('', 'time', None, _('time how long the command takes')),
108 ('', 'profile', None, _('print command execution profile')),
107 ('', 'profile', None, _('print command execution profile')),
109 ('', 'version', None, _('output version information and exit')),
108 ('', 'version', None, _('output version information and exit')),
110 ('h', 'help', None, _('display help and exit')),
109 ('h', 'help', None, _('display help and exit')),
111 ('', 'hidden', False, _('consider hidden changesets')),
110 ('', 'hidden', False, _('consider hidden changesets')),
112 ('', 'pager', 'auto',
111 ('', 'pager', 'auto',
113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
112 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 ]
113 ]
115
114
116 dryrunopts = cmdutil.dryrunopts
115 dryrunopts = cmdutil.dryrunopts
117 remoteopts = cmdutil.remoteopts
116 remoteopts = cmdutil.remoteopts
118 walkopts = cmdutil.walkopts
117 walkopts = cmdutil.walkopts
119 commitopts = cmdutil.commitopts
118 commitopts = cmdutil.commitopts
120 commitopts2 = cmdutil.commitopts2
119 commitopts2 = cmdutil.commitopts2
121 formatteropts = cmdutil.formatteropts
120 formatteropts = cmdutil.formatteropts
122 templateopts = cmdutil.templateopts
121 templateopts = cmdutil.templateopts
123 logopts = cmdutil.logopts
122 logopts = cmdutil.logopts
124 diffopts = cmdutil.diffopts
123 diffopts = cmdutil.diffopts
125 diffwsopts = cmdutil.diffwsopts
124 diffwsopts = cmdutil.diffwsopts
126 diffopts2 = cmdutil.diffopts2
125 diffopts2 = cmdutil.diffopts2
127 mergetoolopts = cmdutil.mergetoolopts
126 mergetoolopts = cmdutil.mergetoolopts
128 similarityopts = cmdutil.similarityopts
127 similarityopts = cmdutil.similarityopts
129 subrepoopts = cmdutil.subrepoopts
128 subrepoopts = cmdutil.subrepoopts
130 debugrevlogopts = cmdutil.debugrevlogopts
129 debugrevlogopts = cmdutil.debugrevlogopts
131
130
132 # Commands start here, listed alphabetically
131 # Commands start here, listed alphabetically
133
132
134 @command('add',
133 @command('add',
135 walkopts + subrepoopts + dryrunopts,
134 walkopts + subrepoopts + dryrunopts,
136 _('[OPTION]... [FILE]...'),
135 _('[OPTION]... [FILE]...'),
137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
136 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
138 helpbasic=True, inferrepo=True)
137 helpbasic=True, inferrepo=True)
139 def add(ui, repo, *pats, **opts):
138 def add(ui, repo, *pats, **opts):
140 """add the specified files on the next commit
139 """add the specified files on the next commit
141
140
142 Schedule files to be version controlled and added to the
141 Schedule files to be version controlled and added to the
143 repository.
142 repository.
144
143
145 The files will be added to the repository at the next commit. To
144 The files will be added to the repository at the next commit. To
146 undo an add before that, see :hg:`forget`.
145 undo an add before that, see :hg:`forget`.
147
146
148 If no names are given, add all files to the repository (except
147 If no names are given, add all files to the repository (except
149 files matching ``.hgignore``).
148 files matching ``.hgignore``).
150
149
151 .. container:: verbose
150 .. container:: verbose
152
151
153 Examples:
152 Examples:
154
153
155 - New (unknown) files are added
154 - New (unknown) files are added
156 automatically by :hg:`add`::
155 automatically by :hg:`add`::
157
156
158 $ ls
157 $ ls
159 foo.c
158 foo.c
160 $ hg status
159 $ hg status
161 ? foo.c
160 ? foo.c
162 $ hg add
161 $ hg add
163 adding foo.c
162 adding foo.c
164 $ hg status
163 $ hg status
165 A foo.c
164 A foo.c
166
165
167 - Specific files to be added can be specified::
166 - Specific files to be added can be specified::
168
167
169 $ ls
168 $ ls
170 bar.c foo.c
169 bar.c foo.c
171 $ hg status
170 $ hg status
172 ? bar.c
171 ? bar.c
173 ? foo.c
172 ? foo.c
174 $ hg add bar.c
173 $ hg add bar.c
175 $ hg status
174 $ hg status
176 A bar.c
175 A bar.c
177 ? foo.c
176 ? foo.c
178
177
179 Returns 0 if all files are successfully added.
178 Returns 0 if all files are successfully added.
180 """
179 """
181
180
182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
181 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
183 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
182 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
184 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
183 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
185 return rejected and 1 or 0
184 return rejected and 1 or 0
186
185
187 @command('addremove',
186 @command('addremove',
188 similarityopts + subrepoopts + walkopts + dryrunopts,
187 similarityopts + subrepoopts + walkopts + dryrunopts,
189 _('[OPTION]... [FILE]...'),
188 _('[OPTION]... [FILE]...'),
190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
189 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
191 inferrepo=True)
190 inferrepo=True)
192 def addremove(ui, repo, *pats, **opts):
191 def addremove(ui, repo, *pats, **opts):
193 """add all new files, delete all missing files
192 """add all new files, delete all missing files
194
193
195 Add all new files and remove all missing files from the
194 Add all new files and remove all missing files from the
196 repository.
195 repository.
197
196
198 Unless names are given, new files are ignored if they match any of
197 Unless names are given, new files are ignored if they match any of
199 the patterns in ``.hgignore``. As with add, these changes take
198 the patterns in ``.hgignore``. As with add, these changes take
200 effect at the next commit.
199 effect at the next commit.
201
200
202 Use the -s/--similarity option to detect renamed files. This
201 Use the -s/--similarity option to detect renamed files. This
203 option takes a percentage between 0 (disabled) and 100 (files must
202 option takes a percentage between 0 (disabled) and 100 (files must
204 be identical) as its parameter. With a parameter greater than 0,
203 be identical) as its parameter. With a parameter greater than 0,
205 this compares every removed file with every added file and records
204 this compares every removed file with every added file and records
206 those similar enough as renames. Detecting renamed files this way
205 those similar enough as renames. Detecting renamed files this way
207 can be expensive. After using this option, :hg:`status -C` can be
206 can be expensive. After using this option, :hg:`status -C` can be
208 used to check which files were identified as moved or renamed. If
207 used to check which files were identified as moved or renamed. If
209 not specified, -s/--similarity defaults to 100 and only renames of
208 not specified, -s/--similarity defaults to 100 and only renames of
210 identical files are detected.
209 identical files are detected.
211
210
212 .. container:: verbose
211 .. container:: verbose
213
212
214 Examples:
213 Examples:
215
214
216 - A number of files (bar.c and foo.c) are new,
215 - A number of files (bar.c and foo.c) are new,
217 while foobar.c has been removed (without using :hg:`remove`)
216 while foobar.c has been removed (without using :hg:`remove`)
218 from the repository::
217 from the repository::
219
218
220 $ ls
219 $ ls
221 bar.c foo.c
220 bar.c foo.c
222 $ hg status
221 $ hg status
223 ! foobar.c
222 ! foobar.c
224 ? bar.c
223 ? bar.c
225 ? foo.c
224 ? foo.c
226 $ hg addremove
225 $ hg addremove
227 adding bar.c
226 adding bar.c
228 adding foo.c
227 adding foo.c
229 removing foobar.c
228 removing foobar.c
230 $ hg status
229 $ hg status
231 A bar.c
230 A bar.c
232 A foo.c
231 A foo.c
233 R foobar.c
232 R foobar.c
234
233
235 - A file foobar.c was moved to foo.c without using :hg:`rename`.
234 - A file foobar.c was moved to foo.c without using :hg:`rename`.
236 Afterwards, it was edited slightly::
235 Afterwards, it was edited slightly::
237
236
238 $ ls
237 $ ls
239 foo.c
238 foo.c
240 $ hg status
239 $ hg status
241 ! foobar.c
240 ! foobar.c
242 ? foo.c
241 ? foo.c
243 $ hg addremove --similarity 90
242 $ hg addremove --similarity 90
244 removing foobar.c
243 removing foobar.c
245 adding foo.c
244 adding foo.c
246 recording removal of foobar.c as rename to foo.c (94% similar)
245 recording removal of foobar.c as rename to foo.c (94% similar)
247 $ hg status -C
246 $ hg status -C
248 A foo.c
247 A foo.c
249 foobar.c
248 foobar.c
250 R foobar.c
249 R foobar.c
251
250
252 Returns 0 if all files are successfully added.
251 Returns 0 if all files are successfully added.
253 """
252 """
254 opts = pycompat.byteskwargs(opts)
253 opts = pycompat.byteskwargs(opts)
255 if not opts.get('similarity'):
254 if not opts.get('similarity'):
256 opts['similarity'] = '100'
255 opts['similarity'] = '100'
257 matcher = scmutil.match(repo[None], pats, opts)
256 matcher = scmutil.match(repo[None], pats, opts)
258 relative = scmutil.anypats(pats, opts)
257 relative = scmutil.anypats(pats, opts)
259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
260 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
259 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
261
260
262 @command('annotate|blame',
261 @command('annotate|blame',
263 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
262 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
264 ('', 'follow', None,
263 ('', 'follow', None,
265 _('follow copies/renames and list the filename (DEPRECATED)')),
264 _('follow copies/renames and list the filename (DEPRECATED)')),
266 ('', 'no-follow', None, _("don't follow copies and renames")),
265 ('', 'no-follow', None, _("don't follow copies and renames")),
267 ('a', 'text', None, _('treat all files as text')),
266 ('a', 'text', None, _('treat all files as text')),
268 ('u', 'user', None, _('list the author (long with -v)')),
267 ('u', 'user', None, _('list the author (long with -v)')),
269 ('f', 'file', None, _('list the filename')),
268 ('f', 'file', None, _('list the filename')),
270 ('d', 'date', None, _('list the date (short with -q)')),
269 ('d', 'date', None, _('list the date (short with -q)')),
271 ('n', 'number', None, _('list the revision number (default)')),
270 ('n', 'number', None, _('list the revision number (default)')),
272 ('c', 'changeset', None, _('list the changeset')),
271 ('c', 'changeset', None, _('list the changeset')),
273 ('l', 'line-number', None, _('show line number at the first appearance')),
272 ('l', 'line-number', None, _('show line number at the first appearance')),
274 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
273 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
275 ] + diffwsopts + walkopts + formatteropts,
274 ] + diffwsopts + walkopts + formatteropts,
276 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
275 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
277 helpcategory=command.CATEGORY_FILE_CONTENTS,
276 helpcategory=command.CATEGORY_FILE_CONTENTS,
278 helpbasic=True, inferrepo=True)
277 helpbasic=True, inferrepo=True)
279 def annotate(ui, repo, *pats, **opts):
278 def annotate(ui, repo, *pats, **opts):
280 """show changeset information by line for each file
279 """show changeset information by line for each file
281
280
282 List changes in files, showing the revision id responsible for
281 List changes in files, showing the revision id responsible for
283 each line.
282 each line.
284
283
285 This command is useful for discovering when a change was made and
284 This command is useful for discovering when a change was made and
286 by whom.
285 by whom.
287
286
288 If you include --file, --user, or --date, the revision number is
287 If you include --file, --user, or --date, the revision number is
289 suppressed unless you also include --number.
288 suppressed unless you also include --number.
290
289
291 Without the -a/--text option, annotate will avoid processing files
290 Without the -a/--text option, annotate will avoid processing files
292 it detects as binary. With -a, annotate will annotate the file
291 it detects as binary. With -a, annotate will annotate the file
293 anyway, although the results will probably be neither useful
292 anyway, although the results will probably be neither useful
294 nor desirable.
293 nor desirable.
295
294
296 .. container:: verbose
295 .. container:: verbose
297
296
298 Template:
297 Template:
299
298
300 The following keywords are supported in addition to the common template
299 The following keywords are supported in addition to the common template
301 keywords and functions. See also :hg:`help templates`.
300 keywords and functions. See also :hg:`help templates`.
302
301
303 :lines: List of lines with annotation data.
302 :lines: List of lines with annotation data.
304 :path: String. Repository-absolute path of the specified file.
303 :path: String. Repository-absolute path of the specified file.
305
304
306 And each entry of ``{lines}`` provides the following sub-keywords in
305 And each entry of ``{lines}`` provides the following sub-keywords in
307 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
306 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
308
307
309 :line: String. Line content.
308 :line: String. Line content.
310 :lineno: Integer. Line number at that revision.
309 :lineno: Integer. Line number at that revision.
311 :path: String. Repository-absolute path of the file at that revision.
310 :path: String. Repository-absolute path of the file at that revision.
312
311
313 See :hg:`help templates.operators` for the list expansion syntax.
312 See :hg:`help templates.operators` for the list expansion syntax.
314
313
315 Returns 0 on success.
314 Returns 0 on success.
316 """
315 """
317 opts = pycompat.byteskwargs(opts)
316 opts = pycompat.byteskwargs(opts)
318 if not pats:
317 if not pats:
319 raise error.Abort(_('at least one filename or pattern is required'))
318 raise error.Abort(_('at least one filename or pattern is required'))
320
319
321 if opts.get('follow'):
320 if opts.get('follow'):
322 # --follow is deprecated and now just an alias for -f/--file
321 # --follow is deprecated and now just an alias for -f/--file
323 # to mimic the behavior of Mercurial before version 1.5
322 # to mimic the behavior of Mercurial before version 1.5
324 opts['file'] = True
323 opts['file'] = True
325
324
326 if (not opts.get('user') and not opts.get('changeset')
325 if (not opts.get('user') and not opts.get('changeset')
327 and not opts.get('date') and not opts.get('file')):
326 and not opts.get('date') and not opts.get('file')):
328 opts['number'] = True
327 opts['number'] = True
329
328
330 linenumber = opts.get('line_number') is not None
329 linenumber = opts.get('line_number') is not None
331 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
330 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
332 raise error.Abort(_('at least one of -n/-c is required for -l'))
331 raise error.Abort(_('at least one of -n/-c is required for -l'))
333
332
334 rev = opts.get('rev')
333 rev = opts.get('rev')
335 if rev:
334 if rev:
336 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
335 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
337 ctx = scmutil.revsingle(repo, rev)
336 ctx = scmutil.revsingle(repo, rev)
338
337
339 ui.pager('annotate')
338 ui.pager('annotate')
340 rootfm = ui.formatter('annotate', opts)
339 rootfm = ui.formatter('annotate', opts)
341 if ui.debugflag:
340 if ui.debugflag:
342 shorthex = pycompat.identity
341 shorthex = pycompat.identity
343 else:
342 else:
344 def shorthex(h):
343 def shorthex(h):
345 return h[:12]
344 return h[:12]
346 if ui.quiet:
345 if ui.quiet:
347 datefunc = dateutil.shortdate
346 datefunc = dateutil.shortdate
348 else:
347 else:
349 datefunc = dateutil.datestr
348 datefunc = dateutil.datestr
350 if ctx.rev() is None:
349 if ctx.rev() is None:
351 if opts.get('changeset'):
350 if opts.get('changeset'):
352 # omit "+" suffix which is appended to node hex
351 # omit "+" suffix which is appended to node hex
353 def formatrev(rev):
352 def formatrev(rev):
354 if rev == wdirrev:
353 if rev == wdirrev:
355 return '%d' % ctx.p1().rev()
354 return '%d' % ctx.p1().rev()
356 else:
355 else:
357 return '%d' % rev
356 return '%d' % rev
358 else:
357 else:
359 def formatrev(rev):
358 def formatrev(rev):
360 if rev == wdirrev:
359 if rev == wdirrev:
361 return '%d+' % ctx.p1().rev()
360 return '%d+' % ctx.p1().rev()
362 else:
361 else:
363 return '%d ' % rev
362 return '%d ' % rev
364 def formathex(h):
363 def formathex(h):
365 if h == wdirhex:
364 if h == wdirhex:
366 return '%s+' % shorthex(hex(ctx.p1().node()))
365 return '%s+' % shorthex(hex(ctx.p1().node()))
367 else:
366 else:
368 return '%s ' % shorthex(h)
367 return '%s ' % shorthex(h)
369 else:
368 else:
370 formatrev = b'%d'.__mod__
369 formatrev = b'%d'.__mod__
371 formathex = shorthex
370 formathex = shorthex
372
371
373 opmap = [
372 opmap = [
374 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
373 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
375 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
374 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
376 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
375 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
377 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
376 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
378 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
377 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
379 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
378 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
380 ]
379 ]
381 opnamemap = {
380 opnamemap = {
382 'rev': 'number',
381 'rev': 'number',
383 'node': 'changeset',
382 'node': 'changeset',
384 'path': 'file',
383 'path': 'file',
385 'lineno': 'line_number',
384 'lineno': 'line_number',
386 }
385 }
387
386
388 if rootfm.isplain():
387 if rootfm.isplain():
389 def makefunc(get, fmt):
388 def makefunc(get, fmt):
390 return lambda x: fmt(get(x))
389 return lambda x: fmt(get(x))
391 else:
390 else:
392 def makefunc(get, fmt):
391 def makefunc(get, fmt):
393 return get
392 return get
394 datahint = rootfm.datahint()
393 datahint = rootfm.datahint()
395 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
394 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
396 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
395 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
397 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
396 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
398 fields = ' '.join(fn for fn, sep, get, fmt in opmap
397 fields = ' '.join(fn for fn, sep, get, fmt in opmap
399 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
398 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
400
399
401 def bad(x, y):
400 def bad(x, y):
402 raise error.Abort("%s: %s" % (x, y))
401 raise error.Abort("%s: %s" % (x, y))
403
402
404 m = scmutil.match(ctx, pats, opts, badfn=bad)
403 m = scmutil.match(ctx, pats, opts, badfn=bad)
405
404
406 follow = not opts.get('no_follow')
405 follow = not opts.get('no_follow')
407 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
406 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
408 whitespace=True)
407 whitespace=True)
409 skiprevs = opts.get('skip')
408 skiprevs = opts.get('skip')
410 if skiprevs:
409 if skiprevs:
411 skiprevs = scmutil.revrange(repo, skiprevs)
410 skiprevs = scmutil.revrange(repo, skiprevs)
412
411
413 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
412 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
414 for abs in ctx.walk(m):
413 for abs in ctx.walk(m):
415 fctx = ctx[abs]
414 fctx = ctx[abs]
416 rootfm.startitem()
415 rootfm.startitem()
417 rootfm.data(path=abs)
416 rootfm.data(path=abs)
418 if not opts.get('text') and fctx.isbinary():
417 if not opts.get('text') and fctx.isbinary():
419 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
418 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
420 continue
419 continue
421
420
422 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
421 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
423 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
422 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
424 diffopts=diffopts)
423 diffopts=diffopts)
425 if not lines:
424 if not lines:
426 fm.end()
425 fm.end()
427 continue
426 continue
428 formats = []
427 formats = []
429 pieces = []
428 pieces = []
430
429
431 for f, sep in funcmap:
430 for f, sep in funcmap:
432 l = [f(n) for n in lines]
431 l = [f(n) for n in lines]
433 if fm.isplain():
432 if fm.isplain():
434 sizes = [encoding.colwidth(x) for x in l]
433 sizes = [encoding.colwidth(x) for x in l]
435 ml = max(sizes)
434 ml = max(sizes)
436 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
435 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
437 else:
436 else:
438 formats.append(['%s' for x in l])
437 formats.append(['%s' for x in l])
439 pieces.append(l)
438 pieces.append(l)
440
439
441 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
440 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
442 fm.startitem()
441 fm.startitem()
443 fm.context(fctx=n.fctx)
442 fm.context(fctx=n.fctx)
444 fm.write(fields, "".join(f), *p)
443 fm.write(fields, "".join(f), *p)
445 if n.skip:
444 if n.skip:
446 fmt = "* %s"
445 fmt = "* %s"
447 else:
446 else:
448 fmt = ": %s"
447 fmt = ": %s"
449 fm.write('line', fmt, n.text)
448 fm.write('line', fmt, n.text)
450
449
451 if not lines[-1].text.endswith('\n'):
450 if not lines[-1].text.endswith('\n'):
452 fm.plain('\n')
451 fm.plain('\n')
453 fm.end()
452 fm.end()
454
453
455 rootfm.end()
454 rootfm.end()
456
455
457 @command('archive',
456 @command('archive',
458 [('', 'no-decode', None, _('do not pass files through decoders')),
457 [('', 'no-decode', None, _('do not pass files through decoders')),
459 ('p', 'prefix', '', _('directory prefix for files in archive'),
458 ('p', 'prefix', '', _('directory prefix for files in archive'),
460 _('PREFIX')),
459 _('PREFIX')),
461 ('r', 'rev', '', _('revision to distribute'), _('REV')),
460 ('r', 'rev', '', _('revision to distribute'), _('REV')),
462 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
461 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
463 ] + subrepoopts + walkopts,
462 ] + subrepoopts + walkopts,
464 _('[OPTION]... DEST'),
463 _('[OPTION]... DEST'),
465 helpcategory=command.CATEGORY_IMPORT_EXPORT)
464 helpcategory=command.CATEGORY_IMPORT_EXPORT)
466 def archive(ui, repo, dest, **opts):
465 def archive(ui, repo, dest, **opts):
467 '''create an unversioned archive of a repository revision
466 '''create an unversioned archive of a repository revision
468
467
469 By default, the revision used is the parent of the working
468 By default, the revision used is the parent of the working
470 directory; use -r/--rev to specify a different revision.
469 directory; use -r/--rev to specify a different revision.
471
470
472 The archive type is automatically detected based on file
471 The archive type is automatically detected based on file
473 extension (to override, use -t/--type).
472 extension (to override, use -t/--type).
474
473
475 .. container:: verbose
474 .. container:: verbose
476
475
477 Examples:
476 Examples:
478
477
479 - create a zip file containing the 1.0 release::
478 - create a zip file containing the 1.0 release::
480
479
481 hg archive -r 1.0 project-1.0.zip
480 hg archive -r 1.0 project-1.0.zip
482
481
483 - create a tarball excluding .hg files::
482 - create a tarball excluding .hg files::
484
483
485 hg archive project.tar.gz -X ".hg*"
484 hg archive project.tar.gz -X ".hg*"
486
485
487 Valid types are:
486 Valid types are:
488
487
489 :``files``: a directory full of files (default)
488 :``files``: a directory full of files (default)
490 :``tar``: tar archive, uncompressed
489 :``tar``: tar archive, uncompressed
491 :``tbz2``: tar archive, compressed using bzip2
490 :``tbz2``: tar archive, compressed using bzip2
492 :``tgz``: tar archive, compressed using gzip
491 :``tgz``: tar archive, compressed using gzip
493 :``uzip``: zip archive, uncompressed
492 :``uzip``: zip archive, uncompressed
494 :``zip``: zip archive, compressed using deflate
493 :``zip``: zip archive, compressed using deflate
495
494
496 The exact name of the destination archive or directory is given
495 The exact name of the destination archive or directory is given
497 using a format string; see :hg:`help export` for details.
496 using a format string; see :hg:`help export` for details.
498
497
499 Each member added to an archive file has a directory prefix
498 Each member added to an archive file has a directory prefix
500 prepended. Use -p/--prefix to specify a format string for the
499 prepended. Use -p/--prefix to specify a format string for the
501 prefix. The default is the basename of the archive, with suffixes
500 prefix. The default is the basename of the archive, with suffixes
502 removed.
501 removed.
503
502
504 Returns 0 on success.
503 Returns 0 on success.
505 '''
504 '''
506
505
507 opts = pycompat.byteskwargs(opts)
506 opts = pycompat.byteskwargs(opts)
508 rev = opts.get('rev')
507 rev = opts.get('rev')
509 if rev:
508 if rev:
510 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
509 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
511 ctx = scmutil.revsingle(repo, rev)
510 ctx = scmutil.revsingle(repo, rev)
512 if not ctx:
511 if not ctx:
513 raise error.Abort(_('no working directory: please specify a revision'))
512 raise error.Abort(_('no working directory: please specify a revision'))
514 node = ctx.node()
513 node = ctx.node()
515 dest = cmdutil.makefilename(ctx, dest)
514 dest = cmdutil.makefilename(ctx, dest)
516 if os.path.realpath(dest) == repo.root:
515 if os.path.realpath(dest) == repo.root:
517 raise error.Abort(_('repository root cannot be destination'))
516 raise error.Abort(_('repository root cannot be destination'))
518
517
519 kind = opts.get('type') or archival.guesskind(dest) or 'files'
518 kind = opts.get('type') or archival.guesskind(dest) or 'files'
520 prefix = opts.get('prefix')
519 prefix = opts.get('prefix')
521
520
522 if dest == '-':
521 if dest == '-':
523 if kind == 'files':
522 if kind == 'files':
524 raise error.Abort(_('cannot archive plain files to stdout'))
523 raise error.Abort(_('cannot archive plain files to stdout'))
525 dest = cmdutil.makefileobj(ctx, dest)
524 dest = cmdutil.makefileobj(ctx, dest)
526 if not prefix:
525 if not prefix:
527 prefix = os.path.basename(repo.root) + '-%h'
526 prefix = os.path.basename(repo.root) + '-%h'
528
527
529 prefix = cmdutil.makefilename(ctx, prefix)
528 prefix = cmdutil.makefilename(ctx, prefix)
530 match = scmutil.match(ctx, [], opts)
529 match = scmutil.match(ctx, [], opts)
531 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
530 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
532 match, prefix, subrepos=opts.get('subrepos'))
531 match, prefix, subrepos=opts.get('subrepos'))
533
532
534 @command('backout',
533 @command('backout',
535 [('', 'merge', None, _('merge with old dirstate parent after backout')),
534 [('', 'merge', None, _('merge with old dirstate parent after backout')),
536 ('', 'commit', None,
535 ('', 'commit', None,
537 _('commit if no conflicts were encountered (DEPRECATED)')),
536 _('commit if no conflicts were encountered (DEPRECATED)')),
538 ('', 'no-commit', None, _('do not commit')),
537 ('', 'no-commit', None, _('do not commit')),
539 ('', 'parent', '',
538 ('', 'parent', '',
540 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
539 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
541 ('r', 'rev', '', _('revision to backout'), _('REV')),
540 ('r', 'rev', '', _('revision to backout'), _('REV')),
542 ('e', 'edit', False, _('invoke editor on commit messages')),
541 ('e', 'edit', False, _('invoke editor on commit messages')),
543 ] + mergetoolopts + walkopts + commitopts + commitopts2,
542 ] + mergetoolopts + walkopts + commitopts + commitopts2,
544 _('[OPTION]... [-r] REV'),
543 _('[OPTION]... [-r] REV'),
545 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
544 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
546 def backout(ui, repo, node=None, rev=None, **opts):
545 def backout(ui, repo, node=None, rev=None, **opts):
547 '''reverse effect of earlier changeset
546 '''reverse effect of earlier changeset
548
547
549 Prepare a new changeset with the effect of REV undone in the
548 Prepare a new changeset with the effect of REV undone in the
550 current working directory. If no conflicts were encountered,
549 current working directory. If no conflicts were encountered,
551 it will be committed immediately.
550 it will be committed immediately.
552
551
553 If REV is the parent of the working directory, then this new changeset
552 If REV is the parent of the working directory, then this new changeset
554 is committed automatically (unless --no-commit is specified).
553 is committed automatically (unless --no-commit is specified).
555
554
556 .. note::
555 .. note::
557
556
558 :hg:`backout` cannot be used to fix either an unwanted or
557 :hg:`backout` cannot be used to fix either an unwanted or
559 incorrect merge.
558 incorrect merge.
560
559
561 .. container:: verbose
560 .. container:: verbose
562
561
563 Examples:
562 Examples:
564
563
565 - Reverse the effect of the parent of the working directory.
564 - Reverse the effect of the parent of the working directory.
566 This backout will be committed immediately::
565 This backout will be committed immediately::
567
566
568 hg backout -r .
567 hg backout -r .
569
568
570 - Reverse the effect of previous bad revision 23::
569 - Reverse the effect of previous bad revision 23::
571
570
572 hg backout -r 23
571 hg backout -r 23
573
572
574 - Reverse the effect of previous bad revision 23 and
573 - Reverse the effect of previous bad revision 23 and
575 leave changes uncommitted::
574 leave changes uncommitted::
576
575
577 hg backout -r 23 --no-commit
576 hg backout -r 23 --no-commit
578 hg commit -m "Backout revision 23"
577 hg commit -m "Backout revision 23"
579
578
580 By default, the pending changeset will have one parent,
579 By default, the pending changeset will have one parent,
581 maintaining a linear history. With --merge, the pending
580 maintaining a linear history. With --merge, the pending
582 changeset will instead have two parents: the old parent of the
581 changeset will instead have two parents: the old parent of the
583 working directory and a new child of REV that simply undoes REV.
582 working directory and a new child of REV that simply undoes REV.
584
583
585 Before version 1.7, the behavior without --merge was equivalent
584 Before version 1.7, the behavior without --merge was equivalent
586 to specifying --merge followed by :hg:`update --clean .` to
585 to specifying --merge followed by :hg:`update --clean .` to
587 cancel the merge and leave the child of REV as a head to be
586 cancel the merge and leave the child of REV as a head to be
588 merged separately.
587 merged separately.
589
588
590 See :hg:`help dates` for a list of formats valid for -d/--date.
589 See :hg:`help dates` for a list of formats valid for -d/--date.
591
590
592 See :hg:`help revert` for a way to restore files to the state
591 See :hg:`help revert` for a way to restore files to the state
593 of another revision.
592 of another revision.
594
593
595 Returns 0 on success, 1 if nothing to backout or there are unresolved
594 Returns 0 on success, 1 if nothing to backout or there are unresolved
596 files.
595 files.
597 '''
596 '''
598 with repo.wlock(), repo.lock():
597 with repo.wlock(), repo.lock():
599 return _dobackout(ui, repo, node, rev, **opts)
598 return _dobackout(ui, repo, node, rev, **opts)
600
599
601 def _dobackout(ui, repo, node=None, rev=None, **opts):
600 def _dobackout(ui, repo, node=None, rev=None, **opts):
602 opts = pycompat.byteskwargs(opts)
601 opts = pycompat.byteskwargs(opts)
603 if opts.get('commit') and opts.get('no_commit'):
602 if opts.get('commit') and opts.get('no_commit'):
604 raise error.Abort(_("cannot use --commit with --no-commit"))
603 raise error.Abort(_("cannot use --commit with --no-commit"))
605 if opts.get('merge') and opts.get('no_commit'):
604 if opts.get('merge') and opts.get('no_commit'):
606 raise error.Abort(_("cannot use --merge with --no-commit"))
605 raise error.Abort(_("cannot use --merge with --no-commit"))
607
606
608 if rev and node:
607 if rev and node:
609 raise error.Abort(_("please specify just one revision"))
608 raise error.Abort(_("please specify just one revision"))
610
609
611 if not rev:
610 if not rev:
612 rev = node
611 rev = node
613
612
614 if not rev:
613 if not rev:
615 raise error.Abort(_("please specify a revision to backout"))
614 raise error.Abort(_("please specify a revision to backout"))
616
615
617 date = opts.get('date')
616 date = opts.get('date')
618 if date:
617 if date:
619 opts['date'] = dateutil.parsedate(date)
618 opts['date'] = dateutil.parsedate(date)
620
619
621 cmdutil.checkunfinished(repo)
620 cmdutil.checkunfinished(repo)
622 cmdutil.bailifchanged(repo)
621 cmdutil.bailifchanged(repo)
623 node = scmutil.revsingle(repo, rev).node()
622 node = scmutil.revsingle(repo, rev).node()
624
623
625 op1, op2 = repo.dirstate.parents()
624 op1, op2 = repo.dirstate.parents()
626 if not repo.changelog.isancestor(node, op1):
625 if not repo.changelog.isancestor(node, op1):
627 raise error.Abort(_('cannot backout change that is not an ancestor'))
626 raise error.Abort(_('cannot backout change that is not an ancestor'))
628
627
629 p1, p2 = repo.changelog.parents(node)
628 p1, p2 = repo.changelog.parents(node)
630 if p1 == nullid:
629 if p1 == nullid:
631 raise error.Abort(_('cannot backout a change with no parents'))
630 raise error.Abort(_('cannot backout a change with no parents'))
632 if p2 != nullid:
631 if p2 != nullid:
633 if not opts.get('parent'):
632 if not opts.get('parent'):
634 raise error.Abort(_('cannot backout a merge changeset'))
633 raise error.Abort(_('cannot backout a merge changeset'))
635 p = repo.lookup(opts['parent'])
634 p = repo.lookup(opts['parent'])
636 if p not in (p1, p2):
635 if p not in (p1, p2):
637 raise error.Abort(_('%s is not a parent of %s') %
636 raise error.Abort(_('%s is not a parent of %s') %
638 (short(p), short(node)))
637 (short(p), short(node)))
639 parent = p
638 parent = p
640 else:
639 else:
641 if opts.get('parent'):
640 if opts.get('parent'):
642 raise error.Abort(_('cannot use --parent on non-merge changeset'))
641 raise error.Abort(_('cannot use --parent on non-merge changeset'))
643 parent = p1
642 parent = p1
644
643
645 # the backout should appear on the same branch
644 # the backout should appear on the same branch
646 branch = repo.dirstate.branch()
645 branch = repo.dirstate.branch()
647 bheads = repo.branchheads(branch)
646 bheads = repo.branchheads(branch)
648 rctx = scmutil.revsingle(repo, hex(parent))
647 rctx = scmutil.revsingle(repo, hex(parent))
649 if not opts.get('merge') and op1 != node:
648 if not opts.get('merge') and op1 != node:
650 with dirstateguard.dirstateguard(repo, 'backout'):
649 with dirstateguard.dirstateguard(repo, 'backout'):
651 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
650 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
652 with ui.configoverride(overrides, 'backout'):
651 with ui.configoverride(overrides, 'backout'):
653 stats = mergemod.update(repo, parent, branchmerge=True,
652 stats = mergemod.update(repo, parent, branchmerge=True,
654 force=True, ancestor=node,
653 force=True, ancestor=node,
655 mergeancestor=False)
654 mergeancestor=False)
656 repo.setparents(op1, op2)
655 repo.setparents(op1, op2)
657 hg._showstats(repo, stats)
656 hg._showstats(repo, stats)
658 if stats.unresolvedcount:
657 if stats.unresolvedcount:
659 repo.ui.status(_("use 'hg resolve' to retry unresolved "
658 repo.ui.status(_("use 'hg resolve' to retry unresolved "
660 "file merges\n"))
659 "file merges\n"))
661 return 1
660 return 1
662 else:
661 else:
663 hg.clean(repo, node, show_stats=False)
662 hg.clean(repo, node, show_stats=False)
664 repo.dirstate.setbranch(branch)
663 repo.dirstate.setbranch(branch)
665 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
664 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
666
665
667 if opts.get('no_commit'):
666 if opts.get('no_commit'):
668 msg = _("changeset %s backed out, "
667 msg = _("changeset %s backed out, "
669 "don't forget to commit.\n")
668 "don't forget to commit.\n")
670 ui.status(msg % short(node))
669 ui.status(msg % short(node))
671 return 0
670 return 0
672
671
673 def commitfunc(ui, repo, message, match, opts):
672 def commitfunc(ui, repo, message, match, opts):
674 editform = 'backout'
673 editform = 'backout'
675 e = cmdutil.getcommiteditor(editform=editform,
674 e = cmdutil.getcommiteditor(editform=editform,
676 **pycompat.strkwargs(opts))
675 **pycompat.strkwargs(opts))
677 if not message:
676 if not message:
678 # we don't translate commit messages
677 # we don't translate commit messages
679 message = "Backed out changeset %s" % short(node)
678 message = "Backed out changeset %s" % short(node)
680 e = cmdutil.getcommiteditor(edit=True, editform=editform)
679 e = cmdutil.getcommiteditor(edit=True, editform=editform)
681 return repo.commit(message, opts.get('user'), opts.get('date'),
680 return repo.commit(message, opts.get('user'), opts.get('date'),
682 match, editor=e)
681 match, editor=e)
683 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
682 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
684 if not newnode:
683 if not newnode:
685 ui.status(_("nothing changed\n"))
684 ui.status(_("nothing changed\n"))
686 return 1
685 return 1
687 cmdutil.commitstatus(repo, newnode, branch, bheads)
686 cmdutil.commitstatus(repo, newnode, branch, bheads)
688
687
689 def nice(node):
688 def nice(node):
690 return '%d:%s' % (repo.changelog.rev(node), short(node))
689 return '%d:%s' % (repo.changelog.rev(node), short(node))
691 ui.status(_('changeset %s backs out changeset %s\n') %
690 ui.status(_('changeset %s backs out changeset %s\n') %
692 (nice(repo.changelog.tip()), nice(node)))
691 (nice(repo.changelog.tip()), nice(node)))
693 if opts.get('merge') and op1 != node:
692 if opts.get('merge') and op1 != node:
694 hg.clean(repo, op1, show_stats=False)
693 hg.clean(repo, op1, show_stats=False)
695 ui.status(_('merging with changeset %s\n')
694 ui.status(_('merging with changeset %s\n')
696 % nice(repo.changelog.tip()))
695 % nice(repo.changelog.tip()))
697 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
696 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
698 with ui.configoverride(overrides, 'backout'):
697 with ui.configoverride(overrides, 'backout'):
699 return hg.merge(repo, hex(repo.changelog.tip()))
698 return hg.merge(repo, hex(repo.changelog.tip()))
700 return 0
699 return 0
701
700
702 @command('bisect',
701 @command('bisect',
703 [('r', 'reset', False, _('reset bisect state')),
702 [('r', 'reset', False, _('reset bisect state')),
704 ('g', 'good', False, _('mark changeset good')),
703 ('g', 'good', False, _('mark changeset good')),
705 ('b', 'bad', False, _('mark changeset bad')),
704 ('b', 'bad', False, _('mark changeset bad')),
706 ('s', 'skip', False, _('skip testing changeset')),
705 ('s', 'skip', False, _('skip testing changeset')),
707 ('e', 'extend', False, _('extend the bisect range')),
706 ('e', 'extend', False, _('extend the bisect range')),
708 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
707 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
709 ('U', 'noupdate', False, _('do not update to target'))],
708 ('U', 'noupdate', False, _('do not update to target'))],
710 _("[-gbsr] [-U] [-c CMD] [REV]"),
709 _("[-gbsr] [-U] [-c CMD] [REV]"),
711 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
710 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
712 def bisect(ui, repo, rev=None, extra=None, command=None,
711 def bisect(ui, repo, rev=None, extra=None, command=None,
713 reset=None, good=None, bad=None, skip=None, extend=None,
712 reset=None, good=None, bad=None, skip=None, extend=None,
714 noupdate=None):
713 noupdate=None):
715 """subdivision search of changesets
714 """subdivision search of changesets
716
715
717 This command helps to find changesets which introduce problems. To
716 This command helps to find changesets which introduce problems. To
718 use, mark the earliest changeset you know exhibits the problem as
717 use, mark the earliest changeset you know exhibits the problem as
719 bad, then mark the latest changeset which is free from the problem
718 bad, then mark the latest changeset which is free from the problem
720 as good. Bisect will update your working directory to a revision
719 as good. Bisect will update your working directory to a revision
721 for testing (unless the -U/--noupdate option is specified). Once
720 for testing (unless the -U/--noupdate option is specified). Once
722 you have performed tests, mark the working directory as good or
721 you have performed tests, mark the working directory as good or
723 bad, and bisect will either update to another candidate changeset
722 bad, and bisect will either update to another candidate changeset
724 or announce that it has found the bad revision.
723 or announce that it has found the bad revision.
725
724
726 As a shortcut, you can also use the revision argument to mark a
725 As a shortcut, you can also use the revision argument to mark a
727 revision as good or bad without checking it out first.
726 revision as good or bad without checking it out first.
728
727
729 If you supply a command, it will be used for automatic bisection.
728 If you supply a command, it will be used for automatic bisection.
730 The environment variable HG_NODE will contain the ID of the
729 The environment variable HG_NODE will contain the ID of the
731 changeset being tested. The exit status of the command will be
730 changeset being tested. The exit status of the command will be
732 used to mark revisions as good or bad: status 0 means good, 125
731 used to mark revisions as good or bad: status 0 means good, 125
733 means to skip the revision, 127 (command not found) will abort the
732 means to skip the revision, 127 (command not found) will abort the
734 bisection, and any other non-zero exit status means the revision
733 bisection, and any other non-zero exit status means the revision
735 is bad.
734 is bad.
736
735
737 .. container:: verbose
736 .. container:: verbose
738
737
739 Some examples:
738 Some examples:
740
739
741 - start a bisection with known bad revision 34, and good revision 12::
740 - start a bisection with known bad revision 34, and good revision 12::
742
741
743 hg bisect --bad 34
742 hg bisect --bad 34
744 hg bisect --good 12
743 hg bisect --good 12
745
744
746 - advance the current bisection by marking current revision as good or
745 - advance the current bisection by marking current revision as good or
747 bad::
746 bad::
748
747
749 hg bisect --good
748 hg bisect --good
750 hg bisect --bad
749 hg bisect --bad
751
750
752 - mark the current revision, or a known revision, to be skipped (e.g. if
751 - mark the current revision, or a known revision, to be skipped (e.g. if
753 that revision is not usable because of another issue)::
752 that revision is not usable because of another issue)::
754
753
755 hg bisect --skip
754 hg bisect --skip
756 hg bisect --skip 23
755 hg bisect --skip 23
757
756
758 - skip all revisions that do not touch directories ``foo`` or ``bar``::
757 - skip all revisions that do not touch directories ``foo`` or ``bar``::
759
758
760 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
759 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
761
760
762 - forget the current bisection::
761 - forget the current bisection::
763
762
764 hg bisect --reset
763 hg bisect --reset
765
764
766 - use 'make && make tests' to automatically find the first broken
765 - use 'make && make tests' to automatically find the first broken
767 revision::
766 revision::
768
767
769 hg bisect --reset
768 hg bisect --reset
770 hg bisect --bad 34
769 hg bisect --bad 34
771 hg bisect --good 12
770 hg bisect --good 12
772 hg bisect --command "make && make tests"
771 hg bisect --command "make && make tests"
773
772
774 - see all changesets whose states are already known in the current
773 - see all changesets whose states are already known in the current
775 bisection::
774 bisection::
776
775
777 hg log -r "bisect(pruned)"
776 hg log -r "bisect(pruned)"
778
777
779 - see the changeset currently being bisected (especially useful
778 - see the changeset currently being bisected (especially useful
780 if running with -U/--noupdate)::
779 if running with -U/--noupdate)::
781
780
782 hg log -r "bisect(current)"
781 hg log -r "bisect(current)"
783
782
784 - see all changesets that took part in the current bisection::
783 - see all changesets that took part in the current bisection::
785
784
786 hg log -r "bisect(range)"
785 hg log -r "bisect(range)"
787
786
788 - you can even get a nice graph::
787 - you can even get a nice graph::
789
788
790 hg log --graph -r "bisect(range)"
789 hg log --graph -r "bisect(range)"
791
790
792 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
791 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
793
792
794 Returns 0 on success.
793 Returns 0 on success.
795 """
794 """
796 # backward compatibility
795 # backward compatibility
797 if rev in "good bad reset init".split():
796 if rev in "good bad reset init".split():
798 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
797 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
799 cmd, rev, extra = rev, extra, None
798 cmd, rev, extra = rev, extra, None
800 if cmd == "good":
799 if cmd == "good":
801 good = True
800 good = True
802 elif cmd == "bad":
801 elif cmd == "bad":
803 bad = True
802 bad = True
804 else:
803 else:
805 reset = True
804 reset = True
806 elif extra:
805 elif extra:
807 raise error.Abort(_('incompatible arguments'))
806 raise error.Abort(_('incompatible arguments'))
808
807
809 incompatibles = {
808 incompatibles = {
810 '--bad': bad,
809 '--bad': bad,
811 '--command': bool(command),
810 '--command': bool(command),
812 '--extend': extend,
811 '--extend': extend,
813 '--good': good,
812 '--good': good,
814 '--reset': reset,
813 '--reset': reset,
815 '--skip': skip,
814 '--skip': skip,
816 }
815 }
817
816
818 enabled = [x for x in incompatibles if incompatibles[x]]
817 enabled = [x for x in incompatibles if incompatibles[x]]
819
818
820 if len(enabled) > 1:
819 if len(enabled) > 1:
821 raise error.Abort(_('%s and %s are incompatible') %
820 raise error.Abort(_('%s and %s are incompatible') %
822 tuple(sorted(enabled)[0:2]))
821 tuple(sorted(enabled)[0:2]))
823
822
824 if reset:
823 if reset:
825 hbisect.resetstate(repo)
824 hbisect.resetstate(repo)
826 return
825 return
827
826
828 state = hbisect.load_state(repo)
827 state = hbisect.load_state(repo)
829
828
830 # update state
829 # update state
831 if good or bad or skip:
830 if good or bad or skip:
832 if rev:
831 if rev:
833 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
832 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
834 else:
833 else:
835 nodes = [repo.lookup('.')]
834 nodes = [repo.lookup('.')]
836 if good:
835 if good:
837 state['good'] += nodes
836 state['good'] += nodes
838 elif bad:
837 elif bad:
839 state['bad'] += nodes
838 state['bad'] += nodes
840 elif skip:
839 elif skip:
841 state['skip'] += nodes
840 state['skip'] += nodes
842 hbisect.save_state(repo, state)
841 hbisect.save_state(repo, state)
843 if not (state['good'] and state['bad']):
842 if not (state['good'] and state['bad']):
844 return
843 return
845
844
846 def mayupdate(repo, node, show_stats=True):
845 def mayupdate(repo, node, show_stats=True):
847 """common used update sequence"""
846 """common used update sequence"""
848 if noupdate:
847 if noupdate:
849 return
848 return
850 cmdutil.checkunfinished(repo)
849 cmdutil.checkunfinished(repo)
851 cmdutil.bailifchanged(repo)
850 cmdutil.bailifchanged(repo)
852 return hg.clean(repo, node, show_stats=show_stats)
851 return hg.clean(repo, node, show_stats=show_stats)
853
852
854 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
853 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
855
854
856 if command:
855 if command:
857 changesets = 1
856 changesets = 1
858 if noupdate:
857 if noupdate:
859 try:
858 try:
860 node = state['current'][0]
859 node = state['current'][0]
861 except LookupError:
860 except LookupError:
862 raise error.Abort(_('current bisect revision is unknown - '
861 raise error.Abort(_('current bisect revision is unknown - '
863 'start a new bisect to fix'))
862 'start a new bisect to fix'))
864 else:
863 else:
865 node, p2 = repo.dirstate.parents()
864 node, p2 = repo.dirstate.parents()
866 if p2 != nullid:
865 if p2 != nullid:
867 raise error.Abort(_('current bisect revision is a merge'))
866 raise error.Abort(_('current bisect revision is a merge'))
868 if rev:
867 if rev:
869 node = repo[scmutil.revsingle(repo, rev, node)].node()
868 node = repo[scmutil.revsingle(repo, rev, node)].node()
870 try:
869 try:
871 while changesets:
870 while changesets:
872 # update state
871 # update state
873 state['current'] = [node]
872 state['current'] = [node]
874 hbisect.save_state(repo, state)
873 hbisect.save_state(repo, state)
875 status = ui.system(command, environ={'HG_NODE': hex(node)},
874 status = ui.system(command, environ={'HG_NODE': hex(node)},
876 blockedtag='bisect_check')
875 blockedtag='bisect_check')
877 if status == 125:
876 if status == 125:
878 transition = "skip"
877 transition = "skip"
879 elif status == 0:
878 elif status == 0:
880 transition = "good"
879 transition = "good"
881 # status < 0 means process was killed
880 # status < 0 means process was killed
882 elif status == 127:
881 elif status == 127:
883 raise error.Abort(_("failed to execute %s") % command)
882 raise error.Abort(_("failed to execute %s") % command)
884 elif status < 0:
883 elif status < 0:
885 raise error.Abort(_("%s killed") % command)
884 raise error.Abort(_("%s killed") % command)
886 else:
885 else:
887 transition = "bad"
886 transition = "bad"
888 state[transition].append(node)
887 state[transition].append(node)
889 ctx = repo[node]
888 ctx = repo[node]
890 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
889 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
891 transition))
890 transition))
892 hbisect.checkstate(state)
891 hbisect.checkstate(state)
893 # bisect
892 # bisect
894 nodes, changesets, bgood = hbisect.bisect(repo, state)
893 nodes, changesets, bgood = hbisect.bisect(repo, state)
895 # update to next check
894 # update to next check
896 node = nodes[0]
895 node = nodes[0]
897 mayupdate(repo, node, show_stats=False)
896 mayupdate(repo, node, show_stats=False)
898 finally:
897 finally:
899 state['current'] = [node]
898 state['current'] = [node]
900 hbisect.save_state(repo, state)
899 hbisect.save_state(repo, state)
901 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
900 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
902 return
901 return
903
902
904 hbisect.checkstate(state)
903 hbisect.checkstate(state)
905
904
906 # actually bisect
905 # actually bisect
907 nodes, changesets, good = hbisect.bisect(repo, state)
906 nodes, changesets, good = hbisect.bisect(repo, state)
908 if extend:
907 if extend:
909 if not changesets:
908 if not changesets:
910 extendnode = hbisect.extendrange(repo, state, nodes, good)
909 extendnode = hbisect.extendrange(repo, state, nodes, good)
911 if extendnode is not None:
910 if extendnode is not None:
912 ui.write(_("Extending search to changeset %d:%s\n")
911 ui.write(_("Extending search to changeset %d:%s\n")
913 % (extendnode.rev(), extendnode))
912 % (extendnode.rev(), extendnode))
914 state['current'] = [extendnode.node()]
913 state['current'] = [extendnode.node()]
915 hbisect.save_state(repo, state)
914 hbisect.save_state(repo, state)
916 return mayupdate(repo, extendnode.node())
915 return mayupdate(repo, extendnode.node())
917 raise error.Abort(_("nothing to extend"))
916 raise error.Abort(_("nothing to extend"))
918
917
919 if changesets == 0:
918 if changesets == 0:
920 hbisect.printresult(ui, repo, state, displayer, nodes, good)
919 hbisect.printresult(ui, repo, state, displayer, nodes, good)
921 else:
920 else:
922 assert len(nodes) == 1 # only a single node can be tested next
921 assert len(nodes) == 1 # only a single node can be tested next
923 node = nodes[0]
922 node = nodes[0]
924 # compute the approximate number of remaining tests
923 # compute the approximate number of remaining tests
925 tests, size = 0, 2
924 tests, size = 0, 2
926 while size <= changesets:
925 while size <= changesets:
927 tests, size = tests + 1, size * 2
926 tests, size = tests + 1, size * 2
928 rev = repo.changelog.rev(node)
927 rev = repo.changelog.rev(node)
929 ui.write(_("Testing changeset %d:%s "
928 ui.write(_("Testing changeset %d:%s "
930 "(%d changesets remaining, ~%d tests)\n")
929 "(%d changesets remaining, ~%d tests)\n")
931 % (rev, short(node), changesets, tests))
930 % (rev, short(node), changesets, tests))
932 state['current'] = [node]
931 state['current'] = [node]
933 hbisect.save_state(repo, state)
932 hbisect.save_state(repo, state)
934 return mayupdate(repo, node)
933 return mayupdate(repo, node)
935
934
936 @command('bookmarks|bookmark',
935 @command('bookmarks|bookmark',
937 [('f', 'force', False, _('force')),
936 [('f', 'force', False, _('force')),
938 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
937 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
939 ('d', 'delete', False, _('delete a given bookmark')),
938 ('d', 'delete', False, _('delete a given bookmark')),
940 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
939 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
941 ('i', 'inactive', False, _('mark a bookmark inactive')),
940 ('i', 'inactive', False, _('mark a bookmark inactive')),
942 ('l', 'list', False, _('list existing bookmarks')),
941 ('l', 'list', False, _('list existing bookmarks')),
943 ] + formatteropts,
942 ] + formatteropts,
944 _('hg bookmarks [OPTIONS]... [NAME]...'),
943 _('hg bookmarks [OPTIONS]... [NAME]...'),
945 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
944 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
946 def bookmark(ui, repo, *names, **opts):
945 def bookmark(ui, repo, *names, **opts):
947 '''create a new bookmark or list existing bookmarks
946 '''create a new bookmark or list existing bookmarks
948
947
949 Bookmarks are labels on changesets to help track lines of development.
948 Bookmarks are labels on changesets to help track lines of development.
950 Bookmarks are unversioned and can be moved, renamed and deleted.
949 Bookmarks are unversioned and can be moved, renamed and deleted.
951 Deleting or moving a bookmark has no effect on the associated changesets.
950 Deleting or moving a bookmark has no effect on the associated changesets.
952
951
953 Creating or updating to a bookmark causes it to be marked as 'active'.
952 Creating or updating to a bookmark causes it to be marked as 'active'.
954 The active bookmark is indicated with a '*'.
953 The active bookmark is indicated with a '*'.
955 When a commit is made, the active bookmark will advance to the new commit.
954 When a commit is made, the active bookmark will advance to the new commit.
956 A plain :hg:`update` will also advance an active bookmark, if possible.
955 A plain :hg:`update` will also advance an active bookmark, if possible.
957 Updating away from a bookmark will cause it to be deactivated.
956 Updating away from a bookmark will cause it to be deactivated.
958
957
959 Bookmarks can be pushed and pulled between repositories (see
958 Bookmarks can be pushed and pulled between repositories (see
960 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
959 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
961 diverged, a new 'divergent bookmark' of the form 'name@path' will
960 diverged, a new 'divergent bookmark' of the form 'name@path' will
962 be created. Using :hg:`merge` will resolve the divergence.
961 be created. Using :hg:`merge` will resolve the divergence.
963
962
964 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
963 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
965 the active bookmark's name.
964 the active bookmark's name.
966
965
967 A bookmark named '@' has the special property that :hg:`clone` will
966 A bookmark named '@' has the special property that :hg:`clone` will
968 check it out by default if it exists.
967 check it out by default if it exists.
969
968
970 .. container:: verbose
969 .. container:: verbose
971
970
972 Template:
971 Template:
973
972
974 The following keywords are supported in addition to the common template
973 The following keywords are supported in addition to the common template
975 keywords and functions such as ``{bookmark}``. See also
974 keywords and functions such as ``{bookmark}``. See also
976 :hg:`help templates`.
975 :hg:`help templates`.
977
976
978 :active: Boolean. True if the bookmark is active.
977 :active: Boolean. True if the bookmark is active.
979
978
980 Examples:
979 Examples:
981
980
982 - create an active bookmark for a new line of development::
981 - create an active bookmark for a new line of development::
983
982
984 hg book new-feature
983 hg book new-feature
985
984
986 - create an inactive bookmark as a place marker::
985 - create an inactive bookmark as a place marker::
987
986
988 hg book -i reviewed
987 hg book -i reviewed
989
988
990 - create an inactive bookmark on another changeset::
989 - create an inactive bookmark on another changeset::
991
990
992 hg book -r .^ tested
991 hg book -r .^ tested
993
992
994 - rename bookmark turkey to dinner::
993 - rename bookmark turkey to dinner::
995
994
996 hg book -m turkey dinner
995 hg book -m turkey dinner
997
996
998 - move the '@' bookmark from another branch::
997 - move the '@' bookmark from another branch::
999
998
1000 hg book -f @
999 hg book -f @
1001
1000
1002 - print only the active bookmark name::
1001 - print only the active bookmark name::
1003
1002
1004 hg book -ql .
1003 hg book -ql .
1005 '''
1004 '''
1006 opts = pycompat.byteskwargs(opts)
1005 opts = pycompat.byteskwargs(opts)
1007 force = opts.get('force')
1006 force = opts.get('force')
1008 rev = opts.get('rev')
1007 rev = opts.get('rev')
1009 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1008 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1010
1009
1011 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1010 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1012 if len(selactions) > 1:
1011 if len(selactions) > 1:
1013 raise error.Abort(_('--%s and --%s are incompatible')
1012 raise error.Abort(_('--%s and --%s are incompatible')
1014 % tuple(selactions[:2]))
1013 % tuple(selactions[:2]))
1015 if selactions:
1014 if selactions:
1016 action = selactions[0]
1015 action = selactions[0]
1017 elif names or rev:
1016 elif names or rev:
1018 action = 'add'
1017 action = 'add'
1019 elif inactive:
1018 elif inactive:
1020 action = 'inactive' # meaning deactivate
1019 action = 'inactive' # meaning deactivate
1021 else:
1020 else:
1022 action = 'list'
1021 action = 'list'
1023
1022
1024 if rev and action in {'delete', 'rename', 'list'}:
1023 if rev and action in {'delete', 'rename', 'list'}:
1025 raise error.Abort(_("--rev is incompatible with --%s") % action)
1024 raise error.Abort(_("--rev is incompatible with --%s") % action)
1026 if inactive and action in {'delete', 'list'}:
1025 if inactive and action in {'delete', 'list'}:
1027 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1026 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1028 if not names and action in {'add', 'delete'}:
1027 if not names and action in {'add', 'delete'}:
1029 raise error.Abort(_("bookmark name required"))
1028 raise error.Abort(_("bookmark name required"))
1030
1029
1031 if action in {'add', 'delete', 'rename', 'inactive'}:
1030 if action in {'add', 'delete', 'rename', 'inactive'}:
1032 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1031 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1033 if action == 'delete':
1032 if action == 'delete':
1034 names = pycompat.maplist(repo._bookmarks.expandname, names)
1033 names = pycompat.maplist(repo._bookmarks.expandname, names)
1035 bookmarks.delete(repo, tr, names)
1034 bookmarks.delete(repo, tr, names)
1036 elif action == 'rename':
1035 elif action == 'rename':
1037 if not names:
1036 if not names:
1038 raise error.Abort(_("new bookmark name required"))
1037 raise error.Abort(_("new bookmark name required"))
1039 elif len(names) > 1:
1038 elif len(names) > 1:
1040 raise error.Abort(_("only one new bookmark name allowed"))
1039 raise error.Abort(_("only one new bookmark name allowed"))
1041 oldname = repo._bookmarks.expandname(opts['rename'])
1040 oldname = repo._bookmarks.expandname(opts['rename'])
1042 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1041 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1043 elif action == 'add':
1042 elif action == 'add':
1044 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1043 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1045 elif action == 'inactive':
1044 elif action == 'inactive':
1046 if len(repo._bookmarks) == 0:
1045 if len(repo._bookmarks) == 0:
1047 ui.status(_("no bookmarks set\n"))
1046 ui.status(_("no bookmarks set\n"))
1048 elif not repo._activebookmark:
1047 elif not repo._activebookmark:
1049 ui.status(_("no active bookmark\n"))
1048 ui.status(_("no active bookmark\n"))
1050 else:
1049 else:
1051 bookmarks.deactivate(repo)
1050 bookmarks.deactivate(repo)
1052 elif action == 'list':
1051 elif action == 'list':
1053 names = pycompat.maplist(repo._bookmarks.expandname, names)
1052 names = pycompat.maplist(repo._bookmarks.expandname, names)
1054 with ui.formatter('bookmarks', opts) as fm:
1053 with ui.formatter('bookmarks', opts) as fm:
1055 bookmarks.printbookmarks(ui, repo, fm, names)
1054 bookmarks.printbookmarks(ui, repo, fm, names)
1056 else:
1055 else:
1057 raise error.ProgrammingError('invalid action: %s' % action)
1056 raise error.ProgrammingError('invalid action: %s' % action)
1058
1057
1059 @command('branch',
1058 @command('branch',
1060 [('f', 'force', None,
1059 [('f', 'force', None,
1061 _('set branch name even if it shadows an existing branch')),
1060 _('set branch name even if it shadows an existing branch')),
1062 ('C', 'clean', None, _('reset branch name to parent branch name')),
1061 ('C', 'clean', None, _('reset branch name to parent branch name')),
1063 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1062 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1064 ],
1063 ],
1065 _('[-fC] [NAME]'),
1064 _('[-fC] [NAME]'),
1066 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1065 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1067 def branch(ui, repo, label=None, **opts):
1066 def branch(ui, repo, label=None, **opts):
1068 """set or show the current branch name
1067 """set or show the current branch name
1069
1068
1070 .. note::
1069 .. note::
1071
1070
1072 Branch names are permanent and global. Use :hg:`bookmark` to create a
1071 Branch names are permanent and global. Use :hg:`bookmark` to create a
1073 light-weight bookmark instead. See :hg:`help glossary` for more
1072 light-weight bookmark instead. See :hg:`help glossary` for more
1074 information about named branches and bookmarks.
1073 information about named branches and bookmarks.
1075
1074
1076 With no argument, show the current branch name. With one argument,
1075 With no argument, show the current branch name. With one argument,
1077 set the working directory branch name (the branch will not exist
1076 set the working directory branch name (the branch will not exist
1078 in the repository until the next commit). Standard practice
1077 in the repository until the next commit). Standard practice
1079 recommends that primary development take place on the 'default'
1078 recommends that primary development take place on the 'default'
1080 branch.
1079 branch.
1081
1080
1082 Unless -f/--force is specified, branch will not let you set a
1081 Unless -f/--force is specified, branch will not let you set a
1083 branch name that already exists.
1082 branch name that already exists.
1084
1083
1085 Use -C/--clean to reset the working directory branch to that of
1084 Use -C/--clean to reset the working directory branch to that of
1086 the parent of the working directory, negating a previous branch
1085 the parent of the working directory, negating a previous branch
1087 change.
1086 change.
1088
1087
1089 Use the command :hg:`update` to switch to an existing branch. Use
1088 Use the command :hg:`update` to switch to an existing branch. Use
1090 :hg:`commit --close-branch` to mark this branch head as closed.
1089 :hg:`commit --close-branch` to mark this branch head as closed.
1091 When all heads of a branch are closed, the branch will be
1090 When all heads of a branch are closed, the branch will be
1092 considered closed.
1091 considered closed.
1093
1092
1094 Returns 0 on success.
1093 Returns 0 on success.
1095 """
1094 """
1096 opts = pycompat.byteskwargs(opts)
1095 opts = pycompat.byteskwargs(opts)
1097 revs = opts.get('rev')
1096 revs = opts.get('rev')
1098 if label:
1097 if label:
1099 label = label.strip()
1098 label = label.strip()
1100
1099
1101 if not opts.get('clean') and not label:
1100 if not opts.get('clean') and not label:
1102 if revs:
1101 if revs:
1103 raise error.Abort(_("no branch name specified for the revisions"))
1102 raise error.Abort(_("no branch name specified for the revisions"))
1104 ui.write("%s\n" % repo.dirstate.branch())
1103 ui.write("%s\n" % repo.dirstate.branch())
1105 return
1104 return
1106
1105
1107 with repo.wlock():
1106 with repo.wlock():
1108 if opts.get('clean'):
1107 if opts.get('clean'):
1109 label = repo['.'].branch()
1108 label = repo['.'].branch()
1110 repo.dirstate.setbranch(label)
1109 repo.dirstate.setbranch(label)
1111 ui.status(_('reset working directory to branch %s\n') % label)
1110 ui.status(_('reset working directory to branch %s\n') % label)
1112 elif label:
1111 elif label:
1113
1112
1114 scmutil.checknewlabel(repo, label, 'branch')
1113 scmutil.checknewlabel(repo, label, 'branch')
1115 if revs:
1114 if revs:
1116 return cmdutil.changebranch(ui, repo, revs, label)
1115 return cmdutil.changebranch(ui, repo, revs, label)
1117
1116
1118 if not opts.get('force') and label in repo.branchmap():
1117 if not opts.get('force') and label in repo.branchmap():
1119 if label not in [p.branch() for p in repo[None].parents()]:
1118 if label not in [p.branch() for p in repo[None].parents()]:
1120 raise error.Abort(_('a branch of the same name already'
1119 raise error.Abort(_('a branch of the same name already'
1121 ' exists'),
1120 ' exists'),
1122 # i18n: "it" refers to an existing branch
1121 # i18n: "it" refers to an existing branch
1123 hint=_("use 'hg update' to switch to it"))
1122 hint=_("use 'hg update' to switch to it"))
1124
1123
1125 repo.dirstate.setbranch(label)
1124 repo.dirstate.setbranch(label)
1126 ui.status(_('marked working directory as branch %s\n') % label)
1125 ui.status(_('marked working directory as branch %s\n') % label)
1127
1126
1128 # find any open named branches aside from default
1127 # find any open named branches aside from default
1129 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1128 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1130 if n != "default" and not c]
1129 if n != "default" and not c]
1131 if not others:
1130 if not others:
1132 ui.status(_('(branches are permanent and global, '
1131 ui.status(_('(branches are permanent and global, '
1133 'did you want a bookmark?)\n'))
1132 'did you want a bookmark?)\n'))
1134
1133
1135 @command('branches',
1134 @command('branches',
1136 [('a', 'active', False,
1135 [('a', 'active', False,
1137 _('show only branches that have unmerged heads (DEPRECATED)')),
1136 _('show only branches that have unmerged heads (DEPRECATED)')),
1138 ('c', 'closed', False, _('show normal and closed branches')),
1137 ('c', 'closed', False, _('show normal and closed branches')),
1139 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1138 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1140 ] + formatteropts,
1139 ] + formatteropts,
1141 _('[-c]'),
1140 _('[-c]'),
1142 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1141 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1143 intents={INTENT_READONLY})
1142 intents={INTENT_READONLY})
1144 def branches(ui, repo, active=False, closed=False, **opts):
1143 def branches(ui, repo, active=False, closed=False, **opts):
1145 """list repository named branches
1144 """list repository named branches
1146
1145
1147 List the repository's named branches, indicating which ones are
1146 List the repository's named branches, indicating which ones are
1148 inactive. If -c/--closed is specified, also list branches which have
1147 inactive. If -c/--closed is specified, also list branches which have
1149 been marked closed (see :hg:`commit --close-branch`).
1148 been marked closed (see :hg:`commit --close-branch`).
1150
1149
1151 Use the command :hg:`update` to switch to an existing branch.
1150 Use the command :hg:`update` to switch to an existing branch.
1152
1151
1153 .. container:: verbose
1152 .. container:: verbose
1154
1153
1155 Template:
1154 Template:
1156
1155
1157 The following keywords are supported in addition to the common template
1156 The following keywords are supported in addition to the common template
1158 keywords and functions such as ``{branch}``. See also
1157 keywords and functions such as ``{branch}``. See also
1159 :hg:`help templates`.
1158 :hg:`help templates`.
1160
1159
1161 :active: Boolean. True if the branch is active.
1160 :active: Boolean. True if the branch is active.
1162 :closed: Boolean. True if the branch is closed.
1161 :closed: Boolean. True if the branch is closed.
1163 :current: Boolean. True if it is the current branch.
1162 :current: Boolean. True if it is the current branch.
1164
1163
1165 Returns 0.
1164 Returns 0.
1166 """
1165 """
1167
1166
1168 opts = pycompat.byteskwargs(opts)
1167 opts = pycompat.byteskwargs(opts)
1169 revs = opts.get('rev')
1168 revs = opts.get('rev')
1170 selectedbranches = None
1169 selectedbranches = None
1171 if revs:
1170 if revs:
1172 revs = scmutil.revrange(repo, revs)
1171 revs = scmutil.revrange(repo, revs)
1173 getbi = repo.revbranchcache().branchinfo
1172 getbi = repo.revbranchcache().branchinfo
1174 selectedbranches = {getbi(r)[0] for r in revs}
1173 selectedbranches = {getbi(r)[0] for r in revs}
1175
1174
1176 ui.pager('branches')
1175 ui.pager('branches')
1177 fm = ui.formatter('branches', opts)
1176 fm = ui.formatter('branches', opts)
1178 hexfunc = fm.hexfunc
1177 hexfunc = fm.hexfunc
1179
1178
1180 allheads = set(repo.heads())
1179 allheads = set(repo.heads())
1181 branches = []
1180 branches = []
1182 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1181 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1183 if selectedbranches is not None and tag not in selectedbranches:
1182 if selectedbranches is not None and tag not in selectedbranches:
1184 continue
1183 continue
1185 isactive = False
1184 isactive = False
1186 if not isclosed:
1185 if not isclosed:
1187 openheads = set(repo.branchmap().iteropen(heads))
1186 openheads = set(repo.branchmap().iteropen(heads))
1188 isactive = bool(openheads & allheads)
1187 isactive = bool(openheads & allheads)
1189 branches.append((tag, repo[tip], isactive, not isclosed))
1188 branches.append((tag, repo[tip], isactive, not isclosed))
1190 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1189 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1191 reverse=True)
1190 reverse=True)
1192
1191
1193 for tag, ctx, isactive, isopen in branches:
1192 for tag, ctx, isactive, isopen in branches:
1194 if active and not isactive:
1193 if active and not isactive:
1195 continue
1194 continue
1196 if isactive:
1195 if isactive:
1197 label = 'branches.active'
1196 label = 'branches.active'
1198 notice = ''
1197 notice = ''
1199 elif not isopen:
1198 elif not isopen:
1200 if not closed:
1199 if not closed:
1201 continue
1200 continue
1202 label = 'branches.closed'
1201 label = 'branches.closed'
1203 notice = _(' (closed)')
1202 notice = _(' (closed)')
1204 else:
1203 else:
1205 label = 'branches.inactive'
1204 label = 'branches.inactive'
1206 notice = _(' (inactive)')
1205 notice = _(' (inactive)')
1207 current = (tag == repo.dirstate.branch())
1206 current = (tag == repo.dirstate.branch())
1208 if current:
1207 if current:
1209 label = 'branches.current'
1208 label = 'branches.current'
1210
1209
1211 fm.startitem()
1210 fm.startitem()
1212 fm.write('branch', '%s', tag, label=label)
1211 fm.write('branch', '%s', tag, label=label)
1213 rev = ctx.rev()
1212 rev = ctx.rev()
1214 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1213 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1215 fmt = ' ' * padsize + ' %d:%s'
1214 fmt = ' ' * padsize + ' %d:%s'
1216 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1215 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1217 label='log.changeset changeset.%s' % ctx.phasestr())
1216 label='log.changeset changeset.%s' % ctx.phasestr())
1218 fm.context(ctx=ctx)
1217 fm.context(ctx=ctx)
1219 fm.data(active=isactive, closed=not isopen, current=current)
1218 fm.data(active=isactive, closed=not isopen, current=current)
1220 if not ui.quiet:
1219 if not ui.quiet:
1221 fm.plain(notice)
1220 fm.plain(notice)
1222 fm.plain('\n')
1221 fm.plain('\n')
1223 fm.end()
1222 fm.end()
1224
1223
1225 @command('bundle',
1224 @command('bundle',
1226 [('f', 'force', None, _('run even when the destination is unrelated')),
1225 [('f', 'force', None, _('run even when the destination is unrelated')),
1227 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1226 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1228 _('REV')),
1227 _('REV')),
1229 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1228 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1230 _('BRANCH')),
1229 _('BRANCH')),
1231 ('', 'base', [],
1230 ('', 'base', [],
1232 _('a base changeset assumed to be available at the destination'),
1231 _('a base changeset assumed to be available at the destination'),
1233 _('REV')),
1232 _('REV')),
1234 ('a', 'all', None, _('bundle all changesets in the repository')),
1233 ('a', 'all', None, _('bundle all changesets in the repository')),
1235 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1234 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1236 ] + remoteopts,
1235 ] + remoteopts,
1237 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1236 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1238 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1237 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1239 def bundle(ui, repo, fname, dest=None, **opts):
1238 def bundle(ui, repo, fname, dest=None, **opts):
1240 """create a bundle file
1239 """create a bundle file
1241
1240
1242 Generate a bundle file containing data to be transferred to another
1241 Generate a bundle file containing data to be transferred to another
1243 repository.
1242 repository.
1244
1243
1245 To create a bundle containing all changesets, use -a/--all
1244 To create a bundle containing all changesets, use -a/--all
1246 (or --base null). Otherwise, hg assumes the destination will have
1245 (or --base null). Otherwise, hg assumes the destination will have
1247 all the nodes you specify with --base parameters. Otherwise, hg
1246 all the nodes you specify with --base parameters. Otherwise, hg
1248 will assume the repository has all the nodes in destination, or
1247 will assume the repository has all the nodes in destination, or
1249 default-push/default if no destination is specified, where destination
1248 default-push/default if no destination is specified, where destination
1250 is the repository you provide through DEST option.
1249 is the repository you provide through DEST option.
1251
1250
1252 You can change bundle format with the -t/--type option. See
1251 You can change bundle format with the -t/--type option. See
1253 :hg:`help bundlespec` for documentation on this format. By default,
1252 :hg:`help bundlespec` for documentation on this format. By default,
1254 the most appropriate format is used and compression defaults to
1253 the most appropriate format is used and compression defaults to
1255 bzip2.
1254 bzip2.
1256
1255
1257 The bundle file can then be transferred using conventional means
1256 The bundle file can then be transferred using conventional means
1258 and applied to another repository with the unbundle or pull
1257 and applied to another repository with the unbundle or pull
1259 command. This is useful when direct push and pull are not
1258 command. This is useful when direct push and pull are not
1260 available or when exporting an entire repository is undesirable.
1259 available or when exporting an entire repository is undesirable.
1261
1260
1262 Applying bundles preserves all changeset contents including
1261 Applying bundles preserves all changeset contents including
1263 permissions, copy/rename information, and revision history.
1262 permissions, copy/rename information, and revision history.
1264
1263
1265 Returns 0 on success, 1 if no changes found.
1264 Returns 0 on success, 1 if no changes found.
1266 """
1265 """
1267 opts = pycompat.byteskwargs(opts)
1266 opts = pycompat.byteskwargs(opts)
1268 revs = None
1267 revs = None
1269 if 'rev' in opts:
1268 if 'rev' in opts:
1270 revstrings = opts['rev']
1269 revstrings = opts['rev']
1271 revs = scmutil.revrange(repo, revstrings)
1270 revs = scmutil.revrange(repo, revstrings)
1272 if revstrings and not revs:
1271 if revstrings and not revs:
1273 raise error.Abort(_('no commits to bundle'))
1272 raise error.Abort(_('no commits to bundle'))
1274
1273
1275 bundletype = opts.get('type', 'bzip2').lower()
1274 bundletype = opts.get('type', 'bzip2').lower()
1276 try:
1275 try:
1277 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1276 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1278 except error.UnsupportedBundleSpecification as e:
1277 except error.UnsupportedBundleSpecification as e:
1279 raise error.Abort(pycompat.bytestr(e),
1278 raise error.Abort(pycompat.bytestr(e),
1280 hint=_("see 'hg help bundlespec' for supported "
1279 hint=_("see 'hg help bundlespec' for supported "
1281 "values for --type"))
1280 "values for --type"))
1282 cgversion = bundlespec.contentopts["cg.version"]
1281 cgversion = bundlespec.contentopts["cg.version"]
1283
1282
1284 # Packed bundles are a pseudo bundle format for now.
1283 # Packed bundles are a pseudo bundle format for now.
1285 if cgversion == 's1':
1284 if cgversion == 's1':
1286 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1285 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1287 hint=_("use 'hg debugcreatestreamclonebundle'"))
1286 hint=_("use 'hg debugcreatestreamclonebundle'"))
1288
1287
1289 if opts.get('all'):
1288 if opts.get('all'):
1290 if dest:
1289 if dest:
1291 raise error.Abort(_("--all is incompatible with specifying "
1290 raise error.Abort(_("--all is incompatible with specifying "
1292 "a destination"))
1291 "a destination"))
1293 if opts.get('base'):
1292 if opts.get('base'):
1294 ui.warn(_("ignoring --base because --all was specified\n"))
1293 ui.warn(_("ignoring --base because --all was specified\n"))
1295 base = [nullrev]
1294 base = [nullrev]
1296 else:
1295 else:
1297 base = scmutil.revrange(repo, opts.get('base'))
1296 base = scmutil.revrange(repo, opts.get('base'))
1298 if cgversion not in changegroup.supportedoutgoingversions(repo):
1297 if cgversion not in changegroup.supportedoutgoingversions(repo):
1299 raise error.Abort(_("repository does not support bundle version %s") %
1298 raise error.Abort(_("repository does not support bundle version %s") %
1300 cgversion)
1299 cgversion)
1301
1300
1302 if base:
1301 if base:
1303 if dest:
1302 if dest:
1304 raise error.Abort(_("--base is incompatible with specifying "
1303 raise error.Abort(_("--base is incompatible with specifying "
1305 "a destination"))
1304 "a destination"))
1306 common = [repo[rev].node() for rev in base]
1305 common = [repo[rev].node() for rev in base]
1307 heads = [repo[r].node() for r in revs] if revs else None
1306 heads = [repo[r].node() for r in revs] if revs else None
1308 outgoing = discovery.outgoing(repo, common, heads)
1307 outgoing = discovery.outgoing(repo, common, heads)
1309 else:
1308 else:
1310 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1309 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1311 dest, branches = hg.parseurl(dest, opts.get('branch'))
1310 dest, branches = hg.parseurl(dest, opts.get('branch'))
1312 other = hg.peer(repo, opts, dest)
1311 other = hg.peer(repo, opts, dest)
1313 revs = [repo[r].hex() for r in revs]
1312 revs = [repo[r].hex() for r in revs]
1314 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1313 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1315 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1314 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1316 outgoing = discovery.findcommonoutgoing(repo, other,
1315 outgoing = discovery.findcommonoutgoing(repo, other,
1317 onlyheads=heads,
1316 onlyheads=heads,
1318 force=opts.get('force'),
1317 force=opts.get('force'),
1319 portable=True)
1318 portable=True)
1320
1319
1321 if not outgoing.missing:
1320 if not outgoing.missing:
1322 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1321 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1323 return 1
1322 return 1
1324
1323
1325 if cgversion == '01': #bundle1
1324 if cgversion == '01': #bundle1
1326 bversion = 'HG10' + bundlespec.wirecompression
1325 bversion = 'HG10' + bundlespec.wirecompression
1327 bcompression = None
1326 bcompression = None
1328 elif cgversion in ('02', '03'):
1327 elif cgversion in ('02', '03'):
1329 bversion = 'HG20'
1328 bversion = 'HG20'
1330 bcompression = bundlespec.wirecompression
1329 bcompression = bundlespec.wirecompression
1331 else:
1330 else:
1332 raise error.ProgrammingError(
1331 raise error.ProgrammingError(
1333 'bundle: unexpected changegroup version %s' % cgversion)
1332 'bundle: unexpected changegroup version %s' % cgversion)
1334
1333
1335 # TODO compression options should be derived from bundlespec parsing.
1334 # TODO compression options should be derived from bundlespec parsing.
1336 # This is a temporary hack to allow adjusting bundle compression
1335 # This is a temporary hack to allow adjusting bundle compression
1337 # level without a) formalizing the bundlespec changes to declare it
1336 # level without a) formalizing the bundlespec changes to declare it
1338 # b) introducing a command flag.
1337 # b) introducing a command flag.
1339 compopts = {}
1338 compopts = {}
1340 complevel = ui.configint('experimental',
1339 complevel = ui.configint('experimental',
1341 'bundlecomplevel.' + bundlespec.compression)
1340 'bundlecomplevel.' + bundlespec.compression)
1342 if complevel is None:
1341 if complevel is None:
1343 complevel = ui.configint('experimental', 'bundlecomplevel')
1342 complevel = ui.configint('experimental', 'bundlecomplevel')
1344 if complevel is not None:
1343 if complevel is not None:
1345 compopts['level'] = complevel
1344 compopts['level'] = complevel
1346
1345
1347 # Allow overriding the bundling of obsmarker in phases through
1346 # Allow overriding the bundling of obsmarker in phases through
1348 # configuration while we don't have a bundle version that include them
1347 # configuration while we don't have a bundle version that include them
1349 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1348 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1350 bundlespec.contentopts['obsolescence'] = True
1349 bundlespec.contentopts['obsolescence'] = True
1351 if repo.ui.configbool('experimental', 'bundle-phases'):
1350 if repo.ui.configbool('experimental', 'bundle-phases'):
1352 bundlespec.contentopts['phases'] = True
1351 bundlespec.contentopts['phases'] = True
1353
1352
1354 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1353 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1355 bundlespec.contentopts, compression=bcompression,
1354 bundlespec.contentopts, compression=bcompression,
1356 compopts=compopts)
1355 compopts=compopts)
1357
1356
1358 @command('cat',
1357 @command('cat',
1359 [('o', 'output', '',
1358 [('o', 'output', '',
1360 _('print output to file with formatted name'), _('FORMAT')),
1359 _('print output to file with formatted name'), _('FORMAT')),
1361 ('r', 'rev', '', _('print the given revision'), _('REV')),
1360 ('r', 'rev', '', _('print the given revision'), _('REV')),
1362 ('', 'decode', None, _('apply any matching decode filter')),
1361 ('', 'decode', None, _('apply any matching decode filter')),
1363 ] + walkopts + formatteropts,
1362 ] + walkopts + formatteropts,
1364 _('[OPTION]... FILE...'),
1363 _('[OPTION]... FILE...'),
1365 helpcategory=command.CATEGORY_FILE_CONTENTS,
1364 helpcategory=command.CATEGORY_FILE_CONTENTS,
1366 inferrepo=True,
1365 inferrepo=True,
1367 intents={INTENT_READONLY})
1366 intents={INTENT_READONLY})
1368 def cat(ui, repo, file1, *pats, **opts):
1367 def cat(ui, repo, file1, *pats, **opts):
1369 """output the current or given revision of files
1368 """output the current or given revision of files
1370
1369
1371 Print the specified files as they were at the given revision. If
1370 Print the specified files as they were at the given revision. If
1372 no revision is given, the parent of the working directory is used.
1371 no revision is given, the parent of the working directory is used.
1373
1372
1374 Output may be to a file, in which case the name of the file is
1373 Output may be to a file, in which case the name of the file is
1375 given using a template string. See :hg:`help templates`. In addition
1374 given using a template string. See :hg:`help templates`. In addition
1376 to the common template keywords, the following formatting rules are
1375 to the common template keywords, the following formatting rules are
1377 supported:
1376 supported:
1378
1377
1379 :``%%``: literal "%" character
1378 :``%%``: literal "%" character
1380 :``%s``: basename of file being printed
1379 :``%s``: basename of file being printed
1381 :``%d``: dirname of file being printed, or '.' if in repository root
1380 :``%d``: dirname of file being printed, or '.' if in repository root
1382 :``%p``: root-relative path name of file being printed
1381 :``%p``: root-relative path name of file being printed
1383 :``%H``: changeset hash (40 hexadecimal digits)
1382 :``%H``: changeset hash (40 hexadecimal digits)
1384 :``%R``: changeset revision number
1383 :``%R``: changeset revision number
1385 :``%h``: short-form changeset hash (12 hexadecimal digits)
1384 :``%h``: short-form changeset hash (12 hexadecimal digits)
1386 :``%r``: zero-padded changeset revision number
1385 :``%r``: zero-padded changeset revision number
1387 :``%b``: basename of the exporting repository
1386 :``%b``: basename of the exporting repository
1388 :``\\``: literal "\\" character
1387 :``\\``: literal "\\" character
1389
1388
1390 .. container:: verbose
1389 .. container:: verbose
1391
1390
1392 Template:
1391 Template:
1393
1392
1394 The following keywords are supported in addition to the common template
1393 The following keywords are supported in addition to the common template
1395 keywords and functions. See also :hg:`help templates`.
1394 keywords and functions. See also :hg:`help templates`.
1396
1395
1397 :data: String. File content.
1396 :data: String. File content.
1398 :path: String. Repository-absolute path of the file.
1397 :path: String. Repository-absolute path of the file.
1399
1398
1400 Returns 0 on success.
1399 Returns 0 on success.
1401 """
1400 """
1402 opts = pycompat.byteskwargs(opts)
1401 opts = pycompat.byteskwargs(opts)
1403 rev = opts.get('rev')
1402 rev = opts.get('rev')
1404 if rev:
1403 if rev:
1405 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1404 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1406 ctx = scmutil.revsingle(repo, rev)
1405 ctx = scmutil.revsingle(repo, rev)
1407 m = scmutil.match(ctx, (file1,) + pats, opts)
1406 m = scmutil.match(ctx, (file1,) + pats, opts)
1408 fntemplate = opts.pop('output', '')
1407 fntemplate = opts.pop('output', '')
1409 if cmdutil.isstdiofilename(fntemplate):
1408 if cmdutil.isstdiofilename(fntemplate):
1410 fntemplate = ''
1409 fntemplate = ''
1411
1410
1412 if fntemplate:
1411 if fntemplate:
1413 fm = formatter.nullformatter(ui, 'cat', opts)
1412 fm = formatter.nullformatter(ui, 'cat', opts)
1414 else:
1413 else:
1415 ui.pager('cat')
1414 ui.pager('cat')
1416 fm = ui.formatter('cat', opts)
1415 fm = ui.formatter('cat', opts)
1417 with fm:
1416 with fm:
1418 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1417 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1419 **pycompat.strkwargs(opts))
1418 **pycompat.strkwargs(opts))
1420
1419
1421 @command('clone',
1420 @command('clone',
1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1421 [('U', 'noupdate', None, _('the clone will include an empty working '
1423 'directory (only a repository)')),
1422 'directory (only a repository)')),
1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1423 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1425 _('REV')),
1424 _('REV')),
1426 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1425 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1427 ' and its ancestors'), _('REV')),
1426 ' and its ancestors'), _('REV')),
1428 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1427 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1429 ' changesets and their ancestors'), _('BRANCH')),
1428 ' changesets and their ancestors'), _('BRANCH')),
1430 ('', 'pull', None, _('use pull protocol to copy metadata')),
1429 ('', 'pull', None, _('use pull protocol to copy metadata')),
1431 ('', 'uncompressed', None,
1430 ('', 'uncompressed', None,
1432 _('an alias to --stream (DEPRECATED)')),
1431 _('an alias to --stream (DEPRECATED)')),
1433 ('', 'stream', None,
1432 ('', 'stream', None,
1434 _('clone with minimal data processing')),
1433 _('clone with minimal data processing')),
1435 ] + remoteopts,
1434 ] + remoteopts,
1436 _('[OPTION]... SOURCE [DEST]'),
1435 _('[OPTION]... SOURCE [DEST]'),
1437 helpcategory=command.CATEGORY_REPO_CREATION,
1436 helpcategory=command.CATEGORY_REPO_CREATION,
1438 helpbasic=True, norepo=True)
1437 helpbasic=True, norepo=True)
1439 def clone(ui, source, dest=None, **opts):
1438 def clone(ui, source, dest=None, **opts):
1440 """make a copy of an existing repository
1439 """make a copy of an existing repository
1441
1440
1442 Create a copy of an existing repository in a new directory.
1441 Create a copy of an existing repository in a new directory.
1443
1442
1444 If no destination directory name is specified, it defaults to the
1443 If no destination directory name is specified, it defaults to the
1445 basename of the source.
1444 basename of the source.
1446
1445
1447 The location of the source is added to the new repository's
1446 The location of the source is added to the new repository's
1448 ``.hg/hgrc`` file, as the default to be used for future pulls.
1447 ``.hg/hgrc`` file, as the default to be used for future pulls.
1449
1448
1450 Only local paths and ``ssh://`` URLs are supported as
1449 Only local paths and ``ssh://`` URLs are supported as
1451 destinations. For ``ssh://`` destinations, no working directory or
1450 destinations. For ``ssh://`` destinations, no working directory or
1452 ``.hg/hgrc`` will be created on the remote side.
1451 ``.hg/hgrc`` will be created on the remote side.
1453
1452
1454 If the source repository has a bookmark called '@' set, that
1453 If the source repository has a bookmark called '@' set, that
1455 revision will be checked out in the new repository by default.
1454 revision will be checked out in the new repository by default.
1456
1455
1457 To check out a particular version, use -u/--update, or
1456 To check out a particular version, use -u/--update, or
1458 -U/--noupdate to create a clone with no working directory.
1457 -U/--noupdate to create a clone with no working directory.
1459
1458
1460 To pull only a subset of changesets, specify one or more revisions
1459 To pull only a subset of changesets, specify one or more revisions
1461 identifiers with -r/--rev or branches with -b/--branch. The
1460 identifiers with -r/--rev or branches with -b/--branch. The
1462 resulting clone will contain only the specified changesets and
1461 resulting clone will contain only the specified changesets and
1463 their ancestors. These options (or 'clone src#rev dest') imply
1462 their ancestors. These options (or 'clone src#rev dest') imply
1464 --pull, even for local source repositories.
1463 --pull, even for local source repositories.
1465
1464
1466 In normal clone mode, the remote normalizes repository data into a common
1465 In normal clone mode, the remote normalizes repository data into a common
1467 exchange format and the receiving end translates this data into its local
1466 exchange format and the receiving end translates this data into its local
1468 storage format. --stream activates a different clone mode that essentially
1467 storage format. --stream activates a different clone mode that essentially
1469 copies repository files from the remote with minimal data processing. This
1468 copies repository files from the remote with minimal data processing. This
1470 significantly reduces the CPU cost of a clone both remotely and locally.
1469 significantly reduces the CPU cost of a clone both remotely and locally.
1471 However, it often increases the transferred data size by 30-40%. This can
1470 However, it often increases the transferred data size by 30-40%. This can
1472 result in substantially faster clones where I/O throughput is plentiful,
1471 result in substantially faster clones where I/O throughput is plentiful,
1473 especially for larger repositories. A side-effect of --stream clones is
1472 especially for larger repositories. A side-effect of --stream clones is
1474 that storage settings and requirements on the remote are applied locally:
1473 that storage settings and requirements on the remote are applied locally:
1475 a modern client may inherit legacy or inefficient storage used by the
1474 a modern client may inherit legacy or inefficient storage used by the
1476 remote or a legacy Mercurial client may not be able to clone from a
1475 remote or a legacy Mercurial client may not be able to clone from a
1477 modern Mercurial remote.
1476 modern Mercurial remote.
1478
1477
1479 .. note::
1478 .. note::
1480
1479
1481 Specifying a tag will include the tagged changeset but not the
1480 Specifying a tag will include the tagged changeset but not the
1482 changeset containing the tag.
1481 changeset containing the tag.
1483
1482
1484 .. container:: verbose
1483 .. container:: verbose
1485
1484
1486 For efficiency, hardlinks are used for cloning whenever the
1485 For efficiency, hardlinks are used for cloning whenever the
1487 source and destination are on the same filesystem (note this
1486 source and destination are on the same filesystem (note this
1488 applies only to the repository data, not to the working
1487 applies only to the repository data, not to the working
1489 directory). Some filesystems, such as AFS, implement hardlinking
1488 directory). Some filesystems, such as AFS, implement hardlinking
1490 incorrectly, but do not report errors. In these cases, use the
1489 incorrectly, but do not report errors. In these cases, use the
1491 --pull option to avoid hardlinking.
1490 --pull option to avoid hardlinking.
1492
1491
1493 Mercurial will update the working directory to the first applicable
1492 Mercurial will update the working directory to the first applicable
1494 revision from this list:
1493 revision from this list:
1495
1494
1496 a) null if -U or the source repository has no changesets
1495 a) null if -U or the source repository has no changesets
1497 b) if -u . and the source repository is local, the first parent of
1496 b) if -u . and the source repository is local, the first parent of
1498 the source repository's working directory
1497 the source repository's working directory
1499 c) the changeset specified with -u (if a branch name, this means the
1498 c) the changeset specified with -u (if a branch name, this means the
1500 latest head of that branch)
1499 latest head of that branch)
1501 d) the changeset specified with -r
1500 d) the changeset specified with -r
1502 e) the tipmost head specified with -b
1501 e) the tipmost head specified with -b
1503 f) the tipmost head specified with the url#branch source syntax
1502 f) the tipmost head specified with the url#branch source syntax
1504 g) the revision marked with the '@' bookmark, if present
1503 g) the revision marked with the '@' bookmark, if present
1505 h) the tipmost head of the default branch
1504 h) the tipmost head of the default branch
1506 i) tip
1505 i) tip
1507
1506
1508 When cloning from servers that support it, Mercurial may fetch
1507 When cloning from servers that support it, Mercurial may fetch
1509 pre-generated data from a server-advertised URL or inline from the
1508 pre-generated data from a server-advertised URL or inline from the
1510 same stream. When this is done, hooks operating on incoming changesets
1509 same stream. When this is done, hooks operating on incoming changesets
1511 and changegroups may fire more than once, once for each pre-generated
1510 and changegroups may fire more than once, once for each pre-generated
1512 bundle and as well as for any additional remaining data. In addition,
1511 bundle and as well as for any additional remaining data. In addition,
1513 if an error occurs, the repository may be rolled back to a partial
1512 if an error occurs, the repository may be rolled back to a partial
1514 clone. This behavior may change in future releases.
1513 clone. This behavior may change in future releases.
1515 See :hg:`help -e clonebundles` for more.
1514 See :hg:`help -e clonebundles` for more.
1516
1515
1517 Examples:
1516 Examples:
1518
1517
1519 - clone a remote repository to a new directory named hg/::
1518 - clone a remote repository to a new directory named hg/::
1520
1519
1521 hg clone https://www.mercurial-scm.org/repo/hg/
1520 hg clone https://www.mercurial-scm.org/repo/hg/
1522
1521
1523 - create a lightweight local clone::
1522 - create a lightweight local clone::
1524
1523
1525 hg clone project/ project-feature/
1524 hg clone project/ project-feature/
1526
1525
1527 - clone from an absolute path on an ssh server (note double-slash)::
1526 - clone from an absolute path on an ssh server (note double-slash)::
1528
1527
1529 hg clone ssh://user@server//home/projects/alpha/
1528 hg clone ssh://user@server//home/projects/alpha/
1530
1529
1531 - do a streaming clone while checking out a specified version::
1530 - do a streaming clone while checking out a specified version::
1532
1531
1533 hg clone --stream http://server/repo -u 1.5
1532 hg clone --stream http://server/repo -u 1.5
1534
1533
1535 - create a repository without changesets after a particular revision::
1534 - create a repository without changesets after a particular revision::
1536
1535
1537 hg clone -r 04e544 experimental/ good/
1536 hg clone -r 04e544 experimental/ good/
1538
1537
1539 - clone (and track) a particular named branch::
1538 - clone (and track) a particular named branch::
1540
1539
1541 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1540 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1542
1541
1543 See :hg:`help urls` for details on specifying URLs.
1542 See :hg:`help urls` for details on specifying URLs.
1544
1543
1545 Returns 0 on success.
1544 Returns 0 on success.
1546 """
1545 """
1547 opts = pycompat.byteskwargs(opts)
1546 opts = pycompat.byteskwargs(opts)
1548 if opts.get('noupdate') and opts.get('updaterev'):
1547 if opts.get('noupdate') and opts.get('updaterev'):
1549 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1548 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1550
1549
1551 # --include/--exclude can come from narrow or sparse.
1550 # --include/--exclude can come from narrow or sparse.
1552 includepats, excludepats = None, None
1551 includepats, excludepats = None, None
1553
1552
1554 # hg.clone() differentiates between None and an empty set. So make sure
1553 # hg.clone() differentiates between None and an empty set. So make sure
1555 # patterns are sets if narrow is requested without patterns.
1554 # patterns are sets if narrow is requested without patterns.
1556 if opts.get('narrow'):
1555 if opts.get('narrow'):
1557 includepats = set()
1556 includepats = set()
1558 excludepats = set()
1557 excludepats = set()
1559
1558
1560 if opts.get('include'):
1559 if opts.get('include'):
1561 includepats = narrowspec.parsepatterns(opts.get('include'))
1560 includepats = narrowspec.parsepatterns(opts.get('include'))
1562 if opts.get('exclude'):
1561 if opts.get('exclude'):
1563 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1562 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1564
1563
1565 r = hg.clone(ui, opts, source, dest,
1564 r = hg.clone(ui, opts, source, dest,
1566 pull=opts.get('pull'),
1565 pull=opts.get('pull'),
1567 stream=opts.get('stream') or opts.get('uncompressed'),
1566 stream=opts.get('stream') or opts.get('uncompressed'),
1568 revs=opts.get('rev'),
1567 revs=opts.get('rev'),
1569 update=opts.get('updaterev') or not opts.get('noupdate'),
1568 update=opts.get('updaterev') or not opts.get('noupdate'),
1570 branch=opts.get('branch'),
1569 branch=opts.get('branch'),
1571 shareopts=opts.get('shareopts'),
1570 shareopts=opts.get('shareopts'),
1572 storeincludepats=includepats,
1571 storeincludepats=includepats,
1573 storeexcludepats=excludepats,
1572 storeexcludepats=excludepats,
1574 depth=opts.get('depth') or None)
1573 depth=opts.get('depth') or None)
1575
1574
1576 return r is None
1575 return r is None
1577
1576
1578 @command('commit|ci',
1577 @command('commit|ci',
1579 [('A', 'addremove', None,
1578 [('A', 'addremove', None,
1580 _('mark new/missing files as added/removed before committing')),
1579 _('mark new/missing files as added/removed before committing')),
1581 ('', 'close-branch', None,
1580 ('', 'close-branch', None,
1582 _('mark a branch head as closed')),
1581 _('mark a branch head as closed')),
1583 ('', 'amend', None, _('amend the parent of the working directory')),
1582 ('', 'amend', None, _('amend the parent of the working directory')),
1584 ('s', 'secret', None, _('use the secret phase for committing')),
1583 ('s', 'secret', None, _('use the secret phase for committing')),
1585 ('e', 'edit', None, _('invoke editor on commit messages')),
1584 ('e', 'edit', None, _('invoke editor on commit messages')),
1586 ('i', 'interactive', None, _('use interactive mode')),
1585 ('i', 'interactive', None, _('use interactive mode')),
1587 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1586 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1588 _('[OPTION]... [FILE]...'),
1587 _('[OPTION]... [FILE]...'),
1589 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1588 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1590 inferrepo=True)
1589 inferrepo=True)
1591 def commit(ui, repo, *pats, **opts):
1590 def commit(ui, repo, *pats, **opts):
1592 """commit the specified files or all outstanding changes
1591 """commit the specified files or all outstanding changes
1593
1592
1594 Commit changes to the given files into the repository. Unlike a
1593 Commit changes to the given files into the repository. Unlike a
1595 centralized SCM, this operation is a local operation. See
1594 centralized SCM, this operation is a local operation. See
1596 :hg:`push` for a way to actively distribute your changes.
1595 :hg:`push` for a way to actively distribute your changes.
1597
1596
1598 If a list of files is omitted, all changes reported by :hg:`status`
1597 If a list of files is omitted, all changes reported by :hg:`status`
1599 will be committed.
1598 will be committed.
1600
1599
1601 If you are committing the result of a merge, do not provide any
1600 If you are committing the result of a merge, do not provide any
1602 filenames or -I/-X filters.
1601 filenames or -I/-X filters.
1603
1602
1604 If no commit message is specified, Mercurial starts your
1603 If no commit message is specified, Mercurial starts your
1605 configured editor where you can enter a message. In case your
1604 configured editor where you can enter a message. In case your
1606 commit fails, you will find a backup of your message in
1605 commit fails, you will find a backup of your message in
1607 ``.hg/last-message.txt``.
1606 ``.hg/last-message.txt``.
1608
1607
1609 The --close-branch flag can be used to mark the current branch
1608 The --close-branch flag can be used to mark the current branch
1610 head closed. When all heads of a branch are closed, the branch
1609 head closed. When all heads of a branch are closed, the branch
1611 will be considered closed and no longer listed.
1610 will be considered closed and no longer listed.
1612
1611
1613 The --amend flag can be used to amend the parent of the
1612 The --amend flag can be used to amend the parent of the
1614 working directory with a new commit that contains the changes
1613 working directory with a new commit that contains the changes
1615 in the parent in addition to those currently reported by :hg:`status`,
1614 in the parent in addition to those currently reported by :hg:`status`,
1616 if there are any. The old commit is stored in a backup bundle in
1615 if there are any. The old commit is stored in a backup bundle in
1617 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1616 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1618 on how to restore it).
1617 on how to restore it).
1619
1618
1620 Message, user and date are taken from the amended commit unless
1619 Message, user and date are taken from the amended commit unless
1621 specified. When a message isn't specified on the command line,
1620 specified. When a message isn't specified on the command line,
1622 the editor will open with the message of the amended commit.
1621 the editor will open with the message of the amended commit.
1623
1622
1624 It is not possible to amend public changesets (see :hg:`help phases`)
1623 It is not possible to amend public changesets (see :hg:`help phases`)
1625 or changesets that have children.
1624 or changesets that have children.
1626
1625
1627 See :hg:`help dates` for a list of formats valid for -d/--date.
1626 See :hg:`help dates` for a list of formats valid for -d/--date.
1628
1627
1629 Returns 0 on success, 1 if nothing changed.
1628 Returns 0 on success, 1 if nothing changed.
1630
1629
1631 .. container:: verbose
1630 .. container:: verbose
1632
1631
1633 Examples:
1632 Examples:
1634
1633
1635 - commit all files ending in .py::
1634 - commit all files ending in .py::
1636
1635
1637 hg commit --include "set:**.py"
1636 hg commit --include "set:**.py"
1638
1637
1639 - commit all non-binary files::
1638 - commit all non-binary files::
1640
1639
1641 hg commit --exclude "set:binary()"
1640 hg commit --exclude "set:binary()"
1642
1641
1643 - amend the current commit and set the date to now::
1642 - amend the current commit and set the date to now::
1644
1643
1645 hg commit --amend --date now
1644 hg commit --amend --date now
1646 """
1645 """
1647 with repo.wlock(), repo.lock():
1646 with repo.wlock(), repo.lock():
1648 return _docommit(ui, repo, *pats, **opts)
1647 return _docommit(ui, repo, *pats, **opts)
1649
1648
1650 def _docommit(ui, repo, *pats, **opts):
1649 def _docommit(ui, repo, *pats, **opts):
1651 if opts.get(r'interactive'):
1650 if opts.get(r'interactive'):
1652 opts.pop(r'interactive')
1651 opts.pop(r'interactive')
1653 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1652 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1654 cmdutil.recordfilter, *pats,
1653 cmdutil.recordfilter, *pats,
1655 **opts)
1654 **opts)
1656 # ret can be 0 (no changes to record) or the value returned by
1655 # ret can be 0 (no changes to record) or the value returned by
1657 # commit(), 1 if nothing changed or None on success.
1656 # commit(), 1 if nothing changed or None on success.
1658 return 1 if ret == 0 else ret
1657 return 1 if ret == 0 else ret
1659
1658
1660 opts = pycompat.byteskwargs(opts)
1659 opts = pycompat.byteskwargs(opts)
1661 if opts.get('subrepos'):
1660 if opts.get('subrepos'):
1662 if opts.get('amend'):
1661 if opts.get('amend'):
1663 raise error.Abort(_('cannot amend with --subrepos'))
1662 raise error.Abort(_('cannot amend with --subrepos'))
1664 # Let --subrepos on the command line override config setting.
1663 # Let --subrepos on the command line override config setting.
1665 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1664 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1666
1665
1667 cmdutil.checkunfinished(repo, commit=True)
1666 cmdutil.checkunfinished(repo, commit=True)
1668
1667
1669 branch = repo[None].branch()
1668 branch = repo[None].branch()
1670 bheads = repo.branchheads(branch)
1669 bheads = repo.branchheads(branch)
1671
1670
1672 extra = {}
1671 extra = {}
1673 if opts.get('close_branch'):
1672 if opts.get('close_branch'):
1674 extra['close'] = '1'
1673 extra['close'] = '1'
1675
1674
1676 if not bheads:
1675 if not bheads:
1677 raise error.Abort(_('can only close branch heads'))
1676 raise error.Abort(_('can only close branch heads'))
1678 elif opts.get('amend'):
1677 elif opts.get('amend'):
1679 if (repo['.'].p1().branch() != branch and
1678 if (repo['.'].p1().branch() != branch and
1680 repo['.'].p2().branch() != branch):
1679 repo['.'].p2().branch() != branch):
1681 raise error.Abort(_('can only close branch heads'))
1680 raise error.Abort(_('can only close branch heads'))
1682
1681
1683 if opts.get('amend'):
1682 if opts.get('amend'):
1684 if ui.configbool('ui', 'commitsubrepos'):
1683 if ui.configbool('ui', 'commitsubrepos'):
1685 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1684 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1686
1685
1687 old = repo['.']
1686 old = repo['.']
1688 rewriteutil.precheck(repo, [old.rev()], 'amend')
1687 rewriteutil.precheck(repo, [old.rev()], 'amend')
1689
1688
1690 # Currently histedit gets confused if an amend happens while histedit
1689 # Currently histedit gets confused if an amend happens while histedit
1691 # is in progress. Since we have a checkunfinished command, we are
1690 # is in progress. Since we have a checkunfinished command, we are
1692 # temporarily honoring it.
1691 # temporarily honoring it.
1693 #
1692 #
1694 # Note: eventually this guard will be removed. Please do not expect
1693 # Note: eventually this guard will be removed. Please do not expect
1695 # this behavior to remain.
1694 # this behavior to remain.
1696 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1695 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1697 cmdutil.checkunfinished(repo)
1696 cmdutil.checkunfinished(repo)
1698
1697
1699 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1698 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1700 if node == old.node():
1699 if node == old.node():
1701 ui.status(_("nothing changed\n"))
1700 ui.status(_("nothing changed\n"))
1702 return 1
1701 return 1
1703 else:
1702 else:
1704 def commitfunc(ui, repo, message, match, opts):
1703 def commitfunc(ui, repo, message, match, opts):
1705 overrides = {}
1704 overrides = {}
1706 if opts.get('secret'):
1705 if opts.get('secret'):
1707 overrides[('phases', 'new-commit')] = 'secret'
1706 overrides[('phases', 'new-commit')] = 'secret'
1708
1707
1709 baseui = repo.baseui
1708 baseui = repo.baseui
1710 with baseui.configoverride(overrides, 'commit'):
1709 with baseui.configoverride(overrides, 'commit'):
1711 with ui.configoverride(overrides, 'commit'):
1710 with ui.configoverride(overrides, 'commit'):
1712 editform = cmdutil.mergeeditform(repo[None],
1711 editform = cmdutil.mergeeditform(repo[None],
1713 'commit.normal')
1712 'commit.normal')
1714 editor = cmdutil.getcommiteditor(
1713 editor = cmdutil.getcommiteditor(
1715 editform=editform, **pycompat.strkwargs(opts))
1714 editform=editform, **pycompat.strkwargs(opts))
1716 return repo.commit(message,
1715 return repo.commit(message,
1717 opts.get('user'),
1716 opts.get('user'),
1718 opts.get('date'),
1717 opts.get('date'),
1719 match,
1718 match,
1720 editor=editor,
1719 editor=editor,
1721 extra=extra)
1720 extra=extra)
1722
1721
1723 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1722 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1724
1723
1725 if not node:
1724 if not node:
1726 stat = cmdutil.postcommitstatus(repo, pats, opts)
1725 stat = cmdutil.postcommitstatus(repo, pats, opts)
1727 if stat[3]:
1726 if stat[3]:
1728 ui.status(_("nothing changed (%d missing files, see "
1727 ui.status(_("nothing changed (%d missing files, see "
1729 "'hg status')\n") % len(stat[3]))
1728 "'hg status')\n") % len(stat[3]))
1730 else:
1729 else:
1731 ui.status(_("nothing changed\n"))
1730 ui.status(_("nothing changed\n"))
1732 return 1
1731 return 1
1733
1732
1734 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1733 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1735
1734
1736 @command('config|showconfig|debugconfig',
1735 @command('config|showconfig|debugconfig',
1737 [('u', 'untrusted', None, _('show untrusted configuration options')),
1736 [('u', 'untrusted', None, _('show untrusted configuration options')),
1738 ('e', 'edit', None, _('edit user config')),
1737 ('e', 'edit', None, _('edit user config')),
1739 ('l', 'local', None, _('edit repository config')),
1738 ('l', 'local', None, _('edit repository config')),
1740 ('g', 'global', None, _('edit global config'))] + formatteropts,
1739 ('g', 'global', None, _('edit global config'))] + formatteropts,
1741 _('[-u] [NAME]...'),
1740 _('[-u] [NAME]...'),
1742 helpcategory=command.CATEGORY_HELP,
1741 helpcategory=command.CATEGORY_HELP,
1743 optionalrepo=True,
1742 optionalrepo=True,
1744 intents={INTENT_READONLY})
1743 intents={INTENT_READONLY})
1745 def config(ui, repo, *values, **opts):
1744 def config(ui, repo, *values, **opts):
1746 """show combined config settings from all hgrc files
1745 """show combined config settings from all hgrc files
1747
1746
1748 With no arguments, print names and values of all config items.
1747 With no arguments, print names and values of all config items.
1749
1748
1750 With one argument of the form section.name, print just the value
1749 With one argument of the form section.name, print just the value
1751 of that config item.
1750 of that config item.
1752
1751
1753 With multiple arguments, print names and values of all config
1752 With multiple arguments, print names and values of all config
1754 items with matching section names or section.names.
1753 items with matching section names or section.names.
1755
1754
1756 With --edit, start an editor on the user-level config file. With
1755 With --edit, start an editor on the user-level config file. With
1757 --global, edit the system-wide config file. With --local, edit the
1756 --global, edit the system-wide config file. With --local, edit the
1758 repository-level config file.
1757 repository-level config file.
1759
1758
1760 With --debug, the source (filename and line number) is printed
1759 With --debug, the source (filename and line number) is printed
1761 for each config item.
1760 for each config item.
1762
1761
1763 See :hg:`help config` for more information about config files.
1762 See :hg:`help config` for more information about config files.
1764
1763
1765 .. container:: verbose
1764 .. container:: verbose
1766
1765
1767 Template:
1766 Template:
1768
1767
1769 The following keywords are supported. See also :hg:`help templates`.
1768 The following keywords are supported. See also :hg:`help templates`.
1770
1769
1771 :name: String. Config name.
1770 :name: String. Config name.
1772 :source: String. Filename and line number where the item is defined.
1771 :source: String. Filename and line number where the item is defined.
1773 :value: String. Config value.
1772 :value: String. Config value.
1774
1773
1775 Returns 0 on success, 1 if NAME does not exist.
1774 Returns 0 on success, 1 if NAME does not exist.
1776
1775
1777 """
1776 """
1778
1777
1779 opts = pycompat.byteskwargs(opts)
1778 opts = pycompat.byteskwargs(opts)
1780 if opts.get('edit') or opts.get('local') or opts.get('global'):
1779 if opts.get('edit') or opts.get('local') or opts.get('global'):
1781 if opts.get('local') and opts.get('global'):
1780 if opts.get('local') and opts.get('global'):
1782 raise error.Abort(_("can't use --local and --global together"))
1781 raise error.Abort(_("can't use --local and --global together"))
1783
1782
1784 if opts.get('local'):
1783 if opts.get('local'):
1785 if not repo:
1784 if not repo:
1786 raise error.Abort(_("can't use --local outside a repository"))
1785 raise error.Abort(_("can't use --local outside a repository"))
1787 paths = [repo.vfs.join('hgrc')]
1786 paths = [repo.vfs.join('hgrc')]
1788 elif opts.get('global'):
1787 elif opts.get('global'):
1789 paths = rcutil.systemrcpath()
1788 paths = rcutil.systemrcpath()
1790 else:
1789 else:
1791 paths = rcutil.userrcpath()
1790 paths = rcutil.userrcpath()
1792
1791
1793 for f in paths:
1792 for f in paths:
1794 if os.path.exists(f):
1793 if os.path.exists(f):
1795 break
1794 break
1796 else:
1795 else:
1797 if opts.get('global'):
1796 if opts.get('global'):
1798 samplehgrc = uimod.samplehgrcs['global']
1797 samplehgrc = uimod.samplehgrcs['global']
1799 elif opts.get('local'):
1798 elif opts.get('local'):
1800 samplehgrc = uimod.samplehgrcs['local']
1799 samplehgrc = uimod.samplehgrcs['local']
1801 else:
1800 else:
1802 samplehgrc = uimod.samplehgrcs['user']
1801 samplehgrc = uimod.samplehgrcs['user']
1803
1802
1804 f = paths[0]
1803 f = paths[0]
1805 fp = open(f, "wb")
1804 fp = open(f, "wb")
1806 fp.write(util.tonativeeol(samplehgrc))
1805 fp.write(util.tonativeeol(samplehgrc))
1807 fp.close()
1806 fp.close()
1808
1807
1809 editor = ui.geteditor()
1808 editor = ui.geteditor()
1810 ui.system("%s \"%s\"" % (editor, f),
1809 ui.system("%s \"%s\"" % (editor, f),
1811 onerr=error.Abort, errprefix=_("edit failed"),
1810 onerr=error.Abort, errprefix=_("edit failed"),
1812 blockedtag='config_edit')
1811 blockedtag='config_edit')
1813 return
1812 return
1814 ui.pager('config')
1813 ui.pager('config')
1815 fm = ui.formatter('config', opts)
1814 fm = ui.formatter('config', opts)
1816 for t, f in rcutil.rccomponents():
1815 for t, f in rcutil.rccomponents():
1817 if t == 'path':
1816 if t == 'path':
1818 ui.debug('read config from: %s\n' % f)
1817 ui.debug('read config from: %s\n' % f)
1819 elif t == 'items':
1818 elif t == 'items':
1820 for section, name, value, source in f:
1819 for section, name, value, source in f:
1821 ui.debug('set config by: %s\n' % source)
1820 ui.debug('set config by: %s\n' % source)
1822 else:
1821 else:
1823 raise error.ProgrammingError('unknown rctype: %s' % t)
1822 raise error.ProgrammingError('unknown rctype: %s' % t)
1824 untrusted = bool(opts.get('untrusted'))
1823 untrusted = bool(opts.get('untrusted'))
1825
1824
1826 selsections = selentries = []
1825 selsections = selentries = []
1827 if values:
1826 if values:
1828 selsections = [v for v in values if '.' not in v]
1827 selsections = [v for v in values if '.' not in v]
1829 selentries = [v for v in values if '.' in v]
1828 selentries = [v for v in values if '.' in v]
1830 uniquesel = (len(selentries) == 1 and not selsections)
1829 uniquesel = (len(selentries) == 1 and not selsections)
1831 selsections = set(selsections)
1830 selsections = set(selsections)
1832 selentries = set(selentries)
1831 selentries = set(selentries)
1833
1832
1834 matched = False
1833 matched = False
1835 for section, name, value in ui.walkconfig(untrusted=untrusted):
1834 for section, name, value in ui.walkconfig(untrusted=untrusted):
1836 source = ui.configsource(section, name, untrusted)
1835 source = ui.configsource(section, name, untrusted)
1837 value = pycompat.bytestr(value)
1836 value = pycompat.bytestr(value)
1838 if fm.isplain():
1837 if fm.isplain():
1839 source = source or 'none'
1838 source = source or 'none'
1840 value = value.replace('\n', '\\n')
1839 value = value.replace('\n', '\\n')
1841 entryname = section + '.' + name
1840 entryname = section + '.' + name
1842 if values and not (section in selsections or entryname in selentries):
1841 if values and not (section in selsections or entryname in selentries):
1843 continue
1842 continue
1844 fm.startitem()
1843 fm.startitem()
1845 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1844 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1846 if uniquesel:
1845 if uniquesel:
1847 fm.data(name=entryname)
1846 fm.data(name=entryname)
1848 fm.write('value', '%s\n', value)
1847 fm.write('value', '%s\n', value)
1849 else:
1848 else:
1850 fm.write('name value', '%s=%s\n', entryname, value)
1849 fm.write('name value', '%s=%s\n', entryname, value)
1851 matched = True
1850 matched = True
1852 fm.end()
1851 fm.end()
1853 if matched:
1852 if matched:
1854 return 0
1853 return 0
1855 return 1
1854 return 1
1856
1855
1857 @command('copy|cp',
1856 @command('copy|cp',
1858 [('A', 'after', None, _('record a copy that has already occurred')),
1857 [('A', 'after', None, _('record a copy that has already occurred')),
1859 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1858 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1860 ] + walkopts + dryrunopts,
1859 ] + walkopts + dryrunopts,
1861 _('[OPTION]... [SOURCE]... DEST'),
1860 _('[OPTION]... [SOURCE]... DEST'),
1862 helpcategory=command.CATEGORY_FILE_CONTENTS)
1861 helpcategory=command.CATEGORY_FILE_CONTENTS)
1863 def copy(ui, repo, *pats, **opts):
1862 def copy(ui, repo, *pats, **opts):
1864 """mark files as copied for the next commit
1863 """mark files as copied for the next commit
1865
1864
1866 Mark dest as having copies of source files. If dest is a
1865 Mark dest as having copies of source files. If dest is a
1867 directory, copies are put in that directory. If dest is a file,
1866 directory, copies are put in that directory. If dest is a file,
1868 the source must be a single file.
1867 the source must be a single file.
1869
1868
1870 By default, this command copies the contents of files as they
1869 By default, this command copies the contents of files as they
1871 exist in the working directory. If invoked with -A/--after, the
1870 exist in the working directory. If invoked with -A/--after, the
1872 operation is recorded, but no copying is performed.
1871 operation is recorded, but no copying is performed.
1873
1872
1874 This command takes effect with the next commit. To undo a copy
1873 This command takes effect with the next commit. To undo a copy
1875 before that, see :hg:`revert`.
1874 before that, see :hg:`revert`.
1876
1875
1877 Returns 0 on success, 1 if errors are encountered.
1876 Returns 0 on success, 1 if errors are encountered.
1878 """
1877 """
1879 opts = pycompat.byteskwargs(opts)
1878 opts = pycompat.byteskwargs(opts)
1880 with repo.wlock(False):
1879 with repo.wlock(False):
1881 return cmdutil.copy(ui, repo, pats, opts)
1880 return cmdutil.copy(ui, repo, pats, opts)
1882
1881
1883 @command(
1882 @command(
1884 'debugcommands', [], _('[COMMAND]'),
1883 'debugcommands', [], _('[COMMAND]'),
1885 helpcategory=command.CATEGORY_HELP,
1884 helpcategory=command.CATEGORY_HELP,
1886 norepo=True)
1885 norepo=True)
1887 def debugcommands(ui, cmd='', *args):
1886 def debugcommands(ui, cmd='', *args):
1888 """list all available commands and options"""
1887 """list all available commands and options"""
1889 for cmd, vals in sorted(table.iteritems()):
1888 for cmd, vals in sorted(table.iteritems()):
1890 cmd = cmd.split('|')[0]
1889 cmd = cmd.split('|')[0]
1891 opts = ', '.join([i[1] for i in vals[1]])
1890 opts = ', '.join([i[1] for i in vals[1]])
1892 ui.write('%s: %s\n' % (cmd, opts))
1891 ui.write('%s: %s\n' % (cmd, opts))
1893
1892
1894 @command('debugcomplete',
1893 @command('debugcomplete',
1895 [('o', 'options', None, _('show the command options'))],
1894 [('o', 'options', None, _('show the command options'))],
1896 _('[-o] CMD'),
1895 _('[-o] CMD'),
1897 helpcategory=command.CATEGORY_HELP,
1896 helpcategory=command.CATEGORY_HELP,
1898 norepo=True)
1897 norepo=True)
1899 def debugcomplete(ui, cmd='', **opts):
1898 def debugcomplete(ui, cmd='', **opts):
1900 """returns the completion list associated with the given command"""
1899 """returns the completion list associated with the given command"""
1901
1900
1902 if opts.get(r'options'):
1901 if opts.get(r'options'):
1903 options = []
1902 options = []
1904 otables = [globalopts]
1903 otables = [globalopts]
1905 if cmd:
1904 if cmd:
1906 aliases, entry = cmdutil.findcmd(cmd, table, False)
1905 aliases, entry = cmdutil.findcmd(cmd, table, False)
1907 otables.append(entry[1])
1906 otables.append(entry[1])
1908 for t in otables:
1907 for t in otables:
1909 for o in t:
1908 for o in t:
1910 if "(DEPRECATED)" in o[3]:
1909 if "(DEPRECATED)" in o[3]:
1911 continue
1910 continue
1912 if o[0]:
1911 if o[0]:
1913 options.append('-%s' % o[0])
1912 options.append('-%s' % o[0])
1914 options.append('--%s' % o[1])
1913 options.append('--%s' % o[1])
1915 ui.write("%s\n" % "\n".join(options))
1914 ui.write("%s\n" % "\n".join(options))
1916 return
1915 return
1917
1916
1918 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1917 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1919 if ui.verbose:
1918 if ui.verbose:
1920 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1919 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1921 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1920 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1922
1921
1923 @command('diff',
1922 @command('diff',
1924 [('r', 'rev', [], _('revision'), _('REV')),
1923 [('r', 'rev', [], _('revision'), _('REV')),
1925 ('c', 'change', '', _('change made by revision'), _('REV'))
1924 ('c', 'change', '', _('change made by revision'), _('REV'))
1926 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1925 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1927 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1926 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1928 helpcategory=command.CATEGORY_FILE_CONTENTS,
1927 helpcategory=command.CATEGORY_FILE_CONTENTS,
1929 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1928 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1930 def diff(ui, repo, *pats, **opts):
1929 def diff(ui, repo, *pats, **opts):
1931 """diff repository (or selected files)
1930 """diff repository (or selected files)
1932
1931
1933 Show differences between revisions for the specified files.
1932 Show differences between revisions for the specified files.
1934
1933
1935 Differences between files are shown using the unified diff format.
1934 Differences between files are shown using the unified diff format.
1936
1935
1937 .. note::
1936 .. note::
1938
1937
1939 :hg:`diff` may generate unexpected results for merges, as it will
1938 :hg:`diff` may generate unexpected results for merges, as it will
1940 default to comparing against the working directory's first
1939 default to comparing against the working directory's first
1941 parent changeset if no revisions are specified.
1940 parent changeset if no revisions are specified.
1942
1941
1943 When two revision arguments are given, then changes are shown
1942 When two revision arguments are given, then changes are shown
1944 between those revisions. If only one revision is specified then
1943 between those revisions. If only one revision is specified then
1945 that revision is compared to the working directory, and, when no
1944 that revision is compared to the working directory, and, when no
1946 revisions are specified, the working directory files are compared
1945 revisions are specified, the working directory files are compared
1947 to its first parent.
1946 to its first parent.
1948
1947
1949 Alternatively you can specify -c/--change with a revision to see
1948 Alternatively you can specify -c/--change with a revision to see
1950 the changes in that changeset relative to its first parent.
1949 the changes in that changeset relative to its first parent.
1951
1950
1952 Without the -a/--text option, diff will avoid generating diffs of
1951 Without the -a/--text option, diff will avoid generating diffs of
1953 files it detects as binary. With -a, diff will generate a diff
1952 files it detects as binary. With -a, diff will generate a diff
1954 anyway, probably with undesirable results.
1953 anyway, probably with undesirable results.
1955
1954
1956 Use the -g/--git option to generate diffs in the git extended diff
1955 Use the -g/--git option to generate diffs in the git extended diff
1957 format. For more information, read :hg:`help diffs`.
1956 format. For more information, read :hg:`help diffs`.
1958
1957
1959 .. container:: verbose
1958 .. container:: verbose
1960
1959
1961 Examples:
1960 Examples:
1962
1961
1963 - compare a file in the current working directory to its parent::
1962 - compare a file in the current working directory to its parent::
1964
1963
1965 hg diff foo.c
1964 hg diff foo.c
1966
1965
1967 - compare two historical versions of a directory, with rename info::
1966 - compare two historical versions of a directory, with rename info::
1968
1967
1969 hg diff --git -r 1.0:1.2 lib/
1968 hg diff --git -r 1.0:1.2 lib/
1970
1969
1971 - get change stats relative to the last change on some date::
1970 - get change stats relative to the last change on some date::
1972
1971
1973 hg diff --stat -r "date('may 2')"
1972 hg diff --stat -r "date('may 2')"
1974
1973
1975 - diff all newly-added files that contain a keyword::
1974 - diff all newly-added files that contain a keyword::
1976
1975
1977 hg diff "set:added() and grep(GNU)"
1976 hg diff "set:added() and grep(GNU)"
1978
1977
1979 - compare a revision and its parents::
1978 - compare a revision and its parents::
1980
1979
1981 hg diff -c 9353 # compare against first parent
1980 hg diff -c 9353 # compare against first parent
1982 hg diff -r 9353^:9353 # same using revset syntax
1981 hg diff -r 9353^:9353 # same using revset syntax
1983 hg diff -r 9353^2:9353 # compare against the second parent
1982 hg diff -r 9353^2:9353 # compare against the second parent
1984
1983
1985 Returns 0 on success.
1984 Returns 0 on success.
1986 """
1985 """
1987
1986
1988 opts = pycompat.byteskwargs(opts)
1987 opts = pycompat.byteskwargs(opts)
1989 revs = opts.get('rev')
1988 revs = opts.get('rev')
1990 change = opts.get('change')
1989 change = opts.get('change')
1991 stat = opts.get('stat')
1990 stat = opts.get('stat')
1992 reverse = opts.get('reverse')
1991 reverse = opts.get('reverse')
1993
1992
1994 if revs and change:
1993 if revs and change:
1995 msg = _('cannot specify --rev and --change at the same time')
1994 msg = _('cannot specify --rev and --change at the same time')
1996 raise error.Abort(msg)
1995 raise error.Abort(msg)
1997 elif change:
1996 elif change:
1998 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1997 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1999 ctx2 = scmutil.revsingle(repo, change, None)
1998 ctx2 = scmutil.revsingle(repo, change, None)
2000 ctx1 = ctx2.p1()
1999 ctx1 = ctx2.p1()
2001 else:
2000 else:
2002 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2001 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2003 ctx1, ctx2 = scmutil.revpair(repo, revs)
2002 ctx1, ctx2 = scmutil.revpair(repo, revs)
2004 node1, node2 = ctx1.node(), ctx2.node()
2003 node1, node2 = ctx1.node(), ctx2.node()
2005
2004
2006 if reverse:
2005 if reverse:
2007 node1, node2 = node2, node1
2006 node1, node2 = node2, node1
2008
2007
2009 diffopts = patch.diffallopts(ui, opts)
2008 diffopts = patch.diffallopts(ui, opts)
2010 m = scmutil.match(ctx2, pats, opts)
2009 m = scmutil.match(ctx2, pats, opts)
2011 m = repo.narrowmatch(m)
2010 m = repo.narrowmatch(m)
2012 ui.pager('diff')
2011 ui.pager('diff')
2013 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2012 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2014 listsubrepos=opts.get('subrepos'),
2013 listsubrepos=opts.get('subrepos'),
2015 root=opts.get('root'))
2014 root=opts.get('root'))
2016
2015
2017 @command('export',
2016 @command('export',
2018 [('B', 'bookmark', '',
2017 [('B', 'bookmark', '',
2019 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2018 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2020 ('o', 'output', '',
2019 ('o', 'output', '',
2021 _('print output to file with formatted name'), _('FORMAT')),
2020 _('print output to file with formatted name'), _('FORMAT')),
2022 ('', 'switch-parent', None, _('diff against the second parent')),
2021 ('', 'switch-parent', None, _('diff against the second parent')),
2023 ('r', 'rev', [], _('revisions to export'), _('REV')),
2022 ('r', 'rev', [], _('revisions to export'), _('REV')),
2024 ] + diffopts + formatteropts,
2023 ] + diffopts + formatteropts,
2025 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2024 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2026 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2025 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2027 helpbasic=True, intents={INTENT_READONLY})
2026 helpbasic=True, intents={INTENT_READONLY})
2028 def export(ui, repo, *changesets, **opts):
2027 def export(ui, repo, *changesets, **opts):
2029 """dump the header and diffs for one or more changesets
2028 """dump the header and diffs for one or more changesets
2030
2029
2031 Print the changeset header and diffs for one or more revisions.
2030 Print the changeset header and diffs for one or more revisions.
2032 If no revision is given, the parent of the working directory is used.
2031 If no revision is given, the parent of the working directory is used.
2033
2032
2034 The information shown in the changeset header is: author, date,
2033 The information shown in the changeset header is: author, date,
2035 branch name (if non-default), changeset hash, parent(s) and commit
2034 branch name (if non-default), changeset hash, parent(s) and commit
2036 comment.
2035 comment.
2037
2036
2038 .. note::
2037 .. note::
2039
2038
2040 :hg:`export` may generate unexpected diff output for merge
2039 :hg:`export` may generate unexpected diff output for merge
2041 changesets, as it will compare the merge changeset against its
2040 changesets, as it will compare the merge changeset against its
2042 first parent only.
2041 first parent only.
2043
2042
2044 Output may be to a file, in which case the name of the file is
2043 Output may be to a file, in which case the name of the file is
2045 given using a template string. See :hg:`help templates`. In addition
2044 given using a template string. See :hg:`help templates`. In addition
2046 to the common template keywords, the following formatting rules are
2045 to the common template keywords, the following formatting rules are
2047 supported:
2046 supported:
2048
2047
2049 :``%%``: literal "%" character
2048 :``%%``: literal "%" character
2050 :``%H``: changeset hash (40 hexadecimal digits)
2049 :``%H``: changeset hash (40 hexadecimal digits)
2051 :``%N``: number of patches being generated
2050 :``%N``: number of patches being generated
2052 :``%R``: changeset revision number
2051 :``%R``: changeset revision number
2053 :``%b``: basename of the exporting repository
2052 :``%b``: basename of the exporting repository
2054 :``%h``: short-form changeset hash (12 hexadecimal digits)
2053 :``%h``: short-form changeset hash (12 hexadecimal digits)
2055 :``%m``: first line of the commit message (only alphanumeric characters)
2054 :``%m``: first line of the commit message (only alphanumeric characters)
2056 :``%n``: zero-padded sequence number, starting at 1
2055 :``%n``: zero-padded sequence number, starting at 1
2057 :``%r``: zero-padded changeset revision number
2056 :``%r``: zero-padded changeset revision number
2058 :``\\``: literal "\\" character
2057 :``\\``: literal "\\" character
2059
2058
2060 Without the -a/--text option, export will avoid generating diffs
2059 Without the -a/--text option, export will avoid generating diffs
2061 of files it detects as binary. With -a, export will generate a
2060 of files it detects as binary. With -a, export will generate a
2062 diff anyway, probably with undesirable results.
2061 diff anyway, probably with undesirable results.
2063
2062
2064 With -B/--bookmark changesets reachable by the given bookmark are
2063 With -B/--bookmark changesets reachable by the given bookmark are
2065 selected.
2064 selected.
2066
2065
2067 Use the -g/--git option to generate diffs in the git extended diff
2066 Use the -g/--git option to generate diffs in the git extended diff
2068 format. See :hg:`help diffs` for more information.
2067 format. See :hg:`help diffs` for more information.
2069
2068
2070 With the --switch-parent option, the diff will be against the
2069 With the --switch-parent option, the diff will be against the
2071 second parent. It can be useful to review a merge.
2070 second parent. It can be useful to review a merge.
2072
2071
2073 .. container:: verbose
2072 .. container:: verbose
2074
2073
2075 Template:
2074 Template:
2076
2075
2077 The following keywords are supported in addition to the common template
2076 The following keywords are supported in addition to the common template
2078 keywords and functions. See also :hg:`help templates`.
2077 keywords and functions. See also :hg:`help templates`.
2079
2078
2080 :diff: String. Diff content.
2079 :diff: String. Diff content.
2081 :parents: List of strings. Parent nodes of the changeset.
2080 :parents: List of strings. Parent nodes of the changeset.
2082
2081
2083 Examples:
2082 Examples:
2084
2083
2085 - use export and import to transplant a bugfix to the current
2084 - use export and import to transplant a bugfix to the current
2086 branch::
2085 branch::
2087
2086
2088 hg export -r 9353 | hg import -
2087 hg export -r 9353 | hg import -
2089
2088
2090 - export all the changesets between two revisions to a file with
2089 - export all the changesets between two revisions to a file with
2091 rename information::
2090 rename information::
2092
2091
2093 hg export --git -r 123:150 > changes.txt
2092 hg export --git -r 123:150 > changes.txt
2094
2093
2095 - split outgoing changes into a series of patches with
2094 - split outgoing changes into a series of patches with
2096 descriptive names::
2095 descriptive names::
2097
2096
2098 hg export -r "outgoing()" -o "%n-%m.patch"
2097 hg export -r "outgoing()" -o "%n-%m.patch"
2099
2098
2100 Returns 0 on success.
2099 Returns 0 on success.
2101 """
2100 """
2102 opts = pycompat.byteskwargs(opts)
2101 opts = pycompat.byteskwargs(opts)
2103 bookmark = opts.get('bookmark')
2102 bookmark = opts.get('bookmark')
2104 changesets += tuple(opts.get('rev', []))
2103 changesets += tuple(opts.get('rev', []))
2105
2104
2106 if bookmark and changesets:
2105 if bookmark and changesets:
2107 raise error.Abort(_("-r and -B are mutually exclusive"))
2106 raise error.Abort(_("-r and -B are mutually exclusive"))
2108
2107
2109 if bookmark:
2108 if bookmark:
2110 if bookmark not in repo._bookmarks:
2109 if bookmark not in repo._bookmarks:
2111 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2110 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2112
2111
2113 revs = scmutil.bookmarkrevs(repo, bookmark)
2112 revs = scmutil.bookmarkrevs(repo, bookmark)
2114 else:
2113 else:
2115 if not changesets:
2114 if not changesets:
2116 changesets = ['.']
2115 changesets = ['.']
2117
2116
2118 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2117 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2119 revs = scmutil.revrange(repo, changesets)
2118 revs = scmutil.revrange(repo, changesets)
2120
2119
2121 if not revs:
2120 if not revs:
2122 raise error.Abort(_("export requires at least one changeset"))
2121 raise error.Abort(_("export requires at least one changeset"))
2123 if len(revs) > 1:
2122 if len(revs) > 1:
2124 ui.note(_('exporting patches:\n'))
2123 ui.note(_('exporting patches:\n'))
2125 else:
2124 else:
2126 ui.note(_('exporting patch:\n'))
2125 ui.note(_('exporting patch:\n'))
2127
2126
2128 fntemplate = opts.get('output')
2127 fntemplate = opts.get('output')
2129 if cmdutil.isstdiofilename(fntemplate):
2128 if cmdutil.isstdiofilename(fntemplate):
2130 fntemplate = ''
2129 fntemplate = ''
2131
2130
2132 if fntemplate:
2131 if fntemplate:
2133 fm = formatter.nullformatter(ui, 'export', opts)
2132 fm = formatter.nullformatter(ui, 'export', opts)
2134 else:
2133 else:
2135 ui.pager('export')
2134 ui.pager('export')
2136 fm = ui.formatter('export', opts)
2135 fm = ui.formatter('export', opts)
2137 with fm:
2136 with fm:
2138 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2137 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2139 switch_parent=opts.get('switch_parent'),
2138 switch_parent=opts.get('switch_parent'),
2140 opts=patch.diffallopts(ui, opts))
2139 opts=patch.diffallopts(ui, opts))
2141
2140
2142 @command('files',
2141 @command('files',
2143 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2142 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2144 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2143 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2145 ] + walkopts + formatteropts + subrepoopts,
2144 ] + walkopts + formatteropts + subrepoopts,
2146 _('[OPTION]... [FILE]...'),
2145 _('[OPTION]... [FILE]...'),
2147 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2146 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2148 intents={INTENT_READONLY})
2147 intents={INTENT_READONLY})
2149 def files(ui, repo, *pats, **opts):
2148 def files(ui, repo, *pats, **opts):
2150 """list tracked files
2149 """list tracked files
2151
2150
2152 Print files under Mercurial control in the working directory or
2151 Print files under Mercurial control in the working directory or
2153 specified revision for given files (excluding removed files).
2152 specified revision for given files (excluding removed files).
2154 Files can be specified as filenames or filesets.
2153 Files can be specified as filenames or filesets.
2155
2154
2156 If no files are given to match, this command prints the names
2155 If no files are given to match, this command prints the names
2157 of all files under Mercurial control.
2156 of all files under Mercurial control.
2158
2157
2159 .. container:: verbose
2158 .. container:: verbose
2160
2159
2161 Template:
2160 Template:
2162
2161
2163 The following keywords are supported in addition to the common template
2162 The following keywords are supported in addition to the common template
2164 keywords and functions. See also :hg:`help templates`.
2163 keywords and functions. See also :hg:`help templates`.
2165
2164
2166 :flags: String. Character denoting file's symlink and executable bits.
2165 :flags: String. Character denoting file's symlink and executable bits.
2167 :path: String. Repository-absolute path of the file.
2166 :path: String. Repository-absolute path of the file.
2168 :size: Integer. Size of the file in bytes.
2167 :size: Integer. Size of the file in bytes.
2169
2168
2170 Examples:
2169 Examples:
2171
2170
2172 - list all files under the current directory::
2171 - list all files under the current directory::
2173
2172
2174 hg files .
2173 hg files .
2175
2174
2176 - shows sizes and flags for current revision::
2175 - shows sizes and flags for current revision::
2177
2176
2178 hg files -vr .
2177 hg files -vr .
2179
2178
2180 - list all files named README::
2179 - list all files named README::
2181
2180
2182 hg files -I "**/README"
2181 hg files -I "**/README"
2183
2182
2184 - list all binary files::
2183 - list all binary files::
2185
2184
2186 hg files "set:binary()"
2185 hg files "set:binary()"
2187
2186
2188 - find files containing a regular expression::
2187 - find files containing a regular expression::
2189
2188
2190 hg files "set:grep('bob')"
2189 hg files "set:grep('bob')"
2191
2190
2192 - search tracked file contents with xargs and grep::
2191 - search tracked file contents with xargs and grep::
2193
2192
2194 hg files -0 | xargs -0 grep foo
2193 hg files -0 | xargs -0 grep foo
2195
2194
2196 See :hg:`help patterns` and :hg:`help filesets` for more information
2195 See :hg:`help patterns` and :hg:`help filesets` for more information
2197 on specifying file patterns.
2196 on specifying file patterns.
2198
2197
2199 Returns 0 if a match is found, 1 otherwise.
2198 Returns 0 if a match is found, 1 otherwise.
2200
2199
2201 """
2200 """
2202
2201
2203 opts = pycompat.byteskwargs(opts)
2202 opts = pycompat.byteskwargs(opts)
2204 rev = opts.get('rev')
2203 rev = opts.get('rev')
2205 if rev:
2204 if rev:
2206 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2205 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2207 ctx = scmutil.revsingle(repo, rev, None)
2206 ctx = scmutil.revsingle(repo, rev, None)
2208
2207
2209 end = '\n'
2208 end = '\n'
2210 if opts.get('print0'):
2209 if opts.get('print0'):
2211 end = '\0'
2210 end = '\0'
2212 fmt = '%s' + end
2211 fmt = '%s' + end
2213
2212
2214 m = scmutil.match(ctx, pats, opts)
2213 m = scmutil.match(ctx, pats, opts)
2215 ui.pager('files')
2214 ui.pager('files')
2216 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2215 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2217 with ui.formatter('files', opts) as fm:
2216 with ui.formatter('files', opts) as fm:
2218 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2217 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2219 opts.get('subrepos'))
2218 opts.get('subrepos'))
2220
2219
2221 @command(
2220 @command(
2222 'forget',
2221 'forget',
2223 [('i', 'interactive', None, _('use interactive mode')),
2222 [('i', 'interactive', None, _('use interactive mode')),
2224 ] + walkopts + dryrunopts,
2223 ] + walkopts + dryrunopts,
2225 _('[OPTION]... FILE...'),
2224 _('[OPTION]... FILE...'),
2226 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2225 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2227 helpbasic=True, inferrepo=True)
2226 helpbasic=True, inferrepo=True)
2228 def forget(ui, repo, *pats, **opts):
2227 def forget(ui, repo, *pats, **opts):
2229 """forget the specified files on the next commit
2228 """forget the specified files on the next commit
2230
2229
2231 Mark the specified files so they will no longer be tracked
2230 Mark the specified files so they will no longer be tracked
2232 after the next commit.
2231 after the next commit.
2233
2232
2234 This only removes files from the current branch, not from the
2233 This only removes files from the current branch, not from the
2235 entire project history, and it does not delete them from the
2234 entire project history, and it does not delete them from the
2236 working directory.
2235 working directory.
2237
2236
2238 To delete the file from the working directory, see :hg:`remove`.
2237 To delete the file from the working directory, see :hg:`remove`.
2239
2238
2240 To undo a forget before the next commit, see :hg:`add`.
2239 To undo a forget before the next commit, see :hg:`add`.
2241
2240
2242 .. container:: verbose
2241 .. container:: verbose
2243
2242
2244 Examples:
2243 Examples:
2245
2244
2246 - forget newly-added binary files::
2245 - forget newly-added binary files::
2247
2246
2248 hg forget "set:added() and binary()"
2247 hg forget "set:added() and binary()"
2249
2248
2250 - forget files that would be excluded by .hgignore::
2249 - forget files that would be excluded by .hgignore::
2251
2250
2252 hg forget "set:hgignore()"
2251 hg forget "set:hgignore()"
2253
2252
2254 Returns 0 on success.
2253 Returns 0 on success.
2255 """
2254 """
2256
2255
2257 opts = pycompat.byteskwargs(opts)
2256 opts = pycompat.byteskwargs(opts)
2258 if not pats:
2257 if not pats:
2259 raise error.Abort(_('no files specified'))
2258 raise error.Abort(_('no files specified'))
2260
2259
2261 m = scmutil.match(repo[None], pats, opts)
2260 m = scmutil.match(repo[None], pats, opts)
2262 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2261 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2263 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2262 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2264 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2263 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2265 explicitonly=False, dryrun=dryrun,
2264 explicitonly=False, dryrun=dryrun,
2266 interactive=interactive)[0]
2265 interactive=interactive)[0]
2267 return rejected and 1 or 0
2266 return rejected and 1 or 0
2268
2267
2269 @command(
2268 @command(
2270 'graft',
2269 'graft',
2271 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2270 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2272 ('', 'base', '',
2271 ('', 'base', '',
2273 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2272 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2274 ('c', 'continue', False, _('resume interrupted graft')),
2273 ('c', 'continue', False, _('resume interrupted graft')),
2275 ('', 'stop', False, _('stop interrupted graft')),
2274 ('', 'stop', False, _('stop interrupted graft')),
2276 ('', 'abort', False, _('abort interrupted graft')),
2275 ('', 'abort', False, _('abort interrupted graft')),
2277 ('e', 'edit', False, _('invoke editor on commit messages')),
2276 ('e', 'edit', False, _('invoke editor on commit messages')),
2278 ('', 'log', None, _('append graft info to log message')),
2277 ('', 'log', None, _('append graft info to log message')),
2279 ('', 'no-commit', None,
2278 ('', 'no-commit', None,
2280 _("don't commit, just apply the changes in working directory")),
2279 _("don't commit, just apply the changes in working directory")),
2281 ('f', 'force', False, _('force graft')),
2280 ('f', 'force', False, _('force graft')),
2282 ('D', 'currentdate', False,
2281 ('D', 'currentdate', False,
2283 _('record the current date as commit date')),
2282 _('record the current date as commit date')),
2284 ('U', 'currentuser', False,
2283 ('U', 'currentuser', False,
2285 _('record the current user as committer'))]
2284 _('record the current user as committer'))]
2286 + commitopts2 + mergetoolopts + dryrunopts,
2285 + commitopts2 + mergetoolopts + dryrunopts,
2287 _('[OPTION]... [-r REV]... REV...'),
2286 _('[OPTION]... [-r REV]... REV...'),
2288 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2287 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2289 def graft(ui, repo, *revs, **opts):
2288 def graft(ui, repo, *revs, **opts):
2290 '''copy changes from other branches onto the current branch
2289 '''copy changes from other branches onto the current branch
2291
2290
2292 This command uses Mercurial's merge logic to copy individual
2291 This command uses Mercurial's merge logic to copy individual
2293 changes from other branches without merging branches in the
2292 changes from other branches without merging branches in the
2294 history graph. This is sometimes known as 'backporting' or
2293 history graph. This is sometimes known as 'backporting' or
2295 'cherry-picking'. By default, graft will copy user, date, and
2294 'cherry-picking'. By default, graft will copy user, date, and
2296 description from the source changesets.
2295 description from the source changesets.
2297
2296
2298 Changesets that are ancestors of the current revision, that have
2297 Changesets that are ancestors of the current revision, that have
2299 already been grafted, or that are merges will be skipped.
2298 already been grafted, or that are merges will be skipped.
2300
2299
2301 If --log is specified, log messages will have a comment appended
2300 If --log is specified, log messages will have a comment appended
2302 of the form::
2301 of the form::
2303
2302
2304 (grafted from CHANGESETHASH)
2303 (grafted from CHANGESETHASH)
2305
2304
2306 If --force is specified, revisions will be grafted even if they
2305 If --force is specified, revisions will be grafted even if they
2307 are already ancestors of, or have been grafted to, the destination.
2306 are already ancestors of, or have been grafted to, the destination.
2308 This is useful when the revisions have since been backed out.
2307 This is useful when the revisions have since been backed out.
2309
2308
2310 If a graft merge results in conflicts, the graft process is
2309 If a graft merge results in conflicts, the graft process is
2311 interrupted so that the current merge can be manually resolved.
2310 interrupted so that the current merge can be manually resolved.
2312 Once all conflicts are addressed, the graft process can be
2311 Once all conflicts are addressed, the graft process can be
2313 continued with the -c/--continue option.
2312 continued with the -c/--continue option.
2314
2313
2315 The -c/--continue option reapplies all the earlier options.
2314 The -c/--continue option reapplies all the earlier options.
2316
2315
2317 .. container:: verbose
2316 .. container:: verbose
2318
2317
2319 The --base option exposes more of how graft internally uses merge with a
2318 The --base option exposes more of how graft internally uses merge with a
2320 custom base revision. --base can be used to specify another ancestor than
2319 custom base revision. --base can be used to specify another ancestor than
2321 the first and only parent.
2320 the first and only parent.
2322
2321
2323 The command::
2322 The command::
2324
2323
2325 hg graft -r 345 --base 234
2324 hg graft -r 345 --base 234
2326
2325
2327 is thus pretty much the same as::
2326 is thus pretty much the same as::
2328
2327
2329 hg diff -r 234 -r 345 | hg import
2328 hg diff -r 234 -r 345 | hg import
2330
2329
2331 but using merge to resolve conflicts and track moved files.
2330 but using merge to resolve conflicts and track moved files.
2332
2331
2333 The result of a merge can thus be backported as a single commit by
2332 The result of a merge can thus be backported as a single commit by
2334 specifying one of the merge parents as base, and thus effectively
2333 specifying one of the merge parents as base, and thus effectively
2335 grafting the changes from the other side.
2334 grafting the changes from the other side.
2336
2335
2337 It is also possible to collapse multiple changesets and clean up history
2336 It is also possible to collapse multiple changesets and clean up history
2338 by specifying another ancestor as base, much like rebase --collapse
2337 by specifying another ancestor as base, much like rebase --collapse
2339 --keep.
2338 --keep.
2340
2339
2341 The commit message can be tweaked after the fact using commit --amend .
2340 The commit message can be tweaked after the fact using commit --amend .
2342
2341
2343 For using non-ancestors as the base to backout changes, see the backout
2342 For using non-ancestors as the base to backout changes, see the backout
2344 command and the hidden --parent option.
2343 command and the hidden --parent option.
2345
2344
2346 .. container:: verbose
2345 .. container:: verbose
2347
2346
2348 Examples:
2347 Examples:
2349
2348
2350 - copy a single change to the stable branch and edit its description::
2349 - copy a single change to the stable branch and edit its description::
2351
2350
2352 hg update stable
2351 hg update stable
2353 hg graft --edit 9393
2352 hg graft --edit 9393
2354
2353
2355 - graft a range of changesets with one exception, updating dates::
2354 - graft a range of changesets with one exception, updating dates::
2356
2355
2357 hg graft -D "2085::2093 and not 2091"
2356 hg graft -D "2085::2093 and not 2091"
2358
2357
2359 - continue a graft after resolving conflicts::
2358 - continue a graft after resolving conflicts::
2360
2359
2361 hg graft -c
2360 hg graft -c
2362
2361
2363 - show the source of a grafted changeset::
2362 - show the source of a grafted changeset::
2364
2363
2365 hg log --debug -r .
2364 hg log --debug -r .
2366
2365
2367 - show revisions sorted by date::
2366 - show revisions sorted by date::
2368
2367
2369 hg log -r "sort(all(), date)"
2368 hg log -r "sort(all(), date)"
2370
2369
2371 - backport the result of a merge as a single commit::
2370 - backport the result of a merge as a single commit::
2372
2371
2373 hg graft -r 123 --base 123^
2372 hg graft -r 123 --base 123^
2374
2373
2375 - land a feature branch as one changeset::
2374 - land a feature branch as one changeset::
2376
2375
2377 hg up -cr default
2376 hg up -cr default
2378 hg graft -r featureX --base "ancestor('featureX', 'default')"
2377 hg graft -r featureX --base "ancestor('featureX', 'default')"
2379
2378
2380 See :hg:`help revisions` for more about specifying revisions.
2379 See :hg:`help revisions` for more about specifying revisions.
2381
2380
2382 Returns 0 on successful completion.
2381 Returns 0 on successful completion.
2383 '''
2382 '''
2384 with repo.wlock():
2383 with repo.wlock():
2385 return _dograft(ui, repo, *revs, **opts)
2384 return _dograft(ui, repo, *revs, **opts)
2386
2385
2387 def _dograft(ui, repo, *revs, **opts):
2386 def _dograft(ui, repo, *revs, **opts):
2388 opts = pycompat.byteskwargs(opts)
2387 opts = pycompat.byteskwargs(opts)
2389 if revs and opts.get('rev'):
2388 if revs and opts.get('rev'):
2390 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2389 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2391 'revision ordering!\n'))
2390 'revision ordering!\n'))
2392
2391
2393 revs = list(revs)
2392 revs = list(revs)
2394 revs.extend(opts.get('rev'))
2393 revs.extend(opts.get('rev'))
2395 basectx = None
2394 basectx = None
2396 if opts.get('base'):
2395 if opts.get('base'):
2397 basectx = scmutil.revsingle(repo, opts['base'], None)
2396 basectx = scmutil.revsingle(repo, opts['base'], None)
2398 # a dict of data to be stored in state file
2397 # a dict of data to be stored in state file
2399 statedata = {}
2398 statedata = {}
2400 # list of new nodes created by ongoing graft
2399 # list of new nodes created by ongoing graft
2401 statedata['newnodes'] = []
2400 statedata['newnodes'] = []
2402
2401
2403 if opts.get('user') and opts.get('currentuser'):
2402 if opts.get('user') and opts.get('currentuser'):
2404 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2403 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2405 if opts.get('date') and opts.get('currentdate'):
2404 if opts.get('date') and opts.get('currentdate'):
2406 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2405 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2407 if not opts.get('user') and opts.get('currentuser'):
2406 if not opts.get('user') and opts.get('currentuser'):
2408 opts['user'] = ui.username()
2407 opts['user'] = ui.username()
2409 if not opts.get('date') and opts.get('currentdate'):
2408 if not opts.get('date') and opts.get('currentdate'):
2410 opts['date'] = "%d %d" % dateutil.makedate()
2409 opts['date'] = "%d %d" % dateutil.makedate()
2411
2410
2412 editor = cmdutil.getcommiteditor(editform='graft',
2411 editor = cmdutil.getcommiteditor(editform='graft',
2413 **pycompat.strkwargs(opts))
2412 **pycompat.strkwargs(opts))
2414
2413
2415 cont = False
2414 cont = False
2416 if opts.get('no_commit'):
2415 if opts.get('no_commit'):
2417 if opts.get('edit'):
2416 if opts.get('edit'):
2418 raise error.Abort(_("cannot specify --no-commit and "
2417 raise error.Abort(_("cannot specify --no-commit and "
2419 "--edit together"))
2418 "--edit together"))
2420 if opts.get('currentuser'):
2419 if opts.get('currentuser'):
2421 raise error.Abort(_("cannot specify --no-commit and "
2420 raise error.Abort(_("cannot specify --no-commit and "
2422 "--currentuser together"))
2421 "--currentuser together"))
2423 if opts.get('currentdate'):
2422 if opts.get('currentdate'):
2424 raise error.Abort(_("cannot specify --no-commit and "
2423 raise error.Abort(_("cannot specify --no-commit and "
2425 "--currentdate together"))
2424 "--currentdate together"))
2426 if opts.get('log'):
2425 if opts.get('log'):
2427 raise error.Abort(_("cannot specify --no-commit and "
2426 raise error.Abort(_("cannot specify --no-commit and "
2428 "--log together"))
2427 "--log together"))
2429
2428
2430 graftstate = statemod.cmdstate(repo, 'graftstate')
2429 graftstate = statemod.cmdstate(repo, 'graftstate')
2431
2430
2432 if opts.get('stop'):
2431 if opts.get('stop'):
2433 if opts.get('continue'):
2432 if opts.get('continue'):
2434 raise error.Abort(_("cannot use '--continue' and "
2433 raise error.Abort(_("cannot use '--continue' and "
2435 "'--stop' together"))
2434 "'--stop' together"))
2436 if opts.get('abort'):
2435 if opts.get('abort'):
2437 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2436 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2438
2437
2439 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2438 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2440 opts.get('date'), opts.get('currentdate'),
2439 opts.get('date'), opts.get('currentdate'),
2441 opts.get('currentuser'), opts.get('rev'))):
2440 opts.get('currentuser'), opts.get('rev'))):
2442 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2441 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2443 return _stopgraft(ui, repo, graftstate)
2442 return _stopgraft(ui, repo, graftstate)
2444 elif opts.get('abort'):
2443 elif opts.get('abort'):
2445 if opts.get('continue'):
2444 if opts.get('continue'):
2446 raise error.Abort(_("cannot use '--continue' and "
2445 raise error.Abort(_("cannot use '--continue' and "
2447 "'--abort' together"))
2446 "'--abort' together"))
2448 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2447 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2449 opts.get('date'), opts.get('currentdate'),
2448 opts.get('date'), opts.get('currentdate'),
2450 opts.get('currentuser'), opts.get('rev'))):
2449 opts.get('currentuser'), opts.get('rev'))):
2451 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2450 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2452
2451
2453 return _abortgraft(ui, repo, graftstate)
2452 return _abortgraft(ui, repo, graftstate)
2454 elif opts.get('continue'):
2453 elif opts.get('continue'):
2455 cont = True
2454 cont = True
2456 if revs:
2455 if revs:
2457 raise error.Abort(_("can't specify --continue and revisions"))
2456 raise error.Abort(_("can't specify --continue and revisions"))
2458 # read in unfinished revisions
2457 # read in unfinished revisions
2459 if graftstate.exists():
2458 if graftstate.exists():
2460 statedata = _readgraftstate(repo, graftstate)
2459 statedata = _readgraftstate(repo, graftstate)
2461 if statedata.get('date'):
2460 if statedata.get('date'):
2462 opts['date'] = statedata['date']
2461 opts['date'] = statedata['date']
2463 if statedata.get('user'):
2462 if statedata.get('user'):
2464 opts['user'] = statedata['user']
2463 opts['user'] = statedata['user']
2465 if statedata.get('log'):
2464 if statedata.get('log'):
2466 opts['log'] = True
2465 opts['log'] = True
2467 if statedata.get('no_commit'):
2466 if statedata.get('no_commit'):
2468 opts['no_commit'] = statedata.get('no_commit')
2467 opts['no_commit'] = statedata.get('no_commit')
2469 nodes = statedata['nodes']
2468 nodes = statedata['nodes']
2470 revs = [repo[node].rev() for node in nodes]
2469 revs = [repo[node].rev() for node in nodes]
2471 else:
2470 else:
2472 cmdutil.wrongtooltocontinue(repo, _('graft'))
2471 cmdutil.wrongtooltocontinue(repo, _('graft'))
2473 else:
2472 else:
2474 if not revs:
2473 if not revs:
2475 raise error.Abort(_('no revisions specified'))
2474 raise error.Abort(_('no revisions specified'))
2476 cmdutil.checkunfinished(repo)
2475 cmdutil.checkunfinished(repo)
2477 cmdutil.bailifchanged(repo)
2476 cmdutil.bailifchanged(repo)
2478 revs = scmutil.revrange(repo, revs)
2477 revs = scmutil.revrange(repo, revs)
2479
2478
2480 skipped = set()
2479 skipped = set()
2481 if basectx is None:
2480 if basectx is None:
2482 # check for merges
2481 # check for merges
2483 for rev in repo.revs('%ld and merge()', revs):
2482 for rev in repo.revs('%ld and merge()', revs):
2484 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2483 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2485 skipped.add(rev)
2484 skipped.add(rev)
2486 revs = [r for r in revs if r not in skipped]
2485 revs = [r for r in revs if r not in skipped]
2487 if not revs:
2486 if not revs:
2488 return -1
2487 return -1
2489 if basectx is not None and len(revs) != 1:
2488 if basectx is not None and len(revs) != 1:
2490 raise error.Abort(_('only one revision allowed with --base '))
2489 raise error.Abort(_('only one revision allowed with --base '))
2491
2490
2492 # Don't check in the --continue case, in effect retaining --force across
2491 # Don't check in the --continue case, in effect retaining --force across
2493 # --continues. That's because without --force, any revisions we decided to
2492 # --continues. That's because without --force, any revisions we decided to
2494 # skip would have been filtered out here, so they wouldn't have made their
2493 # skip would have been filtered out here, so they wouldn't have made their
2495 # way to the graftstate. With --force, any revisions we would have otherwise
2494 # way to the graftstate. With --force, any revisions we would have otherwise
2496 # skipped would not have been filtered out, and if they hadn't been applied
2495 # skipped would not have been filtered out, and if they hadn't been applied
2497 # already, they'd have been in the graftstate.
2496 # already, they'd have been in the graftstate.
2498 if not (cont or opts.get('force')) and basectx is None:
2497 if not (cont or opts.get('force')) and basectx is None:
2499 # check for ancestors of dest branch
2498 # check for ancestors of dest branch
2500 crev = repo['.'].rev()
2499 crev = repo['.'].rev()
2501 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2500 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2502 # XXX make this lazy in the future
2501 # XXX make this lazy in the future
2503 # don't mutate while iterating, create a copy
2502 # don't mutate while iterating, create a copy
2504 for rev in list(revs):
2503 for rev in list(revs):
2505 if rev in ancestors:
2504 if rev in ancestors:
2506 ui.warn(_('skipping ancestor revision %d:%s\n') %
2505 ui.warn(_('skipping ancestor revision %d:%s\n') %
2507 (rev, repo[rev]))
2506 (rev, repo[rev]))
2508 # XXX remove on list is slow
2507 # XXX remove on list is slow
2509 revs.remove(rev)
2508 revs.remove(rev)
2510 if not revs:
2509 if not revs:
2511 return -1
2510 return -1
2512
2511
2513 # analyze revs for earlier grafts
2512 # analyze revs for earlier grafts
2514 ids = {}
2513 ids = {}
2515 for ctx in repo.set("%ld", revs):
2514 for ctx in repo.set("%ld", revs):
2516 ids[ctx.hex()] = ctx.rev()
2515 ids[ctx.hex()] = ctx.rev()
2517 n = ctx.extra().get('source')
2516 n = ctx.extra().get('source')
2518 if n:
2517 if n:
2519 ids[n] = ctx.rev()
2518 ids[n] = ctx.rev()
2520
2519
2521 # check ancestors for earlier grafts
2520 # check ancestors for earlier grafts
2522 ui.debug('scanning for duplicate grafts\n')
2521 ui.debug('scanning for duplicate grafts\n')
2523
2522
2524 # The only changesets we can be sure doesn't contain grafts of any
2523 # The only changesets we can be sure doesn't contain grafts of any
2525 # revs, are the ones that are common ancestors of *all* revs:
2524 # revs, are the ones that are common ancestors of *all* revs:
2526 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2525 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2527 ctx = repo[rev]
2526 ctx = repo[rev]
2528 n = ctx.extra().get('source')
2527 n = ctx.extra().get('source')
2529 if n in ids:
2528 if n in ids:
2530 try:
2529 try:
2531 r = repo[n].rev()
2530 r = repo[n].rev()
2532 except error.RepoLookupError:
2531 except error.RepoLookupError:
2533 r = None
2532 r = None
2534 if r in revs:
2533 if r in revs:
2535 ui.warn(_('skipping revision %d:%s '
2534 ui.warn(_('skipping revision %d:%s '
2536 '(already grafted to %d:%s)\n')
2535 '(already grafted to %d:%s)\n')
2537 % (r, repo[r], rev, ctx))
2536 % (r, repo[r], rev, ctx))
2538 revs.remove(r)
2537 revs.remove(r)
2539 elif ids[n] in revs:
2538 elif ids[n] in revs:
2540 if r is None:
2539 if r is None:
2541 ui.warn(_('skipping already grafted revision %d:%s '
2540 ui.warn(_('skipping already grafted revision %d:%s '
2542 '(%d:%s also has unknown origin %s)\n')
2541 '(%d:%s also has unknown origin %s)\n')
2543 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2542 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2544 else:
2543 else:
2545 ui.warn(_('skipping already grafted revision %d:%s '
2544 ui.warn(_('skipping already grafted revision %d:%s '
2546 '(%d:%s also has origin %d:%s)\n')
2545 '(%d:%s also has origin %d:%s)\n')
2547 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2546 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2548 revs.remove(ids[n])
2547 revs.remove(ids[n])
2549 elif ctx.hex() in ids:
2548 elif ctx.hex() in ids:
2550 r = ids[ctx.hex()]
2549 r = ids[ctx.hex()]
2551 if r in revs:
2550 if r in revs:
2552 ui.warn(_('skipping already grafted revision %d:%s '
2551 ui.warn(_('skipping already grafted revision %d:%s '
2553 '(was grafted from %d:%s)\n') %
2552 '(was grafted from %d:%s)\n') %
2554 (r, repo[r], rev, ctx))
2553 (r, repo[r], rev, ctx))
2555 revs.remove(r)
2554 revs.remove(r)
2556 if not revs:
2555 if not revs:
2557 return -1
2556 return -1
2558
2557
2559 if opts.get('no_commit'):
2558 if opts.get('no_commit'):
2560 statedata['no_commit'] = True
2559 statedata['no_commit'] = True
2561 for pos, ctx in enumerate(repo.set("%ld", revs)):
2560 for pos, ctx in enumerate(repo.set("%ld", revs)):
2562 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2561 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2563 ctx.description().split('\n', 1)[0])
2562 ctx.description().split('\n', 1)[0])
2564 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2563 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2565 if names:
2564 if names:
2566 desc += ' (%s)' % ' '.join(names)
2565 desc += ' (%s)' % ' '.join(names)
2567 ui.status(_('grafting %s\n') % desc)
2566 ui.status(_('grafting %s\n') % desc)
2568 if opts.get('dry_run'):
2567 if opts.get('dry_run'):
2569 continue
2568 continue
2570
2569
2571 source = ctx.extra().get('source')
2570 source = ctx.extra().get('source')
2572 extra = {}
2571 extra = {}
2573 if source:
2572 if source:
2574 extra['source'] = source
2573 extra['source'] = source
2575 extra['intermediate-source'] = ctx.hex()
2574 extra['intermediate-source'] = ctx.hex()
2576 else:
2575 else:
2577 extra['source'] = ctx.hex()
2576 extra['source'] = ctx.hex()
2578 user = ctx.user()
2577 user = ctx.user()
2579 if opts.get('user'):
2578 if opts.get('user'):
2580 user = opts['user']
2579 user = opts['user']
2581 statedata['user'] = user
2580 statedata['user'] = user
2582 date = ctx.date()
2581 date = ctx.date()
2583 if opts.get('date'):
2582 if opts.get('date'):
2584 date = opts['date']
2583 date = opts['date']
2585 statedata['date'] = date
2584 statedata['date'] = date
2586 message = ctx.description()
2585 message = ctx.description()
2587 if opts.get('log'):
2586 if opts.get('log'):
2588 message += '\n(grafted from %s)' % ctx.hex()
2587 message += '\n(grafted from %s)' % ctx.hex()
2589 statedata['log'] = True
2588 statedata['log'] = True
2590
2589
2591 # we don't merge the first commit when continuing
2590 # we don't merge the first commit when continuing
2592 if not cont:
2591 if not cont:
2593 # perform the graft merge with p1(rev) as 'ancestor'
2592 # perform the graft merge with p1(rev) as 'ancestor'
2594 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2593 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2595 base = ctx.p1() if basectx is None else basectx
2594 base = ctx.p1() if basectx is None else basectx
2596 with ui.configoverride(overrides, 'graft'):
2595 with ui.configoverride(overrides, 'graft'):
2597 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2596 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2598 # report any conflicts
2597 # report any conflicts
2599 if stats.unresolvedcount > 0:
2598 if stats.unresolvedcount > 0:
2600 # write out state for --continue
2599 # write out state for --continue
2601 nodes = [repo[rev].hex() for rev in revs[pos:]]
2600 nodes = [repo[rev].hex() for rev in revs[pos:]]
2602 statedata['nodes'] = nodes
2601 statedata['nodes'] = nodes
2603 stateversion = 1
2602 stateversion = 1
2604 graftstate.save(stateversion, statedata)
2603 graftstate.save(stateversion, statedata)
2605 hint = _("use 'hg resolve' and 'hg graft --continue'")
2604 hint = _("use 'hg resolve' and 'hg graft --continue'")
2606 raise error.Abort(
2605 raise error.Abort(
2607 _("unresolved conflicts, can't continue"),
2606 _("unresolved conflicts, can't continue"),
2608 hint=hint)
2607 hint=hint)
2609 else:
2608 else:
2610 cont = False
2609 cont = False
2611
2610
2612 # commit if --no-commit is false
2611 # commit if --no-commit is false
2613 if not opts.get('no_commit'):
2612 if not opts.get('no_commit'):
2614 node = repo.commit(text=message, user=user, date=date, extra=extra,
2613 node = repo.commit(text=message, user=user, date=date, extra=extra,
2615 editor=editor)
2614 editor=editor)
2616 if node is None:
2615 if node is None:
2617 ui.warn(
2616 ui.warn(
2618 _('note: graft of %d:%s created no changes to commit\n') %
2617 _('note: graft of %d:%s created no changes to commit\n') %
2619 (ctx.rev(), ctx))
2618 (ctx.rev(), ctx))
2620 # checking that newnodes exist because old state files won't have it
2619 # checking that newnodes exist because old state files won't have it
2621 elif statedata.get('newnodes') is not None:
2620 elif statedata.get('newnodes') is not None:
2622 statedata['newnodes'].append(node)
2621 statedata['newnodes'].append(node)
2623
2622
2624 # remove state when we complete successfully
2623 # remove state when we complete successfully
2625 if not opts.get('dry_run'):
2624 if not opts.get('dry_run'):
2626 graftstate.delete()
2625 graftstate.delete()
2627
2626
2628 return 0
2627 return 0
2629
2628
2630 def _abortgraft(ui, repo, graftstate):
2629 def _abortgraft(ui, repo, graftstate):
2631 """abort the interrupted graft and rollbacks to the state before interrupted
2630 """abort the interrupted graft and rollbacks to the state before interrupted
2632 graft"""
2631 graft"""
2633 if not graftstate.exists():
2632 if not graftstate.exists():
2634 raise error.Abort(_("no interrupted graft to abort"))
2633 raise error.Abort(_("no interrupted graft to abort"))
2635 statedata = _readgraftstate(repo, graftstate)
2634 statedata = _readgraftstate(repo, graftstate)
2636 newnodes = statedata.get('newnodes')
2635 newnodes = statedata.get('newnodes')
2637 if newnodes is None:
2636 if newnodes is None:
2638 # and old graft state which does not have all the data required to abort
2637 # and old graft state which does not have all the data required to abort
2639 # the graft
2638 # the graft
2640 raise error.Abort(_("cannot abort using an old graftstate"))
2639 raise error.Abort(_("cannot abort using an old graftstate"))
2641
2640
2642 # changeset from which graft operation was started
2641 # changeset from which graft operation was started
2643 if len(newnodes) > 0:
2642 if len(newnodes) > 0:
2644 startctx = repo[newnodes[0]].p1()
2643 startctx = repo[newnodes[0]].p1()
2645 else:
2644 else:
2646 startctx = repo['.']
2645 startctx = repo['.']
2647 # whether to strip or not
2646 # whether to strip or not
2648 cleanup = False
2647 cleanup = False
2649 if newnodes:
2648 if newnodes:
2650 newnodes = [repo[r].rev() for r in newnodes]
2649 newnodes = [repo[r].rev() for r in newnodes]
2651 cleanup = True
2650 cleanup = True
2652 # checking that none of the newnodes turned public or is public
2651 # checking that none of the newnodes turned public or is public
2653 immutable = [c for c in newnodes if not repo[c].mutable()]
2652 immutable = [c for c in newnodes if not repo[c].mutable()]
2654 if immutable:
2653 if immutable:
2655 repo.ui.warn(_("cannot clean up public changesets %s\n")
2654 repo.ui.warn(_("cannot clean up public changesets %s\n")
2656 % ', '.join(bytes(repo[r]) for r in immutable),
2655 % ', '.join(bytes(repo[r]) for r in immutable),
2657 hint=_("see 'hg help phases' for details"))
2656 hint=_("see 'hg help phases' for details"))
2658 cleanup = False
2657 cleanup = False
2659
2658
2660 # checking that no new nodes are created on top of grafted revs
2659 # checking that no new nodes are created on top of grafted revs
2661 desc = set(repo.changelog.descendants(newnodes))
2660 desc = set(repo.changelog.descendants(newnodes))
2662 if desc - set(newnodes):
2661 if desc - set(newnodes):
2663 repo.ui.warn(_("new changesets detected on destination "
2662 repo.ui.warn(_("new changesets detected on destination "
2664 "branch, can't strip\n"))
2663 "branch, can't strip\n"))
2665 cleanup = False
2664 cleanup = False
2666
2665
2667 if cleanup:
2666 if cleanup:
2668 with repo.wlock(), repo.lock():
2667 with repo.wlock(), repo.lock():
2669 hg.updaterepo(repo, startctx.node(), overwrite=True)
2668 hg.updaterepo(repo, startctx.node(), overwrite=True)
2670 # stripping the new nodes created
2669 # stripping the new nodes created
2671 strippoints = [c.node() for c in repo.set("roots(%ld)",
2670 strippoints = [c.node() for c in repo.set("roots(%ld)",
2672 newnodes)]
2671 newnodes)]
2673 repair.strip(repo.ui, repo, strippoints, backup=False)
2672 repair.strip(repo.ui, repo, strippoints, backup=False)
2674
2673
2675 if not cleanup:
2674 if not cleanup:
2676 # we don't update to the startnode if we can't strip
2675 # we don't update to the startnode if we can't strip
2677 startctx = repo['.']
2676 startctx = repo['.']
2678 hg.updaterepo(repo, startctx.node(), overwrite=True)
2677 hg.updaterepo(repo, startctx.node(), overwrite=True)
2679
2678
2680 ui.status(_("graft aborted\n"))
2679 ui.status(_("graft aborted\n"))
2681 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2680 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2682 graftstate.delete()
2681 graftstate.delete()
2683 return 0
2682 return 0
2684
2683
2685 def _readgraftstate(repo, graftstate):
2684 def _readgraftstate(repo, graftstate):
2686 """read the graft state file and return a dict of the data stored in it"""
2685 """read the graft state file and return a dict of the data stored in it"""
2687 try:
2686 try:
2688 return graftstate.read()
2687 return graftstate.read()
2689 except error.CorruptedState:
2688 except error.CorruptedState:
2690 nodes = repo.vfs.read('graftstate').splitlines()
2689 nodes = repo.vfs.read('graftstate').splitlines()
2691 return {'nodes': nodes}
2690 return {'nodes': nodes}
2692
2691
2693 def _stopgraft(ui, repo, graftstate):
2692 def _stopgraft(ui, repo, graftstate):
2694 """stop the interrupted graft"""
2693 """stop the interrupted graft"""
2695 if not graftstate.exists():
2694 if not graftstate.exists():
2696 raise error.Abort(_("no interrupted graft found"))
2695 raise error.Abort(_("no interrupted graft found"))
2697 pctx = repo['.']
2696 pctx = repo['.']
2698 hg.updaterepo(repo, pctx.node(), overwrite=True)
2697 hg.updaterepo(repo, pctx.node(), overwrite=True)
2699 graftstate.delete()
2698 graftstate.delete()
2700 ui.status(_("stopped the interrupted graft\n"))
2699 ui.status(_("stopped the interrupted graft\n"))
2701 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2700 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2702 return 0
2701 return 0
2703
2702
2704 @command('grep',
2703 @command('grep',
2705 [('0', 'print0', None, _('end fields with NUL')),
2704 [('0', 'print0', None, _('end fields with NUL')),
2706 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2705 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2707 ('', 'diff', None, _('print all revisions when the term was introduced '
2706 ('', 'diff', None, _('print all revisions when the term was introduced '
2708 'or removed')),
2707 'or removed')),
2709 ('a', 'text', None, _('treat all files as text')),
2708 ('a', 'text', None, _('treat all files as text')),
2710 ('f', 'follow', None,
2709 ('f', 'follow', None,
2711 _('follow changeset history,'
2710 _('follow changeset history,'
2712 ' or file history across copies and renames')),
2711 ' or file history across copies and renames')),
2713 ('i', 'ignore-case', None, _('ignore case when matching')),
2712 ('i', 'ignore-case', None, _('ignore case when matching')),
2714 ('l', 'files-with-matches', None,
2713 ('l', 'files-with-matches', None,
2715 _('print only filenames and revisions that match')),
2714 _('print only filenames and revisions that match')),
2716 ('n', 'line-number', None, _('print matching line numbers')),
2715 ('n', 'line-number', None, _('print matching line numbers')),
2717 ('r', 'rev', [],
2716 ('r', 'rev', [],
2718 _('only search files changed within revision range'), _('REV')),
2717 _('only search files changed within revision range'), _('REV')),
2719 ('', 'all-files', None,
2718 ('', 'all-files', None,
2720 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2719 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2721 ('u', 'user', None, _('list the author (long with -v)')),
2720 ('u', 'user', None, _('list the author (long with -v)')),
2722 ('d', 'date', None, _('list the date (short with -q)')),
2721 ('d', 'date', None, _('list the date (short with -q)')),
2723 ] + formatteropts + walkopts,
2722 ] + formatteropts + walkopts,
2724 _('[OPTION]... PATTERN [FILE]...'),
2723 _('[OPTION]... PATTERN [FILE]...'),
2725 helpcategory=command.CATEGORY_FILE_CONTENTS,
2724 helpcategory=command.CATEGORY_FILE_CONTENTS,
2726 inferrepo=True,
2725 inferrepo=True,
2727 intents={INTENT_READONLY})
2726 intents={INTENT_READONLY})
2728 def grep(ui, repo, pattern, *pats, **opts):
2727 def grep(ui, repo, pattern, *pats, **opts):
2729 """search revision history for a pattern in specified files
2728 """search revision history for a pattern in specified files
2730
2729
2731 Search revision history for a regular expression in the specified
2730 Search revision history for a regular expression in the specified
2732 files or the entire project.
2731 files or the entire project.
2733
2732
2734 By default, grep prints the most recent revision number for each
2733 By default, grep prints the most recent revision number for each
2735 file in which it finds a match. To get it to print every revision
2734 file in which it finds a match. To get it to print every revision
2736 that contains a change in match status ("-" for a match that becomes
2735 that contains a change in match status ("-" for a match that becomes
2737 a non-match, or "+" for a non-match that becomes a match), use the
2736 a non-match, or "+" for a non-match that becomes a match), use the
2738 --diff flag.
2737 --diff flag.
2739
2738
2740 PATTERN can be any Python (roughly Perl-compatible) regular
2739 PATTERN can be any Python (roughly Perl-compatible) regular
2741 expression.
2740 expression.
2742
2741
2743 If no FILEs are specified (and -f/--follow isn't set), all files in
2742 If no FILEs are specified (and -f/--follow isn't set), all files in
2744 the repository are searched, including those that don't exist in the
2743 the repository are searched, including those that don't exist in the
2745 current branch or have been deleted in a prior changeset.
2744 current branch or have been deleted in a prior changeset.
2746
2745
2747 .. container:: verbose
2746 .. container:: verbose
2748
2747
2749 Template:
2748 Template:
2750
2749
2751 The following keywords are supported in addition to the common template
2750 The following keywords are supported in addition to the common template
2752 keywords and functions. See also :hg:`help templates`.
2751 keywords and functions. See also :hg:`help templates`.
2753
2752
2754 :change: String. Character denoting insertion ``+`` or removal ``-``.
2753 :change: String. Character denoting insertion ``+`` or removal ``-``.
2755 Available if ``--diff`` is specified.
2754 Available if ``--diff`` is specified.
2756 :lineno: Integer. Line number of the match.
2755 :lineno: Integer. Line number of the match.
2757 :path: String. Repository-absolute path of the file.
2756 :path: String. Repository-absolute path of the file.
2758 :texts: List of text chunks.
2757 :texts: List of text chunks.
2759
2758
2760 And each entry of ``{texts}`` provides the following sub-keywords.
2759 And each entry of ``{texts}`` provides the following sub-keywords.
2761
2760
2762 :matched: Boolean. True if the chunk matches the specified pattern.
2761 :matched: Boolean. True if the chunk matches the specified pattern.
2763 :text: String. Chunk content.
2762 :text: String. Chunk content.
2764
2763
2765 See :hg:`help templates.operators` for the list expansion syntax.
2764 See :hg:`help templates.operators` for the list expansion syntax.
2766
2765
2767 Returns 0 if a match is found, 1 otherwise.
2766 Returns 0 if a match is found, 1 otherwise.
2768 """
2767 """
2769 opts = pycompat.byteskwargs(opts)
2768 opts = pycompat.byteskwargs(opts)
2770 diff = opts.get('all') or opts.get('diff')
2769 diff = opts.get('all') or opts.get('diff')
2771 all_files = opts.get('all_files')
2770 all_files = opts.get('all_files')
2772 if diff and opts.get('all_files'):
2771 if diff and opts.get('all_files'):
2773 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2772 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2774 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2773 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2775 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2774 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2776 # experimental config: commands.grep.all-files
2775 # experimental config: commands.grep.all-files
2777 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2776 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2778 plaingrep = opts.get('all_files') and not opts.get('rev')
2777 plaingrep = opts.get('all_files') and not opts.get('rev')
2779 if plaingrep:
2778 if plaingrep:
2780 opts['rev'] = ['wdir()']
2779 opts['rev'] = ['wdir()']
2781
2780
2782 reflags = re.M
2781 reflags = re.M
2783 if opts.get('ignore_case'):
2782 if opts.get('ignore_case'):
2784 reflags |= re.I
2783 reflags |= re.I
2785 try:
2784 try:
2786 regexp = util.re.compile(pattern, reflags)
2785 regexp = util.re.compile(pattern, reflags)
2787 except re.error as inst:
2786 except re.error as inst:
2788 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2787 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2789 return 1
2788 return 1
2790 sep, eol = ':', '\n'
2789 sep, eol = ':', '\n'
2791 if opts.get('print0'):
2790 if opts.get('print0'):
2792 sep = eol = '\0'
2791 sep = eol = '\0'
2793
2792
2794 getfile = util.lrucachefunc(repo.file)
2793 getfile = util.lrucachefunc(repo.file)
2795
2794
2796 def matchlines(body):
2795 def matchlines(body):
2797 begin = 0
2796 begin = 0
2798 linenum = 0
2797 linenum = 0
2799 while begin < len(body):
2798 while begin < len(body):
2800 match = regexp.search(body, begin)
2799 match = regexp.search(body, begin)
2801 if not match:
2800 if not match:
2802 break
2801 break
2803 mstart, mend = match.span()
2802 mstart, mend = match.span()
2804 linenum += body.count('\n', begin, mstart) + 1
2803 linenum += body.count('\n', begin, mstart) + 1
2805 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2804 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2806 begin = body.find('\n', mend) + 1 or len(body) + 1
2805 begin = body.find('\n', mend) + 1 or len(body) + 1
2807 lend = begin - 1
2806 lend = begin - 1
2808 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2807 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2809
2808
2810 class linestate(object):
2809 class linestate(object):
2811 def __init__(self, line, linenum, colstart, colend):
2810 def __init__(self, line, linenum, colstart, colend):
2812 self.line = line
2811 self.line = line
2813 self.linenum = linenum
2812 self.linenum = linenum
2814 self.colstart = colstart
2813 self.colstart = colstart
2815 self.colend = colend
2814 self.colend = colend
2816
2815
2817 def __hash__(self):
2816 def __hash__(self):
2818 return hash((self.linenum, self.line))
2817 return hash((self.linenum, self.line))
2819
2818
2820 def __eq__(self, other):
2819 def __eq__(self, other):
2821 return self.line == other.line
2820 return self.line == other.line
2822
2821
2823 def findpos(self):
2822 def findpos(self):
2824 """Iterate all (start, end) indices of matches"""
2823 """Iterate all (start, end) indices of matches"""
2825 yield self.colstart, self.colend
2824 yield self.colstart, self.colend
2826 p = self.colend
2825 p = self.colend
2827 while p < len(self.line):
2826 while p < len(self.line):
2828 m = regexp.search(self.line, p)
2827 m = regexp.search(self.line, p)
2829 if not m:
2828 if not m:
2830 break
2829 break
2831 yield m.span()
2830 yield m.span()
2832 p = m.end()
2831 p = m.end()
2833
2832
2834 matches = {}
2833 matches = {}
2835 copies = {}
2834 copies = {}
2836 def grepbody(fn, rev, body):
2835 def grepbody(fn, rev, body):
2837 matches[rev].setdefault(fn, [])
2836 matches[rev].setdefault(fn, [])
2838 m = matches[rev][fn]
2837 m = matches[rev][fn]
2839 for lnum, cstart, cend, line in matchlines(body):
2838 for lnum, cstart, cend, line in matchlines(body):
2840 s = linestate(line, lnum, cstart, cend)
2839 s = linestate(line, lnum, cstart, cend)
2841 m.append(s)
2840 m.append(s)
2842
2841
2843 def difflinestates(a, b):
2842 def difflinestates(a, b):
2844 sm = difflib.SequenceMatcher(None, a, b)
2843 sm = difflib.SequenceMatcher(None, a, b)
2845 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2844 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2846 if tag == r'insert':
2845 if tag == r'insert':
2847 for i in pycompat.xrange(blo, bhi):
2846 for i in pycompat.xrange(blo, bhi):
2848 yield ('+', b[i])
2847 yield ('+', b[i])
2849 elif tag == r'delete':
2848 elif tag == r'delete':
2850 for i in pycompat.xrange(alo, ahi):
2849 for i in pycompat.xrange(alo, ahi):
2851 yield ('-', a[i])
2850 yield ('-', a[i])
2852 elif tag == r'replace':
2851 elif tag == r'replace':
2853 for i in pycompat.xrange(alo, ahi):
2852 for i in pycompat.xrange(alo, ahi):
2854 yield ('-', a[i])
2853 yield ('-', a[i])
2855 for i in pycompat.xrange(blo, bhi):
2854 for i in pycompat.xrange(blo, bhi):
2856 yield ('+', b[i])
2855 yield ('+', b[i])
2857
2856
2858 uipathfn = scmutil.getuipathfn(repo)
2857 uipathfn = scmutil.getuipathfn(repo)
2859 def display(fm, fn, ctx, pstates, states):
2858 def display(fm, fn, ctx, pstates, states):
2860 rev = scmutil.intrev(ctx)
2859 rev = scmutil.intrev(ctx)
2861 if fm.isplain():
2860 if fm.isplain():
2862 formatuser = ui.shortuser
2861 formatuser = ui.shortuser
2863 else:
2862 else:
2864 formatuser = pycompat.bytestr
2863 formatuser = pycompat.bytestr
2865 if ui.quiet:
2864 if ui.quiet:
2866 datefmt = '%Y-%m-%d'
2865 datefmt = '%Y-%m-%d'
2867 else:
2866 else:
2868 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2867 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2869 found = False
2868 found = False
2870 @util.cachefunc
2869 @util.cachefunc
2871 def binary():
2870 def binary():
2872 flog = getfile(fn)
2871 flog = getfile(fn)
2873 try:
2872 try:
2874 return stringutil.binary(flog.read(ctx.filenode(fn)))
2873 return stringutil.binary(flog.read(ctx.filenode(fn)))
2875 except error.WdirUnsupported:
2874 except error.WdirUnsupported:
2876 return ctx[fn].isbinary()
2875 return ctx[fn].isbinary()
2877
2876
2878 fieldnamemap = {'linenumber': 'lineno'}
2877 fieldnamemap = {'linenumber': 'lineno'}
2879 if diff:
2878 if diff:
2880 iter = difflinestates(pstates, states)
2879 iter = difflinestates(pstates, states)
2881 else:
2880 else:
2882 iter = [('', l) for l in states]
2881 iter = [('', l) for l in states]
2883 for change, l in iter:
2882 for change, l in iter:
2884 fm.startitem()
2883 fm.startitem()
2885 fm.context(ctx=ctx)
2884 fm.context(ctx=ctx)
2886 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2885 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2887 fm.plain(uipathfn(fn), label='grep.filename')
2886 fm.plain(uipathfn(fn), label='grep.filename')
2888
2887
2889 cols = [
2888 cols = [
2890 ('rev', '%d', rev, not plaingrep, ''),
2889 ('rev', '%d', rev, not plaingrep, ''),
2891 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2890 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2892 ]
2891 ]
2893 if diff:
2892 if diff:
2894 cols.append(
2893 cols.append(
2895 ('change', '%s', change, True,
2894 ('change', '%s', change, True,
2896 'grep.inserted ' if change == '+' else 'grep.deleted ')
2895 'grep.inserted ' if change == '+' else 'grep.deleted ')
2897 )
2896 )
2898 cols.extend([
2897 cols.extend([
2899 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2898 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2900 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2899 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2901 opts.get('date'), ''),
2900 opts.get('date'), ''),
2902 ])
2901 ])
2903 for name, fmt, data, cond, extra_label in cols:
2902 for name, fmt, data, cond, extra_label in cols:
2904 if cond:
2903 if cond:
2905 fm.plain(sep, label='grep.sep')
2904 fm.plain(sep, label='grep.sep')
2906 field = fieldnamemap.get(name, name)
2905 field = fieldnamemap.get(name, name)
2907 label = extra_label + ('grep.%s' % name)
2906 label = extra_label + ('grep.%s' % name)
2908 fm.condwrite(cond, field, fmt, data, label=label)
2907 fm.condwrite(cond, field, fmt, data, label=label)
2909 if not opts.get('files_with_matches'):
2908 if not opts.get('files_with_matches'):
2910 fm.plain(sep, label='grep.sep')
2909 fm.plain(sep, label='grep.sep')
2911 if not opts.get('text') and binary():
2910 if not opts.get('text') and binary():
2912 fm.plain(_(" Binary file matches"))
2911 fm.plain(_(" Binary file matches"))
2913 else:
2912 else:
2914 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2913 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2915 fm.plain(eol)
2914 fm.plain(eol)
2916 found = True
2915 found = True
2917 if opts.get('files_with_matches'):
2916 if opts.get('files_with_matches'):
2918 break
2917 break
2919 return found
2918 return found
2920
2919
2921 def displaymatches(fm, l):
2920 def displaymatches(fm, l):
2922 p = 0
2921 p = 0
2923 for s, e in l.findpos():
2922 for s, e in l.findpos():
2924 if p < s:
2923 if p < s:
2925 fm.startitem()
2924 fm.startitem()
2926 fm.write('text', '%s', l.line[p:s])
2925 fm.write('text', '%s', l.line[p:s])
2927 fm.data(matched=False)
2926 fm.data(matched=False)
2928 fm.startitem()
2927 fm.startitem()
2929 fm.write('text', '%s', l.line[s:e], label='grep.match')
2928 fm.write('text', '%s', l.line[s:e], label='grep.match')
2930 fm.data(matched=True)
2929 fm.data(matched=True)
2931 p = e
2930 p = e
2932 if p < len(l.line):
2931 if p < len(l.line):
2933 fm.startitem()
2932 fm.startitem()
2934 fm.write('text', '%s', l.line[p:])
2933 fm.write('text', '%s', l.line[p:])
2935 fm.data(matched=False)
2934 fm.data(matched=False)
2936 fm.end()
2935 fm.end()
2937
2936
2938 skip = set()
2937 skip = set()
2939 revfiles = {}
2938 revfiles = {}
2940 match = scmutil.match(repo[None], pats, opts)
2939 match = scmutil.match(repo[None], pats, opts)
2941 found = False
2940 found = False
2942 follow = opts.get('follow')
2941 follow = opts.get('follow')
2943
2942
2944 def prep(ctx, fns):
2943 def prep(ctx, fns):
2945 rev = ctx.rev()
2944 rev = ctx.rev()
2946 pctx = ctx.p1()
2945 pctx = ctx.p1()
2947 parent = pctx.rev()
2946 parent = pctx.rev()
2948 matches.setdefault(rev, {})
2947 matches.setdefault(rev, {})
2949 matches.setdefault(parent, {})
2948 matches.setdefault(parent, {})
2950 files = revfiles.setdefault(rev, [])
2949 files = revfiles.setdefault(rev, [])
2951 for fn in fns:
2950 for fn in fns:
2952 flog = getfile(fn)
2951 flog = getfile(fn)
2953 try:
2952 try:
2954 fnode = ctx.filenode(fn)
2953 fnode = ctx.filenode(fn)
2955 except error.LookupError:
2954 except error.LookupError:
2956 continue
2955 continue
2957 copy = None
2956 copy = None
2958 if follow:
2957 if follow:
2959 try:
2958 try:
2960 copied = flog.renamed(fnode)
2959 copied = flog.renamed(fnode)
2961 except error.WdirUnsupported:
2960 except error.WdirUnsupported:
2962 copied = ctx[fn].renamed()
2961 copied = ctx[fn].renamed()
2963 copy = copied and copied[0]
2962 copy = copied and copied[0]
2964 if copy:
2963 if copy:
2965 copies.setdefault(rev, {})[fn] = copy
2964 copies.setdefault(rev, {})[fn] = copy
2966 if fn in skip:
2965 if fn in skip:
2967 skip.add(copy)
2966 skip.add(copy)
2968 if fn in skip:
2967 if fn in skip:
2969 continue
2968 continue
2970 files.append(fn)
2969 files.append(fn)
2971
2970
2972 if fn not in matches[rev]:
2971 if fn not in matches[rev]:
2973 try:
2972 try:
2974 content = flog.read(fnode)
2973 content = flog.read(fnode)
2975 except error.WdirUnsupported:
2974 except error.WdirUnsupported:
2976 content = ctx[fn].data()
2975 content = ctx[fn].data()
2977 grepbody(fn, rev, content)
2976 grepbody(fn, rev, content)
2978
2977
2979 pfn = copy or fn
2978 pfn = copy or fn
2980 if pfn not in matches[parent]:
2979 if pfn not in matches[parent]:
2981 try:
2980 try:
2982 fnode = pctx.filenode(pfn)
2981 fnode = pctx.filenode(pfn)
2983 grepbody(pfn, parent, flog.read(fnode))
2982 grepbody(pfn, parent, flog.read(fnode))
2984 except error.LookupError:
2983 except error.LookupError:
2985 pass
2984 pass
2986
2985
2987 ui.pager('grep')
2986 ui.pager('grep')
2988 fm = ui.formatter('grep', opts)
2987 fm = ui.formatter('grep', opts)
2989 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2988 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2990 rev = ctx.rev()
2989 rev = ctx.rev()
2991 parent = ctx.p1().rev()
2990 parent = ctx.p1().rev()
2992 for fn in sorted(revfiles.get(rev, [])):
2991 for fn in sorted(revfiles.get(rev, [])):
2993 states = matches[rev][fn]
2992 states = matches[rev][fn]
2994 copy = copies.get(rev, {}).get(fn)
2993 copy = copies.get(rev, {}).get(fn)
2995 if fn in skip:
2994 if fn in skip:
2996 if copy:
2995 if copy:
2997 skip.add(copy)
2996 skip.add(copy)
2998 continue
2997 continue
2999 pstates = matches.get(parent, {}).get(copy or fn, [])
2998 pstates = matches.get(parent, {}).get(copy or fn, [])
3000 if pstates or states:
2999 if pstates or states:
3001 r = display(fm, fn, ctx, pstates, states)
3000 r = display(fm, fn, ctx, pstates, states)
3002 found = found or r
3001 found = found or r
3003 if r and not diff and not all_files:
3002 if r and not diff and not all_files:
3004 skip.add(fn)
3003 skip.add(fn)
3005 if copy:
3004 if copy:
3006 skip.add(copy)
3005 skip.add(copy)
3007 del revfiles[rev]
3006 del revfiles[rev]
3008 # We will keep the matches dict for the duration of the window
3007 # We will keep the matches dict for the duration of the window
3009 # clear the matches dict once the window is over
3008 # clear the matches dict once the window is over
3010 if not revfiles:
3009 if not revfiles:
3011 matches.clear()
3010 matches.clear()
3012 fm.end()
3011 fm.end()
3013
3012
3014 return not found
3013 return not found
3015
3014
3016 @command('heads',
3015 @command('heads',
3017 [('r', 'rev', '',
3016 [('r', 'rev', '',
3018 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3017 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3019 ('t', 'topo', False, _('show topological heads only')),
3018 ('t', 'topo', False, _('show topological heads only')),
3020 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3019 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3021 ('c', 'closed', False, _('show normal and closed branch heads')),
3020 ('c', 'closed', False, _('show normal and closed branch heads')),
3022 ] + templateopts,
3021 ] + templateopts,
3023 _('[-ct] [-r STARTREV] [REV]...'),
3022 _('[-ct] [-r STARTREV] [REV]...'),
3024 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3023 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3025 intents={INTENT_READONLY})
3024 intents={INTENT_READONLY})
3026 def heads(ui, repo, *branchrevs, **opts):
3025 def heads(ui, repo, *branchrevs, **opts):
3027 """show branch heads
3026 """show branch heads
3028
3027
3029 With no arguments, show all open branch heads in the repository.
3028 With no arguments, show all open branch heads in the repository.
3030 Branch heads are changesets that have no descendants on the
3029 Branch heads are changesets that have no descendants on the
3031 same branch. They are where development generally takes place and
3030 same branch. They are where development generally takes place and
3032 are the usual targets for update and merge operations.
3031 are the usual targets for update and merge operations.
3033
3032
3034 If one or more REVs are given, only open branch heads on the
3033 If one or more REVs are given, only open branch heads on the
3035 branches associated with the specified changesets are shown. This
3034 branches associated with the specified changesets are shown. This
3036 means that you can use :hg:`heads .` to see the heads on the
3035 means that you can use :hg:`heads .` to see the heads on the
3037 currently checked-out branch.
3036 currently checked-out branch.
3038
3037
3039 If -c/--closed is specified, also show branch heads marked closed
3038 If -c/--closed is specified, also show branch heads marked closed
3040 (see :hg:`commit --close-branch`).
3039 (see :hg:`commit --close-branch`).
3041
3040
3042 If STARTREV is specified, only those heads that are descendants of
3041 If STARTREV is specified, only those heads that are descendants of
3043 STARTREV will be displayed.
3042 STARTREV will be displayed.
3044
3043
3045 If -t/--topo is specified, named branch mechanics will be ignored and only
3044 If -t/--topo is specified, named branch mechanics will be ignored and only
3046 topological heads (changesets with no children) will be shown.
3045 topological heads (changesets with no children) will be shown.
3047
3046
3048 Returns 0 if matching heads are found, 1 if not.
3047 Returns 0 if matching heads are found, 1 if not.
3049 """
3048 """
3050
3049
3051 opts = pycompat.byteskwargs(opts)
3050 opts = pycompat.byteskwargs(opts)
3052 start = None
3051 start = None
3053 rev = opts.get('rev')
3052 rev = opts.get('rev')
3054 if rev:
3053 if rev:
3055 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3054 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3056 start = scmutil.revsingle(repo, rev, None).node()
3055 start = scmutil.revsingle(repo, rev, None).node()
3057
3056
3058 if opts.get('topo'):
3057 if opts.get('topo'):
3059 heads = [repo[h] for h in repo.heads(start)]
3058 heads = [repo[h] for h in repo.heads(start)]
3060 else:
3059 else:
3061 heads = []
3060 heads = []
3062 for branch in repo.branchmap():
3061 for branch in repo.branchmap():
3063 heads += repo.branchheads(branch, start, opts.get('closed'))
3062 heads += repo.branchheads(branch, start, opts.get('closed'))
3064 heads = [repo[h] for h in heads]
3063 heads = [repo[h] for h in heads]
3065
3064
3066 if branchrevs:
3065 if branchrevs:
3067 branches = set(repo[r].branch()
3066 branches = set(repo[r].branch()
3068 for r in scmutil.revrange(repo, branchrevs))
3067 for r in scmutil.revrange(repo, branchrevs))
3069 heads = [h for h in heads if h.branch() in branches]
3068 heads = [h for h in heads if h.branch() in branches]
3070
3069
3071 if opts.get('active') and branchrevs:
3070 if opts.get('active') and branchrevs:
3072 dagheads = repo.heads(start)
3071 dagheads = repo.heads(start)
3073 heads = [h for h in heads if h.node() in dagheads]
3072 heads = [h for h in heads if h.node() in dagheads]
3074
3073
3075 if branchrevs:
3074 if branchrevs:
3076 haveheads = set(h.branch() for h in heads)
3075 haveheads = set(h.branch() for h in heads)
3077 if branches - haveheads:
3076 if branches - haveheads:
3078 headless = ', '.join(b for b in branches - haveheads)
3077 headless = ', '.join(b for b in branches - haveheads)
3079 msg = _('no open branch heads found on branches %s')
3078 msg = _('no open branch heads found on branches %s')
3080 if opts.get('rev'):
3079 if opts.get('rev'):
3081 msg += _(' (started at %s)') % opts['rev']
3080 msg += _(' (started at %s)') % opts['rev']
3082 ui.warn((msg + '\n') % headless)
3081 ui.warn((msg + '\n') % headless)
3083
3082
3084 if not heads:
3083 if not heads:
3085 return 1
3084 return 1
3086
3085
3087 ui.pager('heads')
3086 ui.pager('heads')
3088 heads = sorted(heads, key=lambda x: -x.rev())
3087 heads = sorted(heads, key=lambda x: -x.rev())
3089 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3088 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3090 for ctx in heads:
3089 for ctx in heads:
3091 displayer.show(ctx)
3090 displayer.show(ctx)
3092 displayer.close()
3091 displayer.close()
3093
3092
3094 @command('help',
3093 @command('help',
3095 [('e', 'extension', None, _('show only help for extensions')),
3094 [('e', 'extension', None, _('show only help for extensions')),
3096 ('c', 'command', None, _('show only help for commands')),
3095 ('c', 'command', None, _('show only help for commands')),
3097 ('k', 'keyword', None, _('show topics matching keyword')),
3096 ('k', 'keyword', None, _('show topics matching keyword')),
3098 ('s', 'system', [],
3097 ('s', 'system', [],
3099 _('show help for specific platform(s)'), _('PLATFORM')),
3098 _('show help for specific platform(s)'), _('PLATFORM')),
3100 ],
3099 ],
3101 _('[-eck] [-s PLATFORM] [TOPIC]'),
3100 _('[-eck] [-s PLATFORM] [TOPIC]'),
3102 helpcategory=command.CATEGORY_HELP,
3101 helpcategory=command.CATEGORY_HELP,
3103 norepo=True,
3102 norepo=True,
3104 intents={INTENT_READONLY})
3103 intents={INTENT_READONLY})
3105 def help_(ui, name=None, **opts):
3104 def help_(ui, name=None, **opts):
3106 """show help for a given topic or a help overview
3105 """show help for a given topic or a help overview
3107
3106
3108 With no arguments, print a list of commands with short help messages.
3107 With no arguments, print a list of commands with short help messages.
3109
3108
3110 Given a topic, extension, or command name, print help for that
3109 Given a topic, extension, or command name, print help for that
3111 topic.
3110 topic.
3112
3111
3113 Returns 0 if successful.
3112 Returns 0 if successful.
3114 """
3113 """
3115
3114
3116 keep = opts.get(r'system') or []
3115 keep = opts.get(r'system') or []
3117 if len(keep) == 0:
3116 if len(keep) == 0:
3118 if pycompat.sysplatform.startswith('win'):
3117 if pycompat.sysplatform.startswith('win'):
3119 keep.append('windows')
3118 keep.append('windows')
3120 elif pycompat.sysplatform == 'OpenVMS':
3119 elif pycompat.sysplatform == 'OpenVMS':
3121 keep.append('vms')
3120 keep.append('vms')
3122 elif pycompat.sysplatform == 'plan9':
3121 elif pycompat.sysplatform == 'plan9':
3123 keep.append('plan9')
3122 keep.append('plan9')
3124 else:
3123 else:
3125 keep.append('unix')
3124 keep.append('unix')
3126 keep.append(pycompat.sysplatform.lower())
3125 keep.append(pycompat.sysplatform.lower())
3127 if ui.verbose:
3126 if ui.verbose:
3128 keep.append('verbose')
3127 keep.append('verbose')
3129
3128
3130 commands = sys.modules[__name__]
3129 commands = sys.modules[__name__]
3131 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3130 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3132 ui.pager('help')
3131 ui.pager('help')
3133 ui.write(formatted)
3132 ui.write(formatted)
3134
3133
3135
3134
3136 @command('identify|id',
3135 @command('identify|id',
3137 [('r', 'rev', '',
3136 [('r', 'rev', '',
3138 _('identify the specified revision'), _('REV')),
3137 _('identify the specified revision'), _('REV')),
3139 ('n', 'num', None, _('show local revision number')),
3138 ('n', 'num', None, _('show local revision number')),
3140 ('i', 'id', None, _('show global revision id')),
3139 ('i', 'id', None, _('show global revision id')),
3141 ('b', 'branch', None, _('show branch')),
3140 ('b', 'branch', None, _('show branch')),
3142 ('t', 'tags', None, _('show tags')),
3141 ('t', 'tags', None, _('show tags')),
3143 ('B', 'bookmarks', None, _('show bookmarks')),
3142 ('B', 'bookmarks', None, _('show bookmarks')),
3144 ] + remoteopts + formatteropts,
3143 ] + remoteopts + formatteropts,
3145 _('[-nibtB] [-r REV] [SOURCE]'),
3144 _('[-nibtB] [-r REV] [SOURCE]'),
3146 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3145 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3147 optionalrepo=True,
3146 optionalrepo=True,
3148 intents={INTENT_READONLY})
3147 intents={INTENT_READONLY})
3149 def identify(ui, repo, source=None, rev=None,
3148 def identify(ui, repo, source=None, rev=None,
3150 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3149 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3151 """identify the working directory or specified revision
3150 """identify the working directory or specified revision
3152
3151
3153 Print a summary identifying the repository state at REV using one or
3152 Print a summary identifying the repository state at REV using one or
3154 two parent hash identifiers, followed by a "+" if the working
3153 two parent hash identifiers, followed by a "+" if the working
3155 directory has uncommitted changes, the branch name (if not default),
3154 directory has uncommitted changes, the branch name (if not default),
3156 a list of tags, and a list of bookmarks.
3155 a list of tags, and a list of bookmarks.
3157
3156
3158 When REV is not given, print a summary of the current state of the
3157 When REV is not given, print a summary of the current state of the
3159 repository including the working directory. Specify -r. to get information
3158 repository including the working directory. Specify -r. to get information
3160 of the working directory parent without scanning uncommitted changes.
3159 of the working directory parent without scanning uncommitted changes.
3161
3160
3162 Specifying a path to a repository root or Mercurial bundle will
3161 Specifying a path to a repository root or Mercurial bundle will
3163 cause lookup to operate on that repository/bundle.
3162 cause lookup to operate on that repository/bundle.
3164
3163
3165 .. container:: verbose
3164 .. container:: verbose
3166
3165
3167 Template:
3166 Template:
3168
3167
3169 The following keywords are supported in addition to the common template
3168 The following keywords are supported in addition to the common template
3170 keywords and functions. See also :hg:`help templates`.
3169 keywords and functions. See also :hg:`help templates`.
3171
3170
3172 :dirty: String. Character ``+`` denoting if the working directory has
3171 :dirty: String. Character ``+`` denoting if the working directory has
3173 uncommitted changes.
3172 uncommitted changes.
3174 :id: String. One or two nodes, optionally followed by ``+``.
3173 :id: String. One or two nodes, optionally followed by ``+``.
3175 :parents: List of strings. Parent nodes of the changeset.
3174 :parents: List of strings. Parent nodes of the changeset.
3176
3175
3177 Examples:
3176 Examples:
3178
3177
3179 - generate a build identifier for the working directory::
3178 - generate a build identifier for the working directory::
3180
3179
3181 hg id --id > build-id.dat
3180 hg id --id > build-id.dat
3182
3181
3183 - find the revision corresponding to a tag::
3182 - find the revision corresponding to a tag::
3184
3183
3185 hg id -n -r 1.3
3184 hg id -n -r 1.3
3186
3185
3187 - check the most recent revision of a remote repository::
3186 - check the most recent revision of a remote repository::
3188
3187
3189 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3188 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3190
3189
3191 See :hg:`log` for generating more information about specific revisions,
3190 See :hg:`log` for generating more information about specific revisions,
3192 including full hash identifiers.
3191 including full hash identifiers.
3193
3192
3194 Returns 0 if successful.
3193 Returns 0 if successful.
3195 """
3194 """
3196
3195
3197 opts = pycompat.byteskwargs(opts)
3196 opts = pycompat.byteskwargs(opts)
3198 if not repo and not source:
3197 if not repo and not source:
3199 raise error.Abort(_("there is no Mercurial repository here "
3198 raise error.Abort(_("there is no Mercurial repository here "
3200 "(.hg not found)"))
3199 "(.hg not found)"))
3201
3200
3202 default = not (num or id or branch or tags or bookmarks)
3201 default = not (num or id or branch or tags or bookmarks)
3203 output = []
3202 output = []
3204 revs = []
3203 revs = []
3205
3204
3206 if source:
3205 if source:
3207 source, branches = hg.parseurl(ui.expandpath(source))
3206 source, branches = hg.parseurl(ui.expandpath(source))
3208 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3207 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3209 repo = peer.local()
3208 repo = peer.local()
3210 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3209 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3211
3210
3212 fm = ui.formatter('identify', opts)
3211 fm = ui.formatter('identify', opts)
3213 fm.startitem()
3212 fm.startitem()
3214
3213
3215 if not repo:
3214 if not repo:
3216 if num or branch or tags:
3215 if num or branch or tags:
3217 raise error.Abort(
3216 raise error.Abort(
3218 _("can't query remote revision number, branch, or tags"))
3217 _("can't query remote revision number, branch, or tags"))
3219 if not rev and revs:
3218 if not rev and revs:
3220 rev = revs[0]
3219 rev = revs[0]
3221 if not rev:
3220 if not rev:
3222 rev = "tip"
3221 rev = "tip"
3223
3222
3224 remoterev = peer.lookup(rev)
3223 remoterev = peer.lookup(rev)
3225 hexrev = fm.hexfunc(remoterev)
3224 hexrev = fm.hexfunc(remoterev)
3226 if default or id:
3225 if default or id:
3227 output = [hexrev]
3226 output = [hexrev]
3228 fm.data(id=hexrev)
3227 fm.data(id=hexrev)
3229
3228
3230 @util.cachefunc
3229 @util.cachefunc
3231 def getbms():
3230 def getbms():
3232 bms = []
3231 bms = []
3233
3232
3234 if 'bookmarks' in peer.listkeys('namespaces'):
3233 if 'bookmarks' in peer.listkeys('namespaces'):
3235 hexremoterev = hex(remoterev)
3234 hexremoterev = hex(remoterev)
3236 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3235 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3237 if bmr == hexremoterev]
3236 if bmr == hexremoterev]
3238
3237
3239 return sorted(bms)
3238 return sorted(bms)
3240
3239
3241 if fm.isplain():
3240 if fm.isplain():
3242 if bookmarks:
3241 if bookmarks:
3243 output.extend(getbms())
3242 output.extend(getbms())
3244 elif default and not ui.quiet:
3243 elif default and not ui.quiet:
3245 # multiple bookmarks for a single parent separated by '/'
3244 # multiple bookmarks for a single parent separated by '/'
3246 bm = '/'.join(getbms())
3245 bm = '/'.join(getbms())
3247 if bm:
3246 if bm:
3248 output.append(bm)
3247 output.append(bm)
3249 else:
3248 else:
3250 fm.data(node=hex(remoterev))
3249 fm.data(node=hex(remoterev))
3251 if bookmarks or 'bookmarks' in fm.datahint():
3250 if bookmarks or 'bookmarks' in fm.datahint():
3252 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3251 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3253 else:
3252 else:
3254 if rev:
3253 if rev:
3255 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3254 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3256 ctx = scmutil.revsingle(repo, rev, None)
3255 ctx = scmutil.revsingle(repo, rev, None)
3257
3256
3258 if ctx.rev() is None:
3257 if ctx.rev() is None:
3259 ctx = repo[None]
3258 ctx = repo[None]
3260 parents = ctx.parents()
3259 parents = ctx.parents()
3261 taglist = []
3260 taglist = []
3262 for p in parents:
3261 for p in parents:
3263 taglist.extend(p.tags())
3262 taglist.extend(p.tags())
3264
3263
3265 dirty = ""
3264 dirty = ""
3266 if ctx.dirty(missing=True, merge=False, branch=False):
3265 if ctx.dirty(missing=True, merge=False, branch=False):
3267 dirty = '+'
3266 dirty = '+'
3268 fm.data(dirty=dirty)
3267 fm.data(dirty=dirty)
3269
3268
3270 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3269 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3271 if default or id:
3270 if default or id:
3272 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3271 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3273 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3272 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3274
3273
3275 if num:
3274 if num:
3276 numoutput = ["%d" % p.rev() for p in parents]
3275 numoutput = ["%d" % p.rev() for p in parents]
3277 output.append("%s%s" % ('+'.join(numoutput), dirty))
3276 output.append("%s%s" % ('+'.join(numoutput), dirty))
3278
3277
3279 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3278 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3280 for p in parents], name='node'))
3279 for p in parents], name='node'))
3281 else:
3280 else:
3282 hexoutput = fm.hexfunc(ctx.node())
3281 hexoutput = fm.hexfunc(ctx.node())
3283 if default or id:
3282 if default or id:
3284 output = [hexoutput]
3283 output = [hexoutput]
3285 fm.data(id=hexoutput)
3284 fm.data(id=hexoutput)
3286
3285
3287 if num:
3286 if num:
3288 output.append(pycompat.bytestr(ctx.rev()))
3287 output.append(pycompat.bytestr(ctx.rev()))
3289 taglist = ctx.tags()
3288 taglist = ctx.tags()
3290
3289
3291 if default and not ui.quiet:
3290 if default and not ui.quiet:
3292 b = ctx.branch()
3291 b = ctx.branch()
3293 if b != 'default':
3292 if b != 'default':
3294 output.append("(%s)" % b)
3293 output.append("(%s)" % b)
3295
3294
3296 # multiple tags for a single parent separated by '/'
3295 # multiple tags for a single parent separated by '/'
3297 t = '/'.join(taglist)
3296 t = '/'.join(taglist)
3298 if t:
3297 if t:
3299 output.append(t)
3298 output.append(t)
3300
3299
3301 # multiple bookmarks for a single parent separated by '/'
3300 # multiple bookmarks for a single parent separated by '/'
3302 bm = '/'.join(ctx.bookmarks())
3301 bm = '/'.join(ctx.bookmarks())
3303 if bm:
3302 if bm:
3304 output.append(bm)
3303 output.append(bm)
3305 else:
3304 else:
3306 if branch:
3305 if branch:
3307 output.append(ctx.branch())
3306 output.append(ctx.branch())
3308
3307
3309 if tags:
3308 if tags:
3310 output.extend(taglist)
3309 output.extend(taglist)
3311
3310
3312 if bookmarks:
3311 if bookmarks:
3313 output.extend(ctx.bookmarks())
3312 output.extend(ctx.bookmarks())
3314
3313
3315 fm.data(node=ctx.hex())
3314 fm.data(node=ctx.hex())
3316 fm.data(branch=ctx.branch())
3315 fm.data(branch=ctx.branch())
3317 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3316 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3318 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3317 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3319 fm.context(ctx=ctx)
3318 fm.context(ctx=ctx)
3320
3319
3321 fm.plain("%s\n" % ' '.join(output))
3320 fm.plain("%s\n" % ' '.join(output))
3322 fm.end()
3321 fm.end()
3323
3322
3324 @command('import|patch',
3323 @command('import|patch',
3325 [('p', 'strip', 1,
3324 [('p', 'strip', 1,
3326 _('directory strip option for patch. This has the same '
3325 _('directory strip option for patch. This has the same '
3327 'meaning as the corresponding patch option'), _('NUM')),
3326 'meaning as the corresponding patch option'), _('NUM')),
3328 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3327 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3329 ('e', 'edit', False, _('invoke editor on commit messages')),
3328 ('e', 'edit', False, _('invoke editor on commit messages')),
3330 ('f', 'force', None,
3329 ('f', 'force', None,
3331 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3330 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3332 ('', 'no-commit', None,
3331 ('', 'no-commit', None,
3333 _("don't commit, just update the working directory")),
3332 _("don't commit, just update the working directory")),
3334 ('', 'bypass', None,
3333 ('', 'bypass', None,
3335 _("apply patch without touching the working directory")),
3334 _("apply patch without touching the working directory")),
3336 ('', 'partial', None,
3335 ('', 'partial', None,
3337 _('commit even if some hunks fail')),
3336 _('commit even if some hunks fail')),
3338 ('', 'exact', None,
3337 ('', 'exact', None,
3339 _('abort if patch would apply lossily')),
3338 _('abort if patch would apply lossily')),
3340 ('', 'prefix', '',
3339 ('', 'prefix', '',
3341 _('apply patch to subdirectory'), _('DIR')),
3340 _('apply patch to subdirectory'), _('DIR')),
3342 ('', 'import-branch', None,
3341 ('', 'import-branch', None,
3343 _('use any branch information in patch (implied by --exact)'))] +
3342 _('use any branch information in patch (implied by --exact)'))] +
3344 commitopts + commitopts2 + similarityopts,
3343 commitopts + commitopts2 + similarityopts,
3345 _('[OPTION]... PATCH...'),
3344 _('[OPTION]... PATCH...'),
3346 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3345 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3347 def import_(ui, repo, patch1=None, *patches, **opts):
3346 def import_(ui, repo, patch1=None, *patches, **opts):
3348 """import an ordered set of patches
3347 """import an ordered set of patches
3349
3348
3350 Import a list of patches and commit them individually (unless
3349 Import a list of patches and commit them individually (unless
3351 --no-commit is specified).
3350 --no-commit is specified).
3352
3351
3353 To read a patch from standard input (stdin), use "-" as the patch
3352 To read a patch from standard input (stdin), use "-" as the patch
3354 name. If a URL is specified, the patch will be downloaded from
3353 name. If a URL is specified, the patch will be downloaded from
3355 there.
3354 there.
3356
3355
3357 Import first applies changes to the working directory (unless
3356 Import first applies changes to the working directory (unless
3358 --bypass is specified), import will abort if there are outstanding
3357 --bypass is specified), import will abort if there are outstanding
3359 changes.
3358 changes.
3360
3359
3361 Use --bypass to apply and commit patches directly to the
3360 Use --bypass to apply and commit patches directly to the
3362 repository, without affecting the working directory. Without
3361 repository, without affecting the working directory. Without
3363 --exact, patches will be applied on top of the working directory
3362 --exact, patches will be applied on top of the working directory
3364 parent revision.
3363 parent revision.
3365
3364
3366 You can import a patch straight from a mail message. Even patches
3365 You can import a patch straight from a mail message. Even patches
3367 as attachments work (to use the body part, it must have type
3366 as attachments work (to use the body part, it must have type
3368 text/plain or text/x-patch). From and Subject headers of email
3367 text/plain or text/x-patch). From and Subject headers of email
3369 message are used as default committer and commit message. All
3368 message are used as default committer and commit message. All
3370 text/plain body parts before first diff are added to the commit
3369 text/plain body parts before first diff are added to the commit
3371 message.
3370 message.
3372
3371
3373 If the imported patch was generated by :hg:`export`, user and
3372 If the imported patch was generated by :hg:`export`, user and
3374 description from patch override values from message headers and
3373 description from patch override values from message headers and
3375 body. Values given on command line with -m/--message and -u/--user
3374 body. Values given on command line with -m/--message and -u/--user
3376 override these.
3375 override these.
3377
3376
3378 If --exact is specified, import will set the working directory to
3377 If --exact is specified, import will set the working directory to
3379 the parent of each patch before applying it, and will abort if the
3378 the parent of each patch before applying it, and will abort if the
3380 resulting changeset has a different ID than the one recorded in
3379 resulting changeset has a different ID than the one recorded in
3381 the patch. This will guard against various ways that portable
3380 the patch. This will guard against various ways that portable
3382 patch formats and mail systems might fail to transfer Mercurial
3381 patch formats and mail systems might fail to transfer Mercurial
3383 data or metadata. See :hg:`bundle` for lossless transmission.
3382 data or metadata. See :hg:`bundle` for lossless transmission.
3384
3383
3385 Use --partial to ensure a changeset will be created from the patch
3384 Use --partial to ensure a changeset will be created from the patch
3386 even if some hunks fail to apply. Hunks that fail to apply will be
3385 even if some hunks fail to apply. Hunks that fail to apply will be
3387 written to a <target-file>.rej file. Conflicts can then be resolved
3386 written to a <target-file>.rej file. Conflicts can then be resolved
3388 by hand before :hg:`commit --amend` is run to update the created
3387 by hand before :hg:`commit --amend` is run to update the created
3389 changeset. This flag exists to let people import patches that
3388 changeset. This flag exists to let people import patches that
3390 partially apply without losing the associated metadata (author,
3389 partially apply without losing the associated metadata (author,
3391 date, description, ...).
3390 date, description, ...).
3392
3391
3393 .. note::
3392 .. note::
3394
3393
3395 When no hunks apply cleanly, :hg:`import --partial` will create
3394 When no hunks apply cleanly, :hg:`import --partial` will create
3396 an empty changeset, importing only the patch metadata.
3395 an empty changeset, importing only the patch metadata.
3397
3396
3398 With -s/--similarity, hg will attempt to discover renames and
3397 With -s/--similarity, hg will attempt to discover renames and
3399 copies in the patch in the same way as :hg:`addremove`.
3398 copies in the patch in the same way as :hg:`addremove`.
3400
3399
3401 It is possible to use external patch programs to perform the patch
3400 It is possible to use external patch programs to perform the patch
3402 by setting the ``ui.patch`` configuration option. For the default
3401 by setting the ``ui.patch`` configuration option. For the default
3403 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3402 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3404 See :hg:`help config` for more information about configuration
3403 See :hg:`help config` for more information about configuration
3405 files and how to use these options.
3404 files and how to use these options.
3406
3405
3407 See :hg:`help dates` for a list of formats valid for -d/--date.
3406 See :hg:`help dates` for a list of formats valid for -d/--date.
3408
3407
3409 .. container:: verbose
3408 .. container:: verbose
3410
3409
3411 Examples:
3410 Examples:
3412
3411
3413 - import a traditional patch from a website and detect renames::
3412 - import a traditional patch from a website and detect renames::
3414
3413
3415 hg import -s 80 http://example.com/bugfix.patch
3414 hg import -s 80 http://example.com/bugfix.patch
3416
3415
3417 - import a changeset from an hgweb server::
3416 - import a changeset from an hgweb server::
3418
3417
3419 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3418 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3420
3419
3421 - import all the patches in an Unix-style mbox::
3420 - import all the patches in an Unix-style mbox::
3422
3421
3423 hg import incoming-patches.mbox
3422 hg import incoming-patches.mbox
3424
3423
3425 - import patches from stdin::
3424 - import patches from stdin::
3426
3425
3427 hg import -
3426 hg import -
3428
3427
3429 - attempt to exactly restore an exported changeset (not always
3428 - attempt to exactly restore an exported changeset (not always
3430 possible)::
3429 possible)::
3431
3430
3432 hg import --exact proposed-fix.patch
3431 hg import --exact proposed-fix.patch
3433
3432
3434 - use an external tool to apply a patch which is too fuzzy for
3433 - use an external tool to apply a patch which is too fuzzy for
3435 the default internal tool.
3434 the default internal tool.
3436
3435
3437 hg import --config ui.patch="patch --merge" fuzzy.patch
3436 hg import --config ui.patch="patch --merge" fuzzy.patch
3438
3437
3439 - change the default fuzzing from 2 to a less strict 7
3438 - change the default fuzzing from 2 to a less strict 7
3440
3439
3441 hg import --config ui.fuzz=7 fuzz.patch
3440 hg import --config ui.fuzz=7 fuzz.patch
3442
3441
3443 Returns 0 on success, 1 on partial success (see --partial).
3442 Returns 0 on success, 1 on partial success (see --partial).
3444 """
3443 """
3445
3444
3446 opts = pycompat.byteskwargs(opts)
3445 opts = pycompat.byteskwargs(opts)
3447 if not patch1:
3446 if not patch1:
3448 raise error.Abort(_('need at least one patch to import'))
3447 raise error.Abort(_('need at least one patch to import'))
3449
3448
3450 patches = (patch1,) + patches
3449 patches = (patch1,) + patches
3451
3450
3452 date = opts.get('date')
3451 date = opts.get('date')
3453 if date:
3452 if date:
3454 opts['date'] = dateutil.parsedate(date)
3453 opts['date'] = dateutil.parsedate(date)
3455
3454
3456 exact = opts.get('exact')
3455 exact = opts.get('exact')
3457 update = not opts.get('bypass')
3456 update = not opts.get('bypass')
3458 if not update and opts.get('no_commit'):
3457 if not update and opts.get('no_commit'):
3459 raise error.Abort(_('cannot use --no-commit with --bypass'))
3458 raise error.Abort(_('cannot use --no-commit with --bypass'))
3460 try:
3459 try:
3461 sim = float(opts.get('similarity') or 0)
3460 sim = float(opts.get('similarity') or 0)
3462 except ValueError:
3461 except ValueError:
3463 raise error.Abort(_('similarity must be a number'))
3462 raise error.Abort(_('similarity must be a number'))
3464 if sim < 0 or sim > 100:
3463 if sim < 0 or sim > 100:
3465 raise error.Abort(_('similarity must be between 0 and 100'))
3464 raise error.Abort(_('similarity must be between 0 and 100'))
3466 if sim and not update:
3465 if sim and not update:
3467 raise error.Abort(_('cannot use --similarity with --bypass'))
3466 raise error.Abort(_('cannot use --similarity with --bypass'))
3468 if exact:
3467 if exact:
3469 if opts.get('edit'):
3468 if opts.get('edit'):
3470 raise error.Abort(_('cannot use --exact with --edit'))
3469 raise error.Abort(_('cannot use --exact with --edit'))
3471 if opts.get('prefix'):
3470 if opts.get('prefix'):
3472 raise error.Abort(_('cannot use --exact with --prefix'))
3471 raise error.Abort(_('cannot use --exact with --prefix'))
3473
3472
3474 base = opts["base"]
3473 base = opts["base"]
3475 msgs = []
3474 msgs = []
3476 ret = 0
3475 ret = 0
3477
3476
3478 with repo.wlock():
3477 with repo.wlock():
3479 if update:
3478 if update:
3480 cmdutil.checkunfinished(repo)
3479 cmdutil.checkunfinished(repo)
3481 if (exact or not opts.get('force')):
3480 if (exact or not opts.get('force')):
3482 cmdutil.bailifchanged(repo)
3481 cmdutil.bailifchanged(repo)
3483
3482
3484 if not opts.get('no_commit'):
3483 if not opts.get('no_commit'):
3485 lock = repo.lock
3484 lock = repo.lock
3486 tr = lambda: repo.transaction('import')
3485 tr = lambda: repo.transaction('import')
3487 dsguard = util.nullcontextmanager
3486 dsguard = util.nullcontextmanager
3488 else:
3487 else:
3489 lock = util.nullcontextmanager
3488 lock = util.nullcontextmanager
3490 tr = util.nullcontextmanager
3489 tr = util.nullcontextmanager
3491 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3490 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3492 with lock(), tr(), dsguard():
3491 with lock(), tr(), dsguard():
3493 parents = repo[None].parents()
3492 parents = repo[None].parents()
3494 for patchurl in patches:
3493 for patchurl in patches:
3495 if patchurl == '-':
3494 if patchurl == '-':
3496 ui.status(_('applying patch from stdin\n'))
3495 ui.status(_('applying patch from stdin\n'))
3497 patchfile = ui.fin
3496 patchfile = ui.fin
3498 patchurl = 'stdin' # for error message
3497 patchurl = 'stdin' # for error message
3499 else:
3498 else:
3500 patchurl = os.path.join(base, patchurl)
3499 patchurl = os.path.join(base, patchurl)
3501 ui.status(_('applying %s\n') % patchurl)
3500 ui.status(_('applying %s\n') % patchurl)
3502 patchfile = hg.openpath(ui, patchurl)
3501 patchfile = hg.openpath(ui, patchurl)
3503
3502
3504 haspatch = False
3503 haspatch = False
3505 for hunk in patch.split(patchfile):
3504 for hunk in patch.split(patchfile):
3506 with patch.extract(ui, hunk) as patchdata:
3505 with patch.extract(ui, hunk) as patchdata:
3507 msg, node, rej = cmdutil.tryimportone(ui, repo,
3506 msg, node, rej = cmdutil.tryimportone(ui, repo,
3508 patchdata,
3507 patchdata,
3509 parents, opts,
3508 parents, opts,
3510 msgs, hg.clean)
3509 msgs, hg.clean)
3511 if msg:
3510 if msg:
3512 haspatch = True
3511 haspatch = True
3513 ui.note(msg + '\n')
3512 ui.note(msg + '\n')
3514 if update or exact:
3513 if update or exact:
3515 parents = repo[None].parents()
3514 parents = repo[None].parents()
3516 else:
3515 else:
3517 parents = [repo[node]]
3516 parents = [repo[node]]
3518 if rej:
3517 if rej:
3519 ui.write_err(_("patch applied partially\n"))
3518 ui.write_err(_("patch applied partially\n"))
3520 ui.write_err(_("(fix the .rej files and run "
3519 ui.write_err(_("(fix the .rej files and run "
3521 "`hg commit --amend`)\n"))
3520 "`hg commit --amend`)\n"))
3522 ret = 1
3521 ret = 1
3523 break
3522 break
3524
3523
3525 if not haspatch:
3524 if not haspatch:
3526 raise error.Abort(_('%s: no diffs found') % patchurl)
3525 raise error.Abort(_('%s: no diffs found') % patchurl)
3527
3526
3528 if msgs:
3527 if msgs:
3529 repo.savecommitmessage('\n* * *\n'.join(msgs))
3528 repo.savecommitmessage('\n* * *\n'.join(msgs))
3530 return ret
3529 return ret
3531
3530
3532 @command('incoming|in',
3531 @command('incoming|in',
3533 [('f', 'force', None,
3532 [('f', 'force', None,
3534 _('run even if remote repository is unrelated')),
3533 _('run even if remote repository is unrelated')),
3535 ('n', 'newest-first', None, _('show newest record first')),
3534 ('n', 'newest-first', None, _('show newest record first')),
3536 ('', 'bundle', '',
3535 ('', 'bundle', '',
3537 _('file to store the bundles into'), _('FILE')),
3536 _('file to store the bundles into'), _('FILE')),
3538 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3537 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3539 ('B', 'bookmarks', False, _("compare bookmarks")),
3538 ('B', 'bookmarks', False, _("compare bookmarks")),
3540 ('b', 'branch', [],
3539 ('b', 'branch', [],
3541 _('a specific branch you would like to pull'), _('BRANCH')),
3540 _('a specific branch you would like to pull'), _('BRANCH')),
3542 ] + logopts + remoteopts + subrepoopts,
3541 ] + logopts + remoteopts + subrepoopts,
3543 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3542 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3544 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3543 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3545 def incoming(ui, repo, source="default", **opts):
3544 def incoming(ui, repo, source="default", **opts):
3546 """show new changesets found in source
3545 """show new changesets found in source
3547
3546
3548 Show new changesets found in the specified path/URL or the default
3547 Show new changesets found in the specified path/URL or the default
3549 pull location. These are the changesets that would have been pulled
3548 pull location. These are the changesets that would have been pulled
3550 by :hg:`pull` at the time you issued this command.
3549 by :hg:`pull` at the time you issued this command.
3551
3550
3552 See pull for valid source format details.
3551 See pull for valid source format details.
3553
3552
3554 .. container:: verbose
3553 .. container:: verbose
3555
3554
3556 With -B/--bookmarks, the result of bookmark comparison between
3555 With -B/--bookmarks, the result of bookmark comparison between
3557 local and remote repositories is displayed. With -v/--verbose,
3556 local and remote repositories is displayed. With -v/--verbose,
3558 status is also displayed for each bookmark like below::
3557 status is also displayed for each bookmark like below::
3559
3558
3560 BM1 01234567890a added
3559 BM1 01234567890a added
3561 BM2 1234567890ab advanced
3560 BM2 1234567890ab advanced
3562 BM3 234567890abc diverged
3561 BM3 234567890abc diverged
3563 BM4 34567890abcd changed
3562 BM4 34567890abcd changed
3564
3563
3565 The action taken locally when pulling depends on the
3564 The action taken locally when pulling depends on the
3566 status of each bookmark:
3565 status of each bookmark:
3567
3566
3568 :``added``: pull will create it
3567 :``added``: pull will create it
3569 :``advanced``: pull will update it
3568 :``advanced``: pull will update it
3570 :``diverged``: pull will create a divergent bookmark
3569 :``diverged``: pull will create a divergent bookmark
3571 :``changed``: result depends on remote changesets
3570 :``changed``: result depends on remote changesets
3572
3571
3573 From the point of view of pulling behavior, bookmark
3572 From the point of view of pulling behavior, bookmark
3574 existing only in the remote repository are treated as ``added``,
3573 existing only in the remote repository are treated as ``added``,
3575 even if it is in fact locally deleted.
3574 even if it is in fact locally deleted.
3576
3575
3577 .. container:: verbose
3576 .. container:: verbose
3578
3577
3579 For remote repository, using --bundle avoids downloading the
3578 For remote repository, using --bundle avoids downloading the
3580 changesets twice if the incoming is followed by a pull.
3579 changesets twice if the incoming is followed by a pull.
3581
3580
3582 Examples:
3581 Examples:
3583
3582
3584 - show incoming changes with patches and full description::
3583 - show incoming changes with patches and full description::
3585
3584
3586 hg incoming -vp
3585 hg incoming -vp
3587
3586
3588 - show incoming changes excluding merges, store a bundle::
3587 - show incoming changes excluding merges, store a bundle::
3589
3588
3590 hg in -vpM --bundle incoming.hg
3589 hg in -vpM --bundle incoming.hg
3591 hg pull incoming.hg
3590 hg pull incoming.hg
3592
3591
3593 - briefly list changes inside a bundle::
3592 - briefly list changes inside a bundle::
3594
3593
3595 hg in changes.hg -T "{desc|firstline}\\n"
3594 hg in changes.hg -T "{desc|firstline}\\n"
3596
3595
3597 Returns 0 if there are incoming changes, 1 otherwise.
3596 Returns 0 if there are incoming changes, 1 otherwise.
3598 """
3597 """
3599 opts = pycompat.byteskwargs(opts)
3598 opts = pycompat.byteskwargs(opts)
3600 if opts.get('graph'):
3599 if opts.get('graph'):
3601 logcmdutil.checkunsupportedgraphflags([], opts)
3600 logcmdutil.checkunsupportedgraphflags([], opts)
3602 def display(other, chlist, displayer):
3601 def display(other, chlist, displayer):
3603 revdag = logcmdutil.graphrevs(other, chlist, opts)
3602 revdag = logcmdutil.graphrevs(other, chlist, opts)
3604 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3603 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3605 graphmod.asciiedges)
3604 graphmod.asciiedges)
3606
3605
3607 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3606 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3608 return 0
3607 return 0
3609
3608
3610 if opts.get('bundle') and opts.get('subrepos'):
3609 if opts.get('bundle') and opts.get('subrepos'):
3611 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3610 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3612
3611
3613 if opts.get('bookmarks'):
3612 if opts.get('bookmarks'):
3614 source, branches = hg.parseurl(ui.expandpath(source),
3613 source, branches = hg.parseurl(ui.expandpath(source),
3615 opts.get('branch'))
3614 opts.get('branch'))
3616 other = hg.peer(repo, opts, source)
3615 other = hg.peer(repo, opts, source)
3617 if 'bookmarks' not in other.listkeys('namespaces'):
3616 if 'bookmarks' not in other.listkeys('namespaces'):
3618 ui.warn(_("remote doesn't support bookmarks\n"))
3617 ui.warn(_("remote doesn't support bookmarks\n"))
3619 return 0
3618 return 0
3620 ui.pager('incoming')
3619 ui.pager('incoming')
3621 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3620 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3622 return bookmarks.incoming(ui, repo, other)
3621 return bookmarks.incoming(ui, repo, other)
3623
3622
3624 repo._subtoppath = ui.expandpath(source)
3623 repo._subtoppath = ui.expandpath(source)
3625 try:
3624 try:
3626 return hg.incoming(ui, repo, source, opts)
3625 return hg.incoming(ui, repo, source, opts)
3627 finally:
3626 finally:
3628 del repo._subtoppath
3627 del repo._subtoppath
3629
3628
3630
3629
3631 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3630 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3632 helpcategory=command.CATEGORY_REPO_CREATION,
3631 helpcategory=command.CATEGORY_REPO_CREATION,
3633 helpbasic=True, norepo=True)
3632 helpbasic=True, norepo=True)
3634 def init(ui, dest=".", **opts):
3633 def init(ui, dest=".", **opts):
3635 """create a new repository in the given directory
3634 """create a new repository in the given directory
3636
3635
3637 Initialize a new repository in the given directory. If the given
3636 Initialize a new repository in the given directory. If the given
3638 directory does not exist, it will be created.
3637 directory does not exist, it will be created.
3639
3638
3640 If no directory is given, the current directory is used.
3639 If no directory is given, the current directory is used.
3641
3640
3642 It is possible to specify an ``ssh://`` URL as the destination.
3641 It is possible to specify an ``ssh://`` URL as the destination.
3643 See :hg:`help urls` for more information.
3642 See :hg:`help urls` for more information.
3644
3643
3645 Returns 0 on success.
3644 Returns 0 on success.
3646 """
3645 """
3647 opts = pycompat.byteskwargs(opts)
3646 opts = pycompat.byteskwargs(opts)
3648 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3647 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3649
3648
3650 @command('locate',
3649 @command('locate',
3651 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3650 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3652 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3651 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3653 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3652 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3654 ] + walkopts,
3653 ] + walkopts,
3655 _('[OPTION]... [PATTERN]...'),
3654 _('[OPTION]... [PATTERN]...'),
3656 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3655 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3657 def locate(ui, repo, *pats, **opts):
3656 def locate(ui, repo, *pats, **opts):
3658 """locate files matching specific patterns (DEPRECATED)
3657 """locate files matching specific patterns (DEPRECATED)
3659
3658
3660 Print files under Mercurial control in the working directory whose
3659 Print files under Mercurial control in the working directory whose
3661 names match the given patterns.
3660 names match the given patterns.
3662
3661
3663 By default, this command searches all directories in the working
3662 By default, this command searches all directories in the working
3664 directory. To search just the current directory and its
3663 directory. To search just the current directory and its
3665 subdirectories, use "--include .".
3664 subdirectories, use "--include .".
3666
3665
3667 If no patterns are given to match, this command prints the names
3666 If no patterns are given to match, this command prints the names
3668 of all files under Mercurial control in the working directory.
3667 of all files under Mercurial control in the working directory.
3669
3668
3670 If you want to feed the output of this command into the "xargs"
3669 If you want to feed the output of this command into the "xargs"
3671 command, use the -0 option to both this command and "xargs". This
3670 command, use the -0 option to both this command and "xargs". This
3672 will avoid the problem of "xargs" treating single filenames that
3671 will avoid the problem of "xargs" treating single filenames that
3673 contain whitespace as multiple filenames.
3672 contain whitespace as multiple filenames.
3674
3673
3675 See :hg:`help files` for a more versatile command.
3674 See :hg:`help files` for a more versatile command.
3676
3675
3677 Returns 0 if a match is found, 1 otherwise.
3676 Returns 0 if a match is found, 1 otherwise.
3678 """
3677 """
3679 opts = pycompat.byteskwargs(opts)
3678 opts = pycompat.byteskwargs(opts)
3680 if opts.get('print0'):
3679 if opts.get('print0'):
3681 end = '\0'
3680 end = '\0'
3682 else:
3681 else:
3683 end = '\n'
3682 end = '\n'
3684 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3683 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3685
3684
3686 ret = 1
3685 ret = 1
3687 m = scmutil.match(ctx, pats, opts, default='relglob',
3686 m = scmutil.match(ctx, pats, opts, default='relglob',
3688 badfn=lambda x, y: False)
3687 badfn=lambda x, y: False)
3689
3688
3690 ui.pager('locate')
3689 ui.pager('locate')
3691 if ctx.rev() is None:
3690 if ctx.rev() is None:
3692 # When run on the working copy, "locate" includes removed files, so
3691 # When run on the working copy, "locate" includes removed files, so
3693 # we get the list of files from the dirstate.
3692 # we get the list of files from the dirstate.
3694 filesgen = sorted(repo.dirstate.matches(m))
3693 filesgen = sorted(repo.dirstate.matches(m))
3695 else:
3694 else:
3696 filesgen = ctx.matches(m)
3695 filesgen = ctx.matches(m)
3697 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3696 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3698 for abs in filesgen:
3697 for abs in filesgen:
3699 if opts.get('fullpath'):
3698 if opts.get('fullpath'):
3700 ui.write(repo.wjoin(abs), end)
3699 ui.write(repo.wjoin(abs), end)
3701 else:
3700 else:
3702 ui.write(uipathfn(abs), end)
3701 ui.write(uipathfn(abs), end)
3703 ret = 0
3702 ret = 0
3704
3703
3705 return ret
3704 return ret
3706
3705
3707 @command('log|history',
3706 @command('log|history',
3708 [('f', 'follow', None,
3707 [('f', 'follow', None,
3709 _('follow changeset history, or file history across copies and renames')),
3708 _('follow changeset history, or file history across copies and renames')),
3710 ('', 'follow-first', None,
3709 ('', 'follow-first', None,
3711 _('only follow the first parent of merge changesets (DEPRECATED)')),
3710 _('only follow the first parent of merge changesets (DEPRECATED)')),
3712 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3711 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3713 ('C', 'copies', None, _('show copied files')),
3712 ('C', 'copies', None, _('show copied files')),
3714 ('k', 'keyword', [],
3713 ('k', 'keyword', [],
3715 _('do case-insensitive search for a given text'), _('TEXT')),
3714 _('do case-insensitive search for a given text'), _('TEXT')),
3716 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3715 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3717 ('L', 'line-range', [],
3716 ('L', 'line-range', [],
3718 _('follow line range of specified file (EXPERIMENTAL)'),
3717 _('follow line range of specified file (EXPERIMENTAL)'),
3719 _('FILE,RANGE')),
3718 _('FILE,RANGE')),
3720 ('', 'removed', None, _('include revisions where files were removed')),
3719 ('', 'removed', None, _('include revisions where files were removed')),
3721 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3720 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3722 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3721 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3723 ('', 'only-branch', [],
3722 ('', 'only-branch', [],
3724 _('show only changesets within the given named branch (DEPRECATED)'),
3723 _('show only changesets within the given named branch (DEPRECATED)'),
3725 _('BRANCH')),
3724 _('BRANCH')),
3726 ('b', 'branch', [],
3725 ('b', 'branch', [],
3727 _('show changesets within the given named branch'), _('BRANCH')),
3726 _('show changesets within the given named branch'), _('BRANCH')),
3728 ('P', 'prune', [],
3727 ('P', 'prune', [],
3729 _('do not display revision or any of its ancestors'), _('REV')),
3728 _('do not display revision or any of its ancestors'), _('REV')),
3730 ] + logopts + walkopts,
3729 ] + logopts + walkopts,
3731 _('[OPTION]... [FILE]'),
3730 _('[OPTION]... [FILE]'),
3732 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3731 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3733 helpbasic=True, inferrepo=True,
3732 helpbasic=True, inferrepo=True,
3734 intents={INTENT_READONLY})
3733 intents={INTENT_READONLY})
3735 def log(ui, repo, *pats, **opts):
3734 def log(ui, repo, *pats, **opts):
3736 """show revision history of entire repository or files
3735 """show revision history of entire repository or files
3737
3736
3738 Print the revision history of the specified files or the entire
3737 Print the revision history of the specified files or the entire
3739 project.
3738 project.
3740
3739
3741 If no revision range is specified, the default is ``tip:0`` unless
3740 If no revision range is specified, the default is ``tip:0`` unless
3742 --follow is set, in which case the working directory parent is
3741 --follow is set, in which case the working directory parent is
3743 used as the starting revision.
3742 used as the starting revision.
3744
3743
3745 File history is shown without following rename or copy history of
3744 File history is shown without following rename or copy history of
3746 files. Use -f/--follow with a filename to follow history across
3745 files. Use -f/--follow with a filename to follow history across
3747 renames and copies. --follow without a filename will only show
3746 renames and copies. --follow without a filename will only show
3748 ancestors of the starting revision.
3747 ancestors of the starting revision.
3749
3748
3750 By default this command prints revision number and changeset id,
3749 By default this command prints revision number and changeset id,
3751 tags, non-trivial parents, user, date and time, and a summary for
3750 tags, non-trivial parents, user, date and time, and a summary for
3752 each commit. When the -v/--verbose switch is used, the list of
3751 each commit. When the -v/--verbose switch is used, the list of
3753 changed files and full commit message are shown.
3752 changed files and full commit message are shown.
3754
3753
3755 With --graph the revisions are shown as an ASCII art DAG with the most
3754 With --graph the revisions are shown as an ASCII art DAG with the most
3756 recent changeset at the top.
3755 recent changeset at the top.
3757 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3756 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3758 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3757 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3759 changeset from the lines below is a parent of the 'o' merge on the same
3758 changeset from the lines below is a parent of the 'o' merge on the same
3760 line.
3759 line.
3761 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3760 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3762 of a '|' indicates one or more revisions in a path are omitted.
3761 of a '|' indicates one or more revisions in a path are omitted.
3763
3762
3764 .. container:: verbose
3763 .. container:: verbose
3765
3764
3766 Use -L/--line-range FILE,M:N options to follow the history of lines
3765 Use -L/--line-range FILE,M:N options to follow the history of lines
3767 from M to N in FILE. With -p/--patch only diff hunks affecting
3766 from M to N in FILE. With -p/--patch only diff hunks affecting
3768 specified line range will be shown. This option requires --follow;
3767 specified line range will be shown. This option requires --follow;
3769 it can be specified multiple times. Currently, this option is not
3768 it can be specified multiple times. Currently, this option is not
3770 compatible with --graph. This option is experimental.
3769 compatible with --graph. This option is experimental.
3771
3770
3772 .. note::
3771 .. note::
3773
3772
3774 :hg:`log --patch` may generate unexpected diff output for merge
3773 :hg:`log --patch` may generate unexpected diff output for merge
3775 changesets, as it will only compare the merge changeset against
3774 changesets, as it will only compare the merge changeset against
3776 its first parent. Also, only files different from BOTH parents
3775 its first parent. Also, only files different from BOTH parents
3777 will appear in files:.
3776 will appear in files:.
3778
3777
3779 .. note::
3778 .. note::
3780
3779
3781 For performance reasons, :hg:`log FILE` may omit duplicate changes
3780 For performance reasons, :hg:`log FILE` may omit duplicate changes
3782 made on branches and will not show removals or mode changes. To
3781 made on branches and will not show removals or mode changes. To
3783 see all such changes, use the --removed switch.
3782 see all such changes, use the --removed switch.
3784
3783
3785 .. container:: verbose
3784 .. container:: verbose
3786
3785
3787 .. note::
3786 .. note::
3788
3787
3789 The history resulting from -L/--line-range options depends on diff
3788 The history resulting from -L/--line-range options depends on diff
3790 options; for instance if white-spaces are ignored, respective changes
3789 options; for instance if white-spaces are ignored, respective changes
3791 with only white-spaces in specified line range will not be listed.
3790 with only white-spaces in specified line range will not be listed.
3792
3791
3793 .. container:: verbose
3792 .. container:: verbose
3794
3793
3795 Some examples:
3794 Some examples:
3796
3795
3797 - changesets with full descriptions and file lists::
3796 - changesets with full descriptions and file lists::
3798
3797
3799 hg log -v
3798 hg log -v
3800
3799
3801 - changesets ancestral to the working directory::
3800 - changesets ancestral to the working directory::
3802
3801
3803 hg log -f
3802 hg log -f
3804
3803
3805 - last 10 commits on the current branch::
3804 - last 10 commits on the current branch::
3806
3805
3807 hg log -l 10 -b .
3806 hg log -l 10 -b .
3808
3807
3809 - changesets showing all modifications of a file, including removals::
3808 - changesets showing all modifications of a file, including removals::
3810
3809
3811 hg log --removed file.c
3810 hg log --removed file.c
3812
3811
3813 - all changesets that touch a directory, with diffs, excluding merges::
3812 - all changesets that touch a directory, with diffs, excluding merges::
3814
3813
3815 hg log -Mp lib/
3814 hg log -Mp lib/
3816
3815
3817 - all revision numbers that match a keyword::
3816 - all revision numbers that match a keyword::
3818
3817
3819 hg log -k bug --template "{rev}\\n"
3818 hg log -k bug --template "{rev}\\n"
3820
3819
3821 - the full hash identifier of the working directory parent::
3820 - the full hash identifier of the working directory parent::
3822
3821
3823 hg log -r . --template "{node}\\n"
3822 hg log -r . --template "{node}\\n"
3824
3823
3825 - list available log templates::
3824 - list available log templates::
3826
3825
3827 hg log -T list
3826 hg log -T list
3828
3827
3829 - check if a given changeset is included in a tagged release::
3828 - check if a given changeset is included in a tagged release::
3830
3829
3831 hg log -r "a21ccf and ancestor(1.9)"
3830 hg log -r "a21ccf and ancestor(1.9)"
3832
3831
3833 - find all changesets by some user in a date range::
3832 - find all changesets by some user in a date range::
3834
3833
3835 hg log -k alice -d "may 2008 to jul 2008"
3834 hg log -k alice -d "may 2008 to jul 2008"
3836
3835
3837 - summary of all changesets after the last tag::
3836 - summary of all changesets after the last tag::
3838
3837
3839 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3838 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3840
3839
3841 - changesets touching lines 13 to 23 for file.c::
3840 - changesets touching lines 13 to 23 for file.c::
3842
3841
3843 hg log -L file.c,13:23
3842 hg log -L file.c,13:23
3844
3843
3845 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3844 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3846 main.c with patch::
3845 main.c with patch::
3847
3846
3848 hg log -L file.c,13:23 -L main.c,2:6 -p
3847 hg log -L file.c,13:23 -L main.c,2:6 -p
3849
3848
3850 See :hg:`help dates` for a list of formats valid for -d/--date.
3849 See :hg:`help dates` for a list of formats valid for -d/--date.
3851
3850
3852 See :hg:`help revisions` for more about specifying and ordering
3851 See :hg:`help revisions` for more about specifying and ordering
3853 revisions.
3852 revisions.
3854
3853
3855 See :hg:`help templates` for more about pre-packaged styles and
3854 See :hg:`help templates` for more about pre-packaged styles and
3856 specifying custom templates. The default template used by the log
3855 specifying custom templates. The default template used by the log
3857 command can be customized via the ``ui.logtemplate`` configuration
3856 command can be customized via the ``ui.logtemplate`` configuration
3858 setting.
3857 setting.
3859
3858
3860 Returns 0 on success.
3859 Returns 0 on success.
3861
3860
3862 """
3861 """
3863 opts = pycompat.byteskwargs(opts)
3862 opts = pycompat.byteskwargs(opts)
3864 linerange = opts.get('line_range')
3863 linerange = opts.get('line_range')
3865
3864
3866 if linerange and not opts.get('follow'):
3865 if linerange and not opts.get('follow'):
3867 raise error.Abort(_('--line-range requires --follow'))
3866 raise error.Abort(_('--line-range requires --follow'))
3868
3867
3869 if linerange and pats:
3868 if linerange and pats:
3870 # TODO: take pats as patterns with no line-range filter
3869 # TODO: take pats as patterns with no line-range filter
3871 raise error.Abort(
3870 raise error.Abort(
3872 _('FILE arguments are not compatible with --line-range option')
3871 _('FILE arguments are not compatible with --line-range option')
3873 )
3872 )
3874
3873
3875 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3874 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3876 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3875 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3877 if linerange:
3876 if linerange:
3878 # TODO: should follow file history from logcmdutil._initialrevs(),
3877 # TODO: should follow file history from logcmdutil._initialrevs(),
3879 # then filter the result by logcmdutil._makerevset() and --limit
3878 # then filter the result by logcmdutil._makerevset() and --limit
3880 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3879 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3881
3880
3882 getrenamed = None
3881 getrenamed = None
3883 if opts.get('copies'):
3882 if opts.get('copies'):
3884 endrev = None
3883 endrev = None
3885 if revs:
3884 if revs:
3886 endrev = revs.max() + 1
3885 endrev = revs.max() + 1
3887 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3886 getrenamed = scmutil.getrenamedfn(repo, endrev=endrev)
3888
3887
3889 ui.pager('log')
3888 ui.pager('log')
3890 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3889 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3891 buffered=True)
3890 buffered=True)
3892 if opts.get('graph'):
3891 if opts.get('graph'):
3893 displayfn = logcmdutil.displaygraphrevs
3892 displayfn = logcmdutil.displaygraphrevs
3894 else:
3893 else:
3895 displayfn = logcmdutil.displayrevs
3894 displayfn = logcmdutil.displayrevs
3896 displayfn(ui, repo, revs, displayer, getrenamed)
3895 displayfn(ui, repo, revs, displayer, getrenamed)
3897
3896
3898 @command('manifest',
3897 @command('manifest',
3899 [('r', 'rev', '', _('revision to display'), _('REV')),
3898 [('r', 'rev', '', _('revision to display'), _('REV')),
3900 ('', 'all', False, _("list files from all revisions"))]
3899 ('', 'all', False, _("list files from all revisions"))]
3901 + formatteropts,
3900 + formatteropts,
3902 _('[-r REV]'),
3901 _('[-r REV]'),
3903 helpcategory=command.CATEGORY_MAINTENANCE,
3902 helpcategory=command.CATEGORY_MAINTENANCE,
3904 intents={INTENT_READONLY})
3903 intents={INTENT_READONLY})
3905 def manifest(ui, repo, node=None, rev=None, **opts):
3904 def manifest(ui, repo, node=None, rev=None, **opts):
3906 """output the current or given revision of the project manifest
3905 """output the current or given revision of the project manifest
3907
3906
3908 Print a list of version controlled files for the given revision.
3907 Print a list of version controlled files for the given revision.
3909 If no revision is given, the first parent of the working directory
3908 If no revision is given, the first parent of the working directory
3910 is used, or the null revision if no revision is checked out.
3909 is used, or the null revision if no revision is checked out.
3911
3910
3912 With -v, print file permissions, symlink and executable bits.
3911 With -v, print file permissions, symlink and executable bits.
3913 With --debug, print file revision hashes.
3912 With --debug, print file revision hashes.
3914
3913
3915 If option --all is specified, the list of all files from all revisions
3914 If option --all is specified, the list of all files from all revisions
3916 is printed. This includes deleted and renamed files.
3915 is printed. This includes deleted and renamed files.
3917
3916
3918 Returns 0 on success.
3917 Returns 0 on success.
3919 """
3918 """
3920 opts = pycompat.byteskwargs(opts)
3919 opts = pycompat.byteskwargs(opts)
3921 fm = ui.formatter('manifest', opts)
3920 fm = ui.formatter('manifest', opts)
3922
3921
3923 if opts.get('all'):
3922 if opts.get('all'):
3924 if rev or node:
3923 if rev or node:
3925 raise error.Abort(_("can't specify a revision with --all"))
3924 raise error.Abort(_("can't specify a revision with --all"))
3926
3925
3927 res = set()
3926 res = set()
3928 for rev in repo:
3927 for rev in repo:
3929 ctx = repo[rev]
3928 ctx = repo[rev]
3930 res |= set(ctx.files())
3929 res |= set(ctx.files())
3931
3930
3932 ui.pager('manifest')
3931 ui.pager('manifest')
3933 for f in sorted(res):
3932 for f in sorted(res):
3934 fm.startitem()
3933 fm.startitem()
3935 fm.write("path", '%s\n', f)
3934 fm.write("path", '%s\n', f)
3936 fm.end()
3935 fm.end()
3937 return
3936 return
3938
3937
3939 if rev and node:
3938 if rev and node:
3940 raise error.Abort(_("please specify just one revision"))
3939 raise error.Abort(_("please specify just one revision"))
3941
3940
3942 if not node:
3941 if not node:
3943 node = rev
3942 node = rev
3944
3943
3945 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3944 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3946 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3945 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3947 if node:
3946 if node:
3948 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3947 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3949 ctx = scmutil.revsingle(repo, node)
3948 ctx = scmutil.revsingle(repo, node)
3950 mf = ctx.manifest()
3949 mf = ctx.manifest()
3951 ui.pager('manifest')
3950 ui.pager('manifest')
3952 for f in ctx:
3951 for f in ctx:
3953 fm.startitem()
3952 fm.startitem()
3954 fm.context(ctx=ctx)
3953 fm.context(ctx=ctx)
3955 fl = ctx[f].flags()
3954 fl = ctx[f].flags()
3956 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3955 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3957 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3956 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3958 fm.write('path', '%s\n', f)
3957 fm.write('path', '%s\n', f)
3959 fm.end()
3958 fm.end()
3960
3959
3961 @command('merge',
3960 @command('merge',
3962 [('f', 'force', None,
3961 [('f', 'force', None,
3963 _('force a merge including outstanding changes (DEPRECATED)')),
3962 _('force a merge including outstanding changes (DEPRECATED)')),
3964 ('r', 'rev', '', _('revision to merge'), _('REV')),
3963 ('r', 'rev', '', _('revision to merge'), _('REV')),
3965 ('P', 'preview', None,
3964 ('P', 'preview', None,
3966 _('review revisions to merge (no merge is performed)')),
3965 _('review revisions to merge (no merge is performed)')),
3967 ('', 'abort', None, _('abort the ongoing merge')),
3966 ('', 'abort', None, _('abort the ongoing merge')),
3968 ] + mergetoolopts,
3967 ] + mergetoolopts,
3969 _('[-P] [[-r] REV]'),
3968 _('[-P] [[-r] REV]'),
3970 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3969 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3971 def merge(ui, repo, node=None, **opts):
3970 def merge(ui, repo, node=None, **opts):
3972 """merge another revision into working directory
3971 """merge another revision into working directory
3973
3972
3974 The current working directory is updated with all changes made in
3973 The current working directory is updated with all changes made in
3975 the requested revision since the last common predecessor revision.
3974 the requested revision since the last common predecessor revision.
3976
3975
3977 Files that changed between either parent are marked as changed for
3976 Files that changed between either parent are marked as changed for
3978 the next commit and a commit must be performed before any further
3977 the next commit and a commit must be performed before any further
3979 updates to the repository are allowed. The next commit will have
3978 updates to the repository are allowed. The next commit will have
3980 two parents.
3979 two parents.
3981
3980
3982 ``--tool`` can be used to specify the merge tool used for file
3981 ``--tool`` can be used to specify the merge tool used for file
3983 merges. It overrides the HGMERGE environment variable and your
3982 merges. It overrides the HGMERGE environment variable and your
3984 configuration files. See :hg:`help merge-tools` for options.
3983 configuration files. See :hg:`help merge-tools` for options.
3985
3984
3986 If no revision is specified, the working directory's parent is a
3985 If no revision is specified, the working directory's parent is a
3987 head revision, and the current branch contains exactly one other
3986 head revision, and the current branch contains exactly one other
3988 head, the other head is merged with by default. Otherwise, an
3987 head, the other head is merged with by default. Otherwise, an
3989 explicit revision with which to merge with must be provided.
3988 explicit revision with which to merge with must be provided.
3990
3989
3991 See :hg:`help resolve` for information on handling file conflicts.
3990 See :hg:`help resolve` for information on handling file conflicts.
3992
3991
3993 To undo an uncommitted merge, use :hg:`merge --abort` which
3992 To undo an uncommitted merge, use :hg:`merge --abort` which
3994 will check out a clean copy of the original merge parent, losing
3993 will check out a clean copy of the original merge parent, losing
3995 all changes.
3994 all changes.
3996
3995
3997 Returns 0 on success, 1 if there are unresolved files.
3996 Returns 0 on success, 1 if there are unresolved files.
3998 """
3997 """
3999
3998
4000 opts = pycompat.byteskwargs(opts)
3999 opts = pycompat.byteskwargs(opts)
4001 abort = opts.get('abort')
4000 abort = opts.get('abort')
4002 if abort and repo.dirstate.p2() == nullid:
4001 if abort and repo.dirstate.p2() == nullid:
4003 cmdutil.wrongtooltocontinue(repo, _('merge'))
4002 cmdutil.wrongtooltocontinue(repo, _('merge'))
4004 if abort:
4003 if abort:
4005 if node:
4004 if node:
4006 raise error.Abort(_("cannot specify a node with --abort"))
4005 raise error.Abort(_("cannot specify a node with --abort"))
4007 if opts.get('rev'):
4006 if opts.get('rev'):
4008 raise error.Abort(_("cannot specify both --rev and --abort"))
4007 raise error.Abort(_("cannot specify both --rev and --abort"))
4009 if opts.get('preview'):
4008 if opts.get('preview'):
4010 raise error.Abort(_("cannot specify --preview with --abort"))
4009 raise error.Abort(_("cannot specify --preview with --abort"))
4011 if opts.get('rev') and node:
4010 if opts.get('rev') and node:
4012 raise error.Abort(_("please specify just one revision"))
4011 raise error.Abort(_("please specify just one revision"))
4013 if not node:
4012 if not node:
4014 node = opts.get('rev')
4013 node = opts.get('rev')
4015
4014
4016 if node:
4015 if node:
4017 node = scmutil.revsingle(repo, node).node()
4016 node = scmutil.revsingle(repo, node).node()
4018
4017
4019 if not node and not abort:
4018 if not node and not abort:
4020 node = repo[destutil.destmerge(repo)].node()
4019 node = repo[destutil.destmerge(repo)].node()
4021
4020
4022 if opts.get('preview'):
4021 if opts.get('preview'):
4023 # find nodes that are ancestors of p2 but not of p1
4022 # find nodes that are ancestors of p2 but not of p1
4024 p1 = repo.lookup('.')
4023 p1 = repo.lookup('.')
4025 p2 = node
4024 p2 = node
4026 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4025 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4027
4026
4028 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4027 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4029 for node in nodes:
4028 for node in nodes:
4030 displayer.show(repo[node])
4029 displayer.show(repo[node])
4031 displayer.close()
4030 displayer.close()
4032 return 0
4031 return 0
4033
4032
4034 # ui.forcemerge is an internal variable, do not document
4033 # ui.forcemerge is an internal variable, do not document
4035 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4034 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4036 with ui.configoverride(overrides, 'merge'):
4035 with ui.configoverride(overrides, 'merge'):
4037 force = opts.get('force')
4036 force = opts.get('force')
4038 labels = ['working copy', 'merge rev']
4037 labels = ['working copy', 'merge rev']
4039 return hg.merge(repo, node, force=force, mergeforce=force,
4038 return hg.merge(repo, node, force=force, mergeforce=force,
4040 labels=labels, abort=abort)
4039 labels=labels, abort=abort)
4041
4040
4042 @command('outgoing|out',
4041 @command('outgoing|out',
4043 [('f', 'force', None, _('run even when the destination is unrelated')),
4042 [('f', 'force', None, _('run even when the destination is unrelated')),
4044 ('r', 'rev', [],
4043 ('r', 'rev', [],
4045 _('a changeset intended to be included in the destination'), _('REV')),
4044 _('a changeset intended to be included in the destination'), _('REV')),
4046 ('n', 'newest-first', None, _('show newest record first')),
4045 ('n', 'newest-first', None, _('show newest record first')),
4047 ('B', 'bookmarks', False, _('compare bookmarks')),
4046 ('B', 'bookmarks', False, _('compare bookmarks')),
4048 ('b', 'branch', [], _('a specific branch you would like to push'),
4047 ('b', 'branch', [], _('a specific branch you would like to push'),
4049 _('BRANCH')),
4048 _('BRANCH')),
4050 ] + logopts + remoteopts + subrepoopts,
4049 ] + logopts + remoteopts + subrepoopts,
4051 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4050 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4052 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4051 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4053 def outgoing(ui, repo, dest=None, **opts):
4052 def outgoing(ui, repo, dest=None, **opts):
4054 """show changesets not found in the destination
4053 """show changesets not found in the destination
4055
4054
4056 Show changesets not found in the specified destination repository
4055 Show changesets not found in the specified destination repository
4057 or the default push location. These are the changesets that would
4056 or the default push location. These are the changesets that would
4058 be pushed if a push was requested.
4057 be pushed if a push was requested.
4059
4058
4060 See pull for details of valid destination formats.
4059 See pull for details of valid destination formats.
4061
4060
4062 .. container:: verbose
4061 .. container:: verbose
4063
4062
4064 With -B/--bookmarks, the result of bookmark comparison between
4063 With -B/--bookmarks, the result of bookmark comparison between
4065 local and remote repositories is displayed. With -v/--verbose,
4064 local and remote repositories is displayed. With -v/--verbose,
4066 status is also displayed for each bookmark like below::
4065 status is also displayed for each bookmark like below::
4067
4066
4068 BM1 01234567890a added
4067 BM1 01234567890a added
4069 BM2 deleted
4068 BM2 deleted
4070 BM3 234567890abc advanced
4069 BM3 234567890abc advanced
4071 BM4 34567890abcd diverged
4070 BM4 34567890abcd diverged
4072 BM5 4567890abcde changed
4071 BM5 4567890abcde changed
4073
4072
4074 The action taken when pushing depends on the
4073 The action taken when pushing depends on the
4075 status of each bookmark:
4074 status of each bookmark:
4076
4075
4077 :``added``: push with ``-B`` will create it
4076 :``added``: push with ``-B`` will create it
4078 :``deleted``: push with ``-B`` will delete it
4077 :``deleted``: push with ``-B`` will delete it
4079 :``advanced``: push will update it
4078 :``advanced``: push will update it
4080 :``diverged``: push with ``-B`` will update it
4079 :``diverged``: push with ``-B`` will update it
4081 :``changed``: push with ``-B`` will update it
4080 :``changed``: push with ``-B`` will update it
4082
4081
4083 From the point of view of pushing behavior, bookmarks
4082 From the point of view of pushing behavior, bookmarks
4084 existing only in the remote repository are treated as
4083 existing only in the remote repository are treated as
4085 ``deleted``, even if it is in fact added remotely.
4084 ``deleted``, even if it is in fact added remotely.
4086
4085
4087 Returns 0 if there are outgoing changes, 1 otherwise.
4086 Returns 0 if there are outgoing changes, 1 otherwise.
4088 """
4087 """
4089 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4088 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4090 # style URLs, so don't overwrite dest.
4089 # style URLs, so don't overwrite dest.
4091 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4090 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4092 if not path:
4091 if not path:
4093 raise error.Abort(_('default repository not configured!'),
4092 raise error.Abort(_('default repository not configured!'),
4094 hint=_("see 'hg help config.paths'"))
4093 hint=_("see 'hg help config.paths'"))
4095
4094
4096 opts = pycompat.byteskwargs(opts)
4095 opts = pycompat.byteskwargs(opts)
4097 if opts.get('graph'):
4096 if opts.get('graph'):
4098 logcmdutil.checkunsupportedgraphflags([], opts)
4097 logcmdutil.checkunsupportedgraphflags([], opts)
4099 o, other = hg._outgoing(ui, repo, dest, opts)
4098 o, other = hg._outgoing(ui, repo, dest, opts)
4100 if not o:
4099 if not o:
4101 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4100 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4102 return
4101 return
4103
4102
4104 revdag = logcmdutil.graphrevs(repo, o, opts)
4103 revdag = logcmdutil.graphrevs(repo, o, opts)
4105 ui.pager('outgoing')
4104 ui.pager('outgoing')
4106 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4105 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4107 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4106 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4108 graphmod.asciiedges)
4107 graphmod.asciiedges)
4109 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4108 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4110 return 0
4109 return 0
4111
4110
4112 if opts.get('bookmarks'):
4111 if opts.get('bookmarks'):
4113 dest = path.pushloc or path.loc
4112 dest = path.pushloc or path.loc
4114 other = hg.peer(repo, opts, dest)
4113 other = hg.peer(repo, opts, dest)
4115 if 'bookmarks' not in other.listkeys('namespaces'):
4114 if 'bookmarks' not in other.listkeys('namespaces'):
4116 ui.warn(_("remote doesn't support bookmarks\n"))
4115 ui.warn(_("remote doesn't support bookmarks\n"))
4117 return 0
4116 return 0
4118 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4117 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4119 ui.pager('outgoing')
4118 ui.pager('outgoing')
4120 return bookmarks.outgoing(ui, repo, other)
4119 return bookmarks.outgoing(ui, repo, other)
4121
4120
4122 repo._subtoppath = path.pushloc or path.loc
4121 repo._subtoppath = path.pushloc or path.loc
4123 try:
4122 try:
4124 return hg.outgoing(ui, repo, dest, opts)
4123 return hg.outgoing(ui, repo, dest, opts)
4125 finally:
4124 finally:
4126 del repo._subtoppath
4125 del repo._subtoppath
4127
4126
4128 @command('parents',
4127 @command('parents',
4129 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4128 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4130 ] + templateopts,
4129 ] + templateopts,
4131 _('[-r REV] [FILE]'),
4130 _('[-r REV] [FILE]'),
4132 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4131 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4133 inferrepo=True)
4132 inferrepo=True)
4134 def parents(ui, repo, file_=None, **opts):
4133 def parents(ui, repo, file_=None, **opts):
4135 """show the parents of the working directory or revision (DEPRECATED)
4134 """show the parents of the working directory or revision (DEPRECATED)
4136
4135
4137 Print the working directory's parent revisions. If a revision is
4136 Print the working directory's parent revisions. If a revision is
4138 given via -r/--rev, the parent of that revision will be printed.
4137 given via -r/--rev, the parent of that revision will be printed.
4139 If a file argument is given, the revision in which the file was
4138 If a file argument is given, the revision in which the file was
4140 last changed (before the working directory revision or the
4139 last changed (before the working directory revision or the
4141 argument to --rev if given) is printed.
4140 argument to --rev if given) is printed.
4142
4141
4143 This command is equivalent to::
4142 This command is equivalent to::
4144
4143
4145 hg log -r "p1()+p2()" or
4144 hg log -r "p1()+p2()" or
4146 hg log -r "p1(REV)+p2(REV)" or
4145 hg log -r "p1(REV)+p2(REV)" or
4147 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4146 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4148 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4147 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4149
4148
4150 See :hg:`summary` and :hg:`help revsets` for related information.
4149 See :hg:`summary` and :hg:`help revsets` for related information.
4151
4150
4152 Returns 0 on success.
4151 Returns 0 on success.
4153 """
4152 """
4154
4153
4155 opts = pycompat.byteskwargs(opts)
4154 opts = pycompat.byteskwargs(opts)
4156 rev = opts.get('rev')
4155 rev = opts.get('rev')
4157 if rev:
4156 if rev:
4158 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4157 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4159 ctx = scmutil.revsingle(repo, rev, None)
4158 ctx = scmutil.revsingle(repo, rev, None)
4160
4159
4161 if file_:
4160 if file_:
4162 m = scmutil.match(ctx, (file_,), opts)
4161 m = scmutil.match(ctx, (file_,), opts)
4163 if m.anypats() or len(m.files()) != 1:
4162 if m.anypats() or len(m.files()) != 1:
4164 raise error.Abort(_('can only specify an explicit filename'))
4163 raise error.Abort(_('can only specify an explicit filename'))
4165 file_ = m.files()[0]
4164 file_ = m.files()[0]
4166 filenodes = []
4165 filenodes = []
4167 for cp in ctx.parents():
4166 for cp in ctx.parents():
4168 if not cp:
4167 if not cp:
4169 continue
4168 continue
4170 try:
4169 try:
4171 filenodes.append(cp.filenode(file_))
4170 filenodes.append(cp.filenode(file_))
4172 except error.LookupError:
4171 except error.LookupError:
4173 pass
4172 pass
4174 if not filenodes:
4173 if not filenodes:
4175 raise error.Abort(_("'%s' not found in manifest!") % file_)
4174 raise error.Abort(_("'%s' not found in manifest!") % file_)
4176 p = []
4175 p = []
4177 for fn in filenodes:
4176 for fn in filenodes:
4178 fctx = repo.filectx(file_, fileid=fn)
4177 fctx = repo.filectx(file_, fileid=fn)
4179 p.append(fctx.node())
4178 p.append(fctx.node())
4180 else:
4179 else:
4181 p = [cp.node() for cp in ctx.parents()]
4180 p = [cp.node() for cp in ctx.parents()]
4182
4181
4183 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4182 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4184 for n in p:
4183 for n in p:
4185 if n != nullid:
4184 if n != nullid:
4186 displayer.show(repo[n])
4185 displayer.show(repo[n])
4187 displayer.close()
4186 displayer.close()
4188
4187
4189 @command('paths', formatteropts, _('[NAME]'),
4188 @command('paths', formatteropts, _('[NAME]'),
4190 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4189 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4191 optionalrepo=True, intents={INTENT_READONLY})
4190 optionalrepo=True, intents={INTENT_READONLY})
4192 def paths(ui, repo, search=None, **opts):
4191 def paths(ui, repo, search=None, **opts):
4193 """show aliases for remote repositories
4192 """show aliases for remote repositories
4194
4193
4195 Show definition of symbolic path name NAME. If no name is given,
4194 Show definition of symbolic path name NAME. If no name is given,
4196 show definition of all available names.
4195 show definition of all available names.
4197
4196
4198 Option -q/--quiet suppresses all output when searching for NAME
4197 Option -q/--quiet suppresses all output when searching for NAME
4199 and shows only the path names when listing all definitions.
4198 and shows only the path names when listing all definitions.
4200
4199
4201 Path names are defined in the [paths] section of your
4200 Path names are defined in the [paths] section of your
4202 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4201 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4203 repository, ``.hg/hgrc`` is used, too.
4202 repository, ``.hg/hgrc`` is used, too.
4204
4203
4205 The path names ``default`` and ``default-push`` have a special
4204 The path names ``default`` and ``default-push`` have a special
4206 meaning. When performing a push or pull operation, they are used
4205 meaning. When performing a push or pull operation, they are used
4207 as fallbacks if no location is specified on the command-line.
4206 as fallbacks if no location is specified on the command-line.
4208 When ``default-push`` is set, it will be used for push and
4207 When ``default-push`` is set, it will be used for push and
4209 ``default`` will be used for pull; otherwise ``default`` is used
4208 ``default`` will be used for pull; otherwise ``default`` is used
4210 as the fallback for both. When cloning a repository, the clone
4209 as the fallback for both. When cloning a repository, the clone
4211 source is written as ``default`` in ``.hg/hgrc``.
4210 source is written as ``default`` in ``.hg/hgrc``.
4212
4211
4213 .. note::
4212 .. note::
4214
4213
4215 ``default`` and ``default-push`` apply to all inbound (e.g.
4214 ``default`` and ``default-push`` apply to all inbound (e.g.
4216 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4215 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4217 and :hg:`bundle`) operations.
4216 and :hg:`bundle`) operations.
4218
4217
4219 See :hg:`help urls` for more information.
4218 See :hg:`help urls` for more information.
4220
4219
4221 .. container:: verbose
4220 .. container:: verbose
4222
4221
4223 Template:
4222 Template:
4224
4223
4225 The following keywords are supported. See also :hg:`help templates`.
4224 The following keywords are supported. See also :hg:`help templates`.
4226
4225
4227 :name: String. Symbolic name of the path alias.
4226 :name: String. Symbolic name of the path alias.
4228 :pushurl: String. URL for push operations.
4227 :pushurl: String. URL for push operations.
4229 :url: String. URL or directory path for the other operations.
4228 :url: String. URL or directory path for the other operations.
4230
4229
4231 Returns 0 on success.
4230 Returns 0 on success.
4232 """
4231 """
4233
4232
4234 opts = pycompat.byteskwargs(opts)
4233 opts = pycompat.byteskwargs(opts)
4235 ui.pager('paths')
4234 ui.pager('paths')
4236 if search:
4235 if search:
4237 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4236 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4238 if name == search]
4237 if name == search]
4239 else:
4238 else:
4240 pathitems = sorted(ui.paths.iteritems())
4239 pathitems = sorted(ui.paths.iteritems())
4241
4240
4242 fm = ui.formatter('paths', opts)
4241 fm = ui.formatter('paths', opts)
4243 if fm.isplain():
4242 if fm.isplain():
4244 hidepassword = util.hidepassword
4243 hidepassword = util.hidepassword
4245 else:
4244 else:
4246 hidepassword = bytes
4245 hidepassword = bytes
4247 if ui.quiet:
4246 if ui.quiet:
4248 namefmt = '%s\n'
4247 namefmt = '%s\n'
4249 else:
4248 else:
4250 namefmt = '%s = '
4249 namefmt = '%s = '
4251 showsubopts = not search and not ui.quiet
4250 showsubopts = not search and not ui.quiet
4252
4251
4253 for name, path in pathitems:
4252 for name, path in pathitems:
4254 fm.startitem()
4253 fm.startitem()
4255 fm.condwrite(not search, 'name', namefmt, name)
4254 fm.condwrite(not search, 'name', namefmt, name)
4256 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4255 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4257 for subopt, value in sorted(path.suboptions.items()):
4256 for subopt, value in sorted(path.suboptions.items()):
4258 assert subopt not in ('name', 'url')
4257 assert subopt not in ('name', 'url')
4259 if showsubopts:
4258 if showsubopts:
4260 fm.plain('%s:%s = ' % (name, subopt))
4259 fm.plain('%s:%s = ' % (name, subopt))
4261 fm.condwrite(showsubopts, subopt, '%s\n', value)
4260 fm.condwrite(showsubopts, subopt, '%s\n', value)
4262
4261
4263 fm.end()
4262 fm.end()
4264
4263
4265 if search and not pathitems:
4264 if search and not pathitems:
4266 if not ui.quiet:
4265 if not ui.quiet:
4267 ui.warn(_("not found!\n"))
4266 ui.warn(_("not found!\n"))
4268 return 1
4267 return 1
4269 else:
4268 else:
4270 return 0
4269 return 0
4271
4270
4272 @command('phase',
4271 @command('phase',
4273 [('p', 'public', False, _('set changeset phase to public')),
4272 [('p', 'public', False, _('set changeset phase to public')),
4274 ('d', 'draft', False, _('set changeset phase to draft')),
4273 ('d', 'draft', False, _('set changeset phase to draft')),
4275 ('s', 'secret', False, _('set changeset phase to secret')),
4274 ('s', 'secret', False, _('set changeset phase to secret')),
4276 ('f', 'force', False, _('allow to move boundary backward')),
4275 ('f', 'force', False, _('allow to move boundary backward')),
4277 ('r', 'rev', [], _('target revision'), _('REV')),
4276 ('r', 'rev', [], _('target revision'), _('REV')),
4278 ],
4277 ],
4279 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4278 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4280 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4279 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4281 def phase(ui, repo, *revs, **opts):
4280 def phase(ui, repo, *revs, **opts):
4282 """set or show the current phase name
4281 """set or show the current phase name
4283
4282
4284 With no argument, show the phase name of the current revision(s).
4283 With no argument, show the phase name of the current revision(s).
4285
4284
4286 With one of -p/--public, -d/--draft or -s/--secret, change the
4285 With one of -p/--public, -d/--draft or -s/--secret, change the
4287 phase value of the specified revisions.
4286 phase value of the specified revisions.
4288
4287
4289 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4288 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4290 lower phase to a higher phase. Phases are ordered as follows::
4289 lower phase to a higher phase. Phases are ordered as follows::
4291
4290
4292 public < draft < secret
4291 public < draft < secret
4293
4292
4294 Returns 0 on success, 1 if some phases could not be changed.
4293 Returns 0 on success, 1 if some phases could not be changed.
4295
4294
4296 (For more information about the phases concept, see :hg:`help phases`.)
4295 (For more information about the phases concept, see :hg:`help phases`.)
4297 """
4296 """
4298 opts = pycompat.byteskwargs(opts)
4297 opts = pycompat.byteskwargs(opts)
4299 # search for a unique phase argument
4298 # search for a unique phase argument
4300 targetphase = None
4299 targetphase = None
4301 for idx, name in enumerate(phases.cmdphasenames):
4300 for idx, name in enumerate(phases.cmdphasenames):
4302 if opts[name]:
4301 if opts[name]:
4303 if targetphase is not None:
4302 if targetphase is not None:
4304 raise error.Abort(_('only one phase can be specified'))
4303 raise error.Abort(_('only one phase can be specified'))
4305 targetphase = idx
4304 targetphase = idx
4306
4305
4307 # look for specified revision
4306 # look for specified revision
4308 revs = list(revs)
4307 revs = list(revs)
4309 revs.extend(opts['rev'])
4308 revs.extend(opts['rev'])
4310 if not revs:
4309 if not revs:
4311 # display both parents as the second parent phase can influence
4310 # display both parents as the second parent phase can influence
4312 # the phase of a merge commit
4311 # the phase of a merge commit
4313 revs = [c.rev() for c in repo[None].parents()]
4312 revs = [c.rev() for c in repo[None].parents()]
4314
4313
4315 revs = scmutil.revrange(repo, revs)
4314 revs = scmutil.revrange(repo, revs)
4316
4315
4317 ret = 0
4316 ret = 0
4318 if targetphase is None:
4317 if targetphase is None:
4319 # display
4318 # display
4320 for r in revs:
4319 for r in revs:
4321 ctx = repo[r]
4320 ctx = repo[r]
4322 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4321 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4323 else:
4322 else:
4324 with repo.lock(), repo.transaction("phase") as tr:
4323 with repo.lock(), repo.transaction("phase") as tr:
4325 # set phase
4324 # set phase
4326 if not revs:
4325 if not revs:
4327 raise error.Abort(_('empty revision set'))
4326 raise error.Abort(_('empty revision set'))
4328 nodes = [repo[r].node() for r in revs]
4327 nodes = [repo[r].node() for r in revs]
4329 # moving revision from public to draft may hide them
4328 # moving revision from public to draft may hide them
4330 # We have to check result on an unfiltered repository
4329 # We have to check result on an unfiltered repository
4331 unfi = repo.unfiltered()
4330 unfi = repo.unfiltered()
4332 getphase = unfi._phasecache.phase
4331 getphase = unfi._phasecache.phase
4333 olddata = [getphase(unfi, r) for r in unfi]
4332 olddata = [getphase(unfi, r) for r in unfi]
4334 phases.advanceboundary(repo, tr, targetphase, nodes)
4333 phases.advanceboundary(repo, tr, targetphase, nodes)
4335 if opts['force']:
4334 if opts['force']:
4336 phases.retractboundary(repo, tr, targetphase, nodes)
4335 phases.retractboundary(repo, tr, targetphase, nodes)
4337 getphase = unfi._phasecache.phase
4336 getphase = unfi._phasecache.phase
4338 newdata = [getphase(unfi, r) for r in unfi]
4337 newdata = [getphase(unfi, r) for r in unfi]
4339 changes = sum(newdata[r] != olddata[r] for r in unfi)
4338 changes = sum(newdata[r] != olddata[r] for r in unfi)
4340 cl = unfi.changelog
4339 cl = unfi.changelog
4341 rejected = [n for n in nodes
4340 rejected = [n for n in nodes
4342 if newdata[cl.rev(n)] < targetphase]
4341 if newdata[cl.rev(n)] < targetphase]
4343 if rejected:
4342 if rejected:
4344 ui.warn(_('cannot move %i changesets to a higher '
4343 ui.warn(_('cannot move %i changesets to a higher '
4345 'phase, use --force\n') % len(rejected))
4344 'phase, use --force\n') % len(rejected))
4346 ret = 1
4345 ret = 1
4347 if changes:
4346 if changes:
4348 msg = _('phase changed for %i changesets\n') % changes
4347 msg = _('phase changed for %i changesets\n') % changes
4349 if ret:
4348 if ret:
4350 ui.status(msg)
4349 ui.status(msg)
4351 else:
4350 else:
4352 ui.note(msg)
4351 ui.note(msg)
4353 else:
4352 else:
4354 ui.warn(_('no phases changed\n'))
4353 ui.warn(_('no phases changed\n'))
4355 return ret
4354 return ret
4356
4355
4357 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4356 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4358 """Run after a changegroup has been added via pull/unbundle
4357 """Run after a changegroup has been added via pull/unbundle
4359
4358
4360 This takes arguments below:
4359 This takes arguments below:
4361
4360
4362 :modheads: change of heads by pull/unbundle
4361 :modheads: change of heads by pull/unbundle
4363 :optupdate: updating working directory is needed or not
4362 :optupdate: updating working directory is needed or not
4364 :checkout: update destination revision (or None to default destination)
4363 :checkout: update destination revision (or None to default destination)
4365 :brev: a name, which might be a bookmark to be activated after updating
4364 :brev: a name, which might be a bookmark to be activated after updating
4366 """
4365 """
4367 if modheads == 0:
4366 if modheads == 0:
4368 return
4367 return
4369 if optupdate:
4368 if optupdate:
4370 try:
4369 try:
4371 return hg.updatetotally(ui, repo, checkout, brev)
4370 return hg.updatetotally(ui, repo, checkout, brev)
4372 except error.UpdateAbort as inst:
4371 except error.UpdateAbort as inst:
4373 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4372 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4374 hint = inst.hint
4373 hint = inst.hint
4375 raise error.UpdateAbort(msg, hint=hint)
4374 raise error.UpdateAbort(msg, hint=hint)
4376 if modheads is not None and modheads > 1:
4375 if modheads is not None and modheads > 1:
4377 currentbranchheads = len(repo.branchheads())
4376 currentbranchheads = len(repo.branchheads())
4378 if currentbranchheads == modheads:
4377 if currentbranchheads == modheads:
4379 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4378 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4380 elif currentbranchheads > 1:
4379 elif currentbranchheads > 1:
4381 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4380 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4382 "merge)\n"))
4381 "merge)\n"))
4383 else:
4382 else:
4384 ui.status(_("(run 'hg heads' to see heads)\n"))
4383 ui.status(_("(run 'hg heads' to see heads)\n"))
4385 elif not ui.configbool('commands', 'update.requiredest'):
4384 elif not ui.configbool('commands', 'update.requiredest'):
4386 ui.status(_("(run 'hg update' to get a working copy)\n"))
4385 ui.status(_("(run 'hg update' to get a working copy)\n"))
4387
4386
4388 @command('pull',
4387 @command('pull',
4389 [('u', 'update', None,
4388 [('u', 'update', None,
4390 _('update to new branch head if new descendants were pulled')),
4389 _('update to new branch head if new descendants were pulled')),
4391 ('f', 'force', None, _('run even when remote repository is unrelated')),
4390 ('f', 'force', None, _('run even when remote repository is unrelated')),
4392 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4391 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4393 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4392 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4394 ('b', 'branch', [], _('a specific branch you would like to pull'),
4393 ('b', 'branch', [], _('a specific branch you would like to pull'),
4395 _('BRANCH')),
4394 _('BRANCH')),
4396 ] + remoteopts,
4395 ] + remoteopts,
4397 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4396 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4398 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4397 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4399 helpbasic=True)
4398 helpbasic=True)
4400 def pull(ui, repo, source="default", **opts):
4399 def pull(ui, repo, source="default", **opts):
4401 """pull changes from the specified source
4400 """pull changes from the specified source
4402
4401
4403 Pull changes from a remote repository to a local one.
4402 Pull changes from a remote repository to a local one.
4404
4403
4405 This finds all changes from the repository at the specified path
4404 This finds all changes from the repository at the specified path
4406 or URL and adds them to a local repository (the current one unless
4405 or URL and adds them to a local repository (the current one unless
4407 -R is specified). By default, this does not update the copy of the
4406 -R is specified). By default, this does not update the copy of the
4408 project in the working directory.
4407 project in the working directory.
4409
4408
4410 When cloning from servers that support it, Mercurial may fetch
4409 When cloning from servers that support it, Mercurial may fetch
4411 pre-generated data. When this is done, hooks operating on incoming
4410 pre-generated data. When this is done, hooks operating on incoming
4412 changesets and changegroups may fire more than once, once for each
4411 changesets and changegroups may fire more than once, once for each
4413 pre-generated bundle and as well as for any additional remaining
4412 pre-generated bundle and as well as for any additional remaining
4414 data. See :hg:`help -e clonebundles` for more.
4413 data. See :hg:`help -e clonebundles` for more.
4415
4414
4416 Use :hg:`incoming` if you want to see what would have been added
4415 Use :hg:`incoming` if you want to see what would have been added
4417 by a pull at the time you issued this command. If you then decide
4416 by a pull at the time you issued this command. If you then decide
4418 to add those changes to the repository, you should use :hg:`pull
4417 to add those changes to the repository, you should use :hg:`pull
4419 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4418 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4420
4419
4421 If SOURCE is omitted, the 'default' path will be used.
4420 If SOURCE is omitted, the 'default' path will be used.
4422 See :hg:`help urls` for more information.
4421 See :hg:`help urls` for more information.
4423
4422
4424 Specifying bookmark as ``.`` is equivalent to specifying the active
4423 Specifying bookmark as ``.`` is equivalent to specifying the active
4425 bookmark's name.
4424 bookmark's name.
4426
4425
4427 Returns 0 on success, 1 if an update had unresolved files.
4426 Returns 0 on success, 1 if an update had unresolved files.
4428 """
4427 """
4429
4428
4430 opts = pycompat.byteskwargs(opts)
4429 opts = pycompat.byteskwargs(opts)
4431 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4430 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4432 msg = _('update destination required by configuration')
4431 msg = _('update destination required by configuration')
4433 hint = _('use hg pull followed by hg update DEST')
4432 hint = _('use hg pull followed by hg update DEST')
4434 raise error.Abort(msg, hint=hint)
4433 raise error.Abort(msg, hint=hint)
4435
4434
4436 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4435 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4437 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4436 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4438 other = hg.peer(repo, opts, source)
4437 other = hg.peer(repo, opts, source)
4439 try:
4438 try:
4440 revs, checkout = hg.addbranchrevs(repo, other, branches,
4439 revs, checkout = hg.addbranchrevs(repo, other, branches,
4441 opts.get('rev'))
4440 opts.get('rev'))
4442
4441
4443 pullopargs = {}
4442 pullopargs = {}
4444
4443
4445 nodes = None
4444 nodes = None
4446 if opts.get('bookmark') or revs:
4445 if opts.get('bookmark') or revs:
4447 # The list of bookmark used here is the same used to actually update
4446 # The list of bookmark used here is the same used to actually update
4448 # the bookmark names, to avoid the race from issue 4689 and we do
4447 # the bookmark names, to avoid the race from issue 4689 and we do
4449 # all lookup and bookmark queries in one go so they see the same
4448 # all lookup and bookmark queries in one go so they see the same
4450 # version of the server state (issue 4700).
4449 # version of the server state (issue 4700).
4451 nodes = []
4450 nodes = []
4452 fnodes = []
4451 fnodes = []
4453 revs = revs or []
4452 revs = revs or []
4454 if revs and not other.capable('lookup'):
4453 if revs and not other.capable('lookup'):
4455 err = _("other repository doesn't support revision lookup, "
4454 err = _("other repository doesn't support revision lookup, "
4456 "so a rev cannot be specified.")
4455 "so a rev cannot be specified.")
4457 raise error.Abort(err)
4456 raise error.Abort(err)
4458 with other.commandexecutor() as e:
4457 with other.commandexecutor() as e:
4459 fremotebookmarks = e.callcommand('listkeys', {
4458 fremotebookmarks = e.callcommand('listkeys', {
4460 'namespace': 'bookmarks'
4459 'namespace': 'bookmarks'
4461 })
4460 })
4462 for r in revs:
4461 for r in revs:
4463 fnodes.append(e.callcommand('lookup', {'key': r}))
4462 fnodes.append(e.callcommand('lookup', {'key': r}))
4464 remotebookmarks = fremotebookmarks.result()
4463 remotebookmarks = fremotebookmarks.result()
4465 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4464 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4466 pullopargs['remotebookmarks'] = remotebookmarks
4465 pullopargs['remotebookmarks'] = remotebookmarks
4467 for b in opts.get('bookmark', []):
4466 for b in opts.get('bookmark', []):
4468 b = repo._bookmarks.expandname(b)
4467 b = repo._bookmarks.expandname(b)
4469 if b not in remotebookmarks:
4468 if b not in remotebookmarks:
4470 raise error.Abort(_('remote bookmark %s not found!') % b)
4469 raise error.Abort(_('remote bookmark %s not found!') % b)
4471 nodes.append(remotebookmarks[b])
4470 nodes.append(remotebookmarks[b])
4472 for i, rev in enumerate(revs):
4471 for i, rev in enumerate(revs):
4473 node = fnodes[i].result()
4472 node = fnodes[i].result()
4474 nodes.append(node)
4473 nodes.append(node)
4475 if rev == checkout:
4474 if rev == checkout:
4476 checkout = node
4475 checkout = node
4477
4476
4478 wlock = util.nullcontextmanager()
4477 wlock = util.nullcontextmanager()
4479 if opts.get('update'):
4478 if opts.get('update'):
4480 wlock = repo.wlock()
4479 wlock = repo.wlock()
4481 with wlock:
4480 with wlock:
4482 pullopargs.update(opts.get('opargs', {}))
4481 pullopargs.update(opts.get('opargs', {}))
4483 modheads = exchange.pull(repo, other, heads=nodes,
4482 modheads = exchange.pull(repo, other, heads=nodes,
4484 force=opts.get('force'),
4483 force=opts.get('force'),
4485 bookmarks=opts.get('bookmark', ()),
4484 bookmarks=opts.get('bookmark', ()),
4486 opargs=pullopargs).cgresult
4485 opargs=pullopargs).cgresult
4487
4486
4488 # brev is a name, which might be a bookmark to be activated at
4487 # brev is a name, which might be a bookmark to be activated at
4489 # the end of the update. In other words, it is an explicit
4488 # the end of the update. In other words, it is an explicit
4490 # destination of the update
4489 # destination of the update
4491 brev = None
4490 brev = None
4492
4491
4493 if checkout:
4492 if checkout:
4494 checkout = repo.changelog.rev(checkout)
4493 checkout = repo.changelog.rev(checkout)
4495
4494
4496 # order below depends on implementation of
4495 # order below depends on implementation of
4497 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4496 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4498 # because 'checkout' is determined without it.
4497 # because 'checkout' is determined without it.
4499 if opts.get('rev'):
4498 if opts.get('rev'):
4500 brev = opts['rev'][0]
4499 brev = opts['rev'][0]
4501 elif opts.get('branch'):
4500 elif opts.get('branch'):
4502 brev = opts['branch'][0]
4501 brev = opts['branch'][0]
4503 else:
4502 else:
4504 brev = branches[0]
4503 brev = branches[0]
4505 repo._subtoppath = source
4504 repo._subtoppath = source
4506 try:
4505 try:
4507 ret = postincoming(ui, repo, modheads, opts.get('update'),
4506 ret = postincoming(ui, repo, modheads, opts.get('update'),
4508 checkout, brev)
4507 checkout, brev)
4509
4508
4510 finally:
4509 finally:
4511 del repo._subtoppath
4510 del repo._subtoppath
4512
4511
4513 finally:
4512 finally:
4514 other.close()
4513 other.close()
4515 return ret
4514 return ret
4516
4515
4517 @command('push',
4516 @command('push',
4518 [('f', 'force', None, _('force push')),
4517 [('f', 'force', None, _('force push')),
4519 ('r', 'rev', [],
4518 ('r', 'rev', [],
4520 _('a changeset intended to be included in the destination'),
4519 _('a changeset intended to be included in the destination'),
4521 _('REV')),
4520 _('REV')),
4522 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4521 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4523 ('b', 'branch', [],
4522 ('b', 'branch', [],
4524 _('a specific branch you would like to push'), _('BRANCH')),
4523 _('a specific branch you would like to push'), _('BRANCH')),
4525 ('', 'new-branch', False, _('allow pushing a new branch')),
4524 ('', 'new-branch', False, _('allow pushing a new branch')),
4526 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4525 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4527 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4526 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4528 ] + remoteopts,
4527 ] + remoteopts,
4529 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4528 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4530 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4529 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4531 helpbasic=True)
4530 helpbasic=True)
4532 def push(ui, repo, dest=None, **opts):
4531 def push(ui, repo, dest=None, **opts):
4533 """push changes to the specified destination
4532 """push changes to the specified destination
4534
4533
4535 Push changesets from the local repository to the specified
4534 Push changesets from the local repository to the specified
4536 destination.
4535 destination.
4537
4536
4538 This operation is symmetrical to pull: it is identical to a pull
4537 This operation is symmetrical to pull: it is identical to a pull
4539 in the destination repository from the current one.
4538 in the destination repository from the current one.
4540
4539
4541 By default, push will not allow creation of new heads at the
4540 By default, push will not allow creation of new heads at the
4542 destination, since multiple heads would make it unclear which head
4541 destination, since multiple heads would make it unclear which head
4543 to use. In this situation, it is recommended to pull and merge
4542 to use. In this situation, it is recommended to pull and merge
4544 before pushing.
4543 before pushing.
4545
4544
4546 Use --new-branch if you want to allow push to create a new named
4545 Use --new-branch if you want to allow push to create a new named
4547 branch that is not present at the destination. This allows you to
4546 branch that is not present at the destination. This allows you to
4548 only create a new branch without forcing other changes.
4547 only create a new branch without forcing other changes.
4549
4548
4550 .. note::
4549 .. note::
4551
4550
4552 Extra care should be taken with the -f/--force option,
4551 Extra care should be taken with the -f/--force option,
4553 which will push all new heads on all branches, an action which will
4552 which will push all new heads on all branches, an action which will
4554 almost always cause confusion for collaborators.
4553 almost always cause confusion for collaborators.
4555
4554
4556 If -r/--rev is used, the specified revision and all its ancestors
4555 If -r/--rev is used, the specified revision and all its ancestors
4557 will be pushed to the remote repository.
4556 will be pushed to the remote repository.
4558
4557
4559 If -B/--bookmark is used, the specified bookmarked revision, its
4558 If -B/--bookmark is used, the specified bookmarked revision, its
4560 ancestors, and the bookmark will be pushed to the remote
4559 ancestors, and the bookmark will be pushed to the remote
4561 repository. Specifying ``.`` is equivalent to specifying the active
4560 repository. Specifying ``.`` is equivalent to specifying the active
4562 bookmark's name.
4561 bookmark's name.
4563
4562
4564 Please see :hg:`help urls` for important details about ``ssh://``
4563 Please see :hg:`help urls` for important details about ``ssh://``
4565 URLs. If DESTINATION is omitted, a default path will be used.
4564 URLs. If DESTINATION is omitted, a default path will be used.
4566
4565
4567 .. container:: verbose
4566 .. container:: verbose
4568
4567
4569 The --pushvars option sends strings to the server that become
4568 The --pushvars option sends strings to the server that become
4570 environment variables prepended with ``HG_USERVAR_``. For example,
4569 environment variables prepended with ``HG_USERVAR_``. For example,
4571 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4570 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4572 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4571 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4573
4572
4574 pushvars can provide for user-overridable hooks as well as set debug
4573 pushvars can provide for user-overridable hooks as well as set debug
4575 levels. One example is having a hook that blocks commits containing
4574 levels. One example is having a hook that blocks commits containing
4576 conflict markers, but enables the user to override the hook if the file
4575 conflict markers, but enables the user to override the hook if the file
4577 is using conflict markers for testing purposes or the file format has
4576 is using conflict markers for testing purposes or the file format has
4578 strings that look like conflict markers.
4577 strings that look like conflict markers.
4579
4578
4580 By default, servers will ignore `--pushvars`. To enable it add the
4579 By default, servers will ignore `--pushvars`. To enable it add the
4581 following to your configuration file::
4580 following to your configuration file::
4582
4581
4583 [push]
4582 [push]
4584 pushvars.server = true
4583 pushvars.server = true
4585
4584
4586 Returns 0 if push was successful, 1 if nothing to push.
4585 Returns 0 if push was successful, 1 if nothing to push.
4587 """
4586 """
4588
4587
4589 opts = pycompat.byteskwargs(opts)
4588 opts = pycompat.byteskwargs(opts)
4590 if opts.get('bookmark'):
4589 if opts.get('bookmark'):
4591 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4590 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4592 for b in opts['bookmark']:
4591 for b in opts['bookmark']:
4593 # translate -B options to -r so changesets get pushed
4592 # translate -B options to -r so changesets get pushed
4594 b = repo._bookmarks.expandname(b)
4593 b = repo._bookmarks.expandname(b)
4595 if b in repo._bookmarks:
4594 if b in repo._bookmarks:
4596 opts.setdefault('rev', []).append(b)
4595 opts.setdefault('rev', []).append(b)
4597 else:
4596 else:
4598 # if we try to push a deleted bookmark, translate it to null
4597 # if we try to push a deleted bookmark, translate it to null
4599 # this lets simultaneous -r, -b options continue working
4598 # this lets simultaneous -r, -b options continue working
4600 opts.setdefault('rev', []).append("null")
4599 opts.setdefault('rev', []).append("null")
4601
4600
4602 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4601 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4603 if not path:
4602 if not path:
4604 raise error.Abort(_('default repository not configured!'),
4603 raise error.Abort(_('default repository not configured!'),
4605 hint=_("see 'hg help config.paths'"))
4604 hint=_("see 'hg help config.paths'"))
4606 dest = path.pushloc or path.loc
4605 dest = path.pushloc or path.loc
4607 branches = (path.branch, opts.get('branch') or [])
4606 branches = (path.branch, opts.get('branch') or [])
4608 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4607 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4609 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4608 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4610 other = hg.peer(repo, opts, dest)
4609 other = hg.peer(repo, opts, dest)
4611
4610
4612 if revs:
4611 if revs:
4613 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4612 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4614 if not revs:
4613 if not revs:
4615 raise error.Abort(_("specified revisions evaluate to an empty set"),
4614 raise error.Abort(_("specified revisions evaluate to an empty set"),
4616 hint=_("use different revision arguments"))
4615 hint=_("use different revision arguments"))
4617 elif path.pushrev:
4616 elif path.pushrev:
4618 # It doesn't make any sense to specify ancestor revisions. So limit
4617 # It doesn't make any sense to specify ancestor revisions. So limit
4619 # to DAG heads to make discovery simpler.
4618 # to DAG heads to make discovery simpler.
4620 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4619 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4621 revs = scmutil.revrange(repo, [expr])
4620 revs = scmutil.revrange(repo, [expr])
4622 revs = [repo[rev].node() for rev in revs]
4621 revs = [repo[rev].node() for rev in revs]
4623 if not revs:
4622 if not revs:
4624 raise error.Abort(_('default push revset for path evaluates to an '
4623 raise error.Abort(_('default push revset for path evaluates to an '
4625 'empty set'))
4624 'empty set'))
4626
4625
4627 repo._subtoppath = dest
4626 repo._subtoppath = dest
4628 try:
4627 try:
4629 # push subrepos depth-first for coherent ordering
4628 # push subrepos depth-first for coherent ordering
4630 c = repo['.']
4629 c = repo['.']
4631 subs = c.substate # only repos that are committed
4630 subs = c.substate # only repos that are committed
4632 for s in sorted(subs):
4631 for s in sorted(subs):
4633 result = c.sub(s).push(opts)
4632 result = c.sub(s).push(opts)
4634 if result == 0:
4633 if result == 0:
4635 return not result
4634 return not result
4636 finally:
4635 finally:
4637 del repo._subtoppath
4636 del repo._subtoppath
4638
4637
4639 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4638 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4640 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4639 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4641
4640
4642 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4641 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4643 newbranch=opts.get('new_branch'),
4642 newbranch=opts.get('new_branch'),
4644 bookmarks=opts.get('bookmark', ()),
4643 bookmarks=opts.get('bookmark', ()),
4645 publish=opts.get('publish'),
4644 publish=opts.get('publish'),
4646 opargs=opargs)
4645 opargs=opargs)
4647
4646
4648 result = not pushop.cgresult
4647 result = not pushop.cgresult
4649
4648
4650 if pushop.bkresult is not None:
4649 if pushop.bkresult is not None:
4651 if pushop.bkresult == 2:
4650 if pushop.bkresult == 2:
4652 result = 2
4651 result = 2
4653 elif not result and pushop.bkresult:
4652 elif not result and pushop.bkresult:
4654 result = 2
4653 result = 2
4655
4654
4656 return result
4655 return result
4657
4656
4658 @command('recover', [], helpcategory=command.CATEGORY_MAINTENANCE)
4657 @command('recover', [], helpcategory=command.CATEGORY_MAINTENANCE)
4659 def recover(ui, repo):
4658 def recover(ui, repo):
4660 """roll back an interrupted transaction
4659 """roll back an interrupted transaction
4661
4660
4662 Recover from an interrupted commit or pull.
4661 Recover from an interrupted commit or pull.
4663
4662
4664 This command tries to fix the repository status after an
4663 This command tries to fix the repository status after an
4665 interrupted operation. It should only be necessary when Mercurial
4664 interrupted operation. It should only be necessary when Mercurial
4666 suggests it.
4665 suggests it.
4667
4666
4668 Returns 0 if successful, 1 if nothing to recover or verify fails.
4667 Returns 0 if successful, 1 if nothing to recover or verify fails.
4669 """
4668 """
4670 if repo.recover():
4669 if repo.recover():
4671 return hg.verify(repo)
4670 return hg.verify(repo)
4672 return 1
4671 return 1
4673
4672
4674 @command('remove|rm',
4673 @command('remove|rm',
4675 [('A', 'after', None, _('record delete for missing files')),
4674 [('A', 'after', None, _('record delete for missing files')),
4676 ('f', 'force', None,
4675 ('f', 'force', None,
4677 _('forget added files, delete modified files')),
4676 _('forget added files, delete modified files')),
4678 ] + subrepoopts + walkopts + dryrunopts,
4677 ] + subrepoopts + walkopts + dryrunopts,
4679 _('[OPTION]... FILE...'),
4678 _('[OPTION]... FILE...'),
4680 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4679 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4681 helpbasic=True, inferrepo=True)
4680 helpbasic=True, inferrepo=True)
4682 def remove(ui, repo, *pats, **opts):
4681 def remove(ui, repo, *pats, **opts):
4683 """remove the specified files on the next commit
4682 """remove the specified files on the next commit
4684
4683
4685 Schedule the indicated files for removal from the current branch.
4684 Schedule the indicated files for removal from the current branch.
4686
4685
4687 This command schedules the files to be removed at the next commit.
4686 This command schedules the files to be removed at the next commit.
4688 To undo a remove before that, see :hg:`revert`. To undo added
4687 To undo a remove before that, see :hg:`revert`. To undo added
4689 files, see :hg:`forget`.
4688 files, see :hg:`forget`.
4690
4689
4691 .. container:: verbose
4690 .. container:: verbose
4692
4691
4693 -A/--after can be used to remove only files that have already
4692 -A/--after can be used to remove only files that have already
4694 been deleted, -f/--force can be used to force deletion, and -Af
4693 been deleted, -f/--force can be used to force deletion, and -Af
4695 can be used to remove files from the next revision without
4694 can be used to remove files from the next revision without
4696 deleting them from the working directory.
4695 deleting them from the working directory.
4697
4696
4698 The following table details the behavior of remove for different
4697 The following table details the behavior of remove for different
4699 file states (columns) and option combinations (rows). The file
4698 file states (columns) and option combinations (rows). The file
4700 states are Added [A], Clean [C], Modified [M] and Missing [!]
4699 states are Added [A], Clean [C], Modified [M] and Missing [!]
4701 (as reported by :hg:`status`). The actions are Warn, Remove
4700 (as reported by :hg:`status`). The actions are Warn, Remove
4702 (from branch) and Delete (from disk):
4701 (from branch) and Delete (from disk):
4703
4702
4704 ========= == == == ==
4703 ========= == == == ==
4705 opt/state A C M !
4704 opt/state A C M !
4706 ========= == == == ==
4705 ========= == == == ==
4707 none W RD W R
4706 none W RD W R
4708 -f R RD RD R
4707 -f R RD RD R
4709 -A W W W R
4708 -A W W W R
4710 -Af R R R R
4709 -Af R R R R
4711 ========= == == == ==
4710 ========= == == == ==
4712
4711
4713 .. note::
4712 .. note::
4714
4713
4715 :hg:`remove` never deletes files in Added [A] state from the
4714 :hg:`remove` never deletes files in Added [A] state from the
4716 working directory, not even if ``--force`` is specified.
4715 working directory, not even if ``--force`` is specified.
4717
4716
4718 Returns 0 on success, 1 if any warnings encountered.
4717 Returns 0 on success, 1 if any warnings encountered.
4719 """
4718 """
4720
4719
4721 opts = pycompat.byteskwargs(opts)
4720 opts = pycompat.byteskwargs(opts)
4722 after, force = opts.get('after'), opts.get('force')
4721 after, force = opts.get('after'), opts.get('force')
4723 dryrun = opts.get('dry_run')
4722 dryrun = opts.get('dry_run')
4724 if not pats and not after:
4723 if not pats and not after:
4725 raise error.Abort(_('no files specified'))
4724 raise error.Abort(_('no files specified'))
4726
4725
4727 m = scmutil.match(repo[None], pats, opts)
4726 m = scmutil.match(repo[None], pats, opts)
4728 subrepos = opts.get('subrepos')
4727 subrepos = opts.get('subrepos')
4729 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4728 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4730 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4729 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4731 dryrun=dryrun)
4730 dryrun=dryrun)
4732
4731
4733 @command('rename|move|mv',
4732 @command('rename|move|mv',
4734 [('A', 'after', None, _('record a rename that has already occurred')),
4733 [('A', 'after', None, _('record a rename that has already occurred')),
4735 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4734 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4736 ] + walkopts + dryrunopts,
4735 ] + walkopts + dryrunopts,
4737 _('[OPTION]... SOURCE... DEST'),
4736 _('[OPTION]... SOURCE... DEST'),
4738 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4737 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4739 def rename(ui, repo, *pats, **opts):
4738 def rename(ui, repo, *pats, **opts):
4740 """rename files; equivalent of copy + remove
4739 """rename files; equivalent of copy + remove
4741
4740
4742 Mark dest as copies of sources; mark sources for deletion. If dest
4741 Mark dest as copies of sources; mark sources for deletion. If dest
4743 is a directory, copies are put in that directory. If dest is a
4742 is a directory, copies are put in that directory. If dest is a
4744 file, there can only be one source.
4743 file, there can only be one source.
4745
4744
4746 By default, this command copies the contents of files as they
4745 By default, this command copies the contents of files as they
4747 exist in the working directory. If invoked with -A/--after, the
4746 exist in the working directory. If invoked with -A/--after, the
4748 operation is recorded, but no copying is performed.
4747 operation is recorded, but no copying is performed.
4749
4748
4750 This command takes effect at the next commit. To undo a rename
4749 This command takes effect at the next commit. To undo a rename
4751 before that, see :hg:`revert`.
4750 before that, see :hg:`revert`.
4752
4751
4753 Returns 0 on success, 1 if errors are encountered.
4752 Returns 0 on success, 1 if errors are encountered.
4754 """
4753 """
4755 opts = pycompat.byteskwargs(opts)
4754 opts = pycompat.byteskwargs(opts)
4756 with repo.wlock(False):
4755 with repo.wlock(False):
4757 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4756 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4758
4757
4759 @command('resolve',
4758 @command('resolve',
4760 [('a', 'all', None, _('select all unresolved files')),
4759 [('a', 'all', None, _('select all unresolved files')),
4761 ('l', 'list', None, _('list state of files needing merge')),
4760 ('l', 'list', None, _('list state of files needing merge')),
4762 ('m', 'mark', None, _('mark files as resolved')),
4761 ('m', 'mark', None, _('mark files as resolved')),
4763 ('u', 'unmark', None, _('mark files as unresolved')),
4762 ('u', 'unmark', None, _('mark files as unresolved')),
4764 ('n', 'no-status', None, _('hide status prefix')),
4763 ('n', 'no-status', None, _('hide status prefix')),
4765 ('', 're-merge', None, _('re-merge files'))]
4764 ('', 're-merge', None, _('re-merge files'))]
4766 + mergetoolopts + walkopts + formatteropts,
4765 + mergetoolopts + walkopts + formatteropts,
4767 _('[OPTION]... [FILE]...'),
4766 _('[OPTION]... [FILE]...'),
4768 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4767 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4769 inferrepo=True)
4768 inferrepo=True)
4770 def resolve(ui, repo, *pats, **opts):
4769 def resolve(ui, repo, *pats, **opts):
4771 """redo merges or set/view the merge status of files
4770 """redo merges or set/view the merge status of files
4772
4771
4773 Merges with unresolved conflicts are often the result of
4772 Merges with unresolved conflicts are often the result of
4774 non-interactive merging using the ``internal:merge`` configuration
4773 non-interactive merging using the ``internal:merge`` configuration
4775 setting, or a command-line merge tool like ``diff3``. The resolve
4774 setting, or a command-line merge tool like ``diff3``. The resolve
4776 command is used to manage the files involved in a merge, after
4775 command is used to manage the files involved in a merge, after
4777 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4776 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4778 working directory must have two parents). See :hg:`help
4777 working directory must have two parents). See :hg:`help
4779 merge-tools` for information on configuring merge tools.
4778 merge-tools` for information on configuring merge tools.
4780
4779
4781 The resolve command can be used in the following ways:
4780 The resolve command can be used in the following ways:
4782
4781
4783 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4782 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4784 the specified files, discarding any previous merge attempts. Re-merging
4783 the specified files, discarding any previous merge attempts. Re-merging
4785 is not performed for files already marked as resolved. Use ``--all/-a``
4784 is not performed for files already marked as resolved. Use ``--all/-a``
4786 to select all unresolved files. ``--tool`` can be used to specify
4785 to select all unresolved files. ``--tool`` can be used to specify
4787 the merge tool used for the given files. It overrides the HGMERGE
4786 the merge tool used for the given files. It overrides the HGMERGE
4788 environment variable and your configuration files. Previous file
4787 environment variable and your configuration files. Previous file
4789 contents are saved with a ``.orig`` suffix.
4788 contents are saved with a ``.orig`` suffix.
4790
4789
4791 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4790 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4792 (e.g. after having manually fixed-up the files). The default is
4791 (e.g. after having manually fixed-up the files). The default is
4793 to mark all unresolved files.
4792 to mark all unresolved files.
4794
4793
4795 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4794 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4796 default is to mark all resolved files.
4795 default is to mark all resolved files.
4797
4796
4798 - :hg:`resolve -l`: list files which had or still have conflicts.
4797 - :hg:`resolve -l`: list files which had or still have conflicts.
4799 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4798 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4800 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4799 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4801 the list. See :hg:`help filesets` for details.
4800 the list. See :hg:`help filesets` for details.
4802
4801
4803 .. note::
4802 .. note::
4804
4803
4805 Mercurial will not let you commit files with unresolved merge
4804 Mercurial will not let you commit files with unresolved merge
4806 conflicts. You must use :hg:`resolve -m ...` before you can
4805 conflicts. You must use :hg:`resolve -m ...` before you can
4807 commit after a conflicting merge.
4806 commit after a conflicting merge.
4808
4807
4809 .. container:: verbose
4808 .. container:: verbose
4810
4809
4811 Template:
4810 Template:
4812
4811
4813 The following keywords are supported in addition to the common template
4812 The following keywords are supported in addition to the common template
4814 keywords and functions. See also :hg:`help templates`.
4813 keywords and functions. See also :hg:`help templates`.
4815
4814
4816 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4815 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4817 :path: String. Repository-absolute path of the file.
4816 :path: String. Repository-absolute path of the file.
4818
4817
4819 Returns 0 on success, 1 if any files fail a resolve attempt.
4818 Returns 0 on success, 1 if any files fail a resolve attempt.
4820 """
4819 """
4821
4820
4822 opts = pycompat.byteskwargs(opts)
4821 opts = pycompat.byteskwargs(opts)
4823 confirm = ui.configbool('commands', 'resolve.confirm')
4822 confirm = ui.configbool('commands', 'resolve.confirm')
4824 flaglist = 'all mark unmark list no_status re_merge'.split()
4823 flaglist = 'all mark unmark list no_status re_merge'.split()
4825 all, mark, unmark, show, nostatus, remerge = [
4824 all, mark, unmark, show, nostatus, remerge = [
4826 opts.get(o) for o in flaglist]
4825 opts.get(o) for o in flaglist]
4827
4826
4828 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4827 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4829 if actioncount > 1:
4828 if actioncount > 1:
4830 raise error.Abort(_("too many actions specified"))
4829 raise error.Abort(_("too many actions specified"))
4831 elif (actioncount == 0
4830 elif (actioncount == 0
4832 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4831 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4833 hint = _('use --mark, --unmark, --list or --re-merge')
4832 hint = _('use --mark, --unmark, --list or --re-merge')
4834 raise error.Abort(_('no action specified'), hint=hint)
4833 raise error.Abort(_('no action specified'), hint=hint)
4835 if pats and all:
4834 if pats and all:
4836 raise error.Abort(_("can't specify --all and patterns"))
4835 raise error.Abort(_("can't specify --all and patterns"))
4837 if not (all or pats or show or mark or unmark):
4836 if not (all or pats or show or mark or unmark):
4838 raise error.Abort(_('no files or directories specified'),
4837 raise error.Abort(_('no files or directories specified'),
4839 hint=('use --all to re-merge all unresolved files'))
4838 hint=('use --all to re-merge all unresolved files'))
4840
4839
4841 if confirm:
4840 if confirm:
4842 if all:
4841 if all:
4843 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4842 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4844 b'$$ &Yes $$ &No')):
4843 b'$$ &Yes $$ &No')):
4845 raise error.Abort(_('user quit'))
4844 raise error.Abort(_('user quit'))
4846 if mark and not pats:
4845 if mark and not pats:
4847 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4846 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4848 b'$$ &Yes $$ &No')):
4847 b'$$ &Yes $$ &No')):
4849 raise error.Abort(_('user quit'))
4848 raise error.Abort(_('user quit'))
4850 if unmark and not pats:
4849 if unmark and not pats:
4851 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4850 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4852 b'$$ &Yes $$ &No')):
4851 b'$$ &Yes $$ &No')):
4853 raise error.Abort(_('user quit'))
4852 raise error.Abort(_('user quit'))
4854
4853
4855 uipathfn = scmutil.getuipathfn(repo)
4854 uipathfn = scmutil.getuipathfn(repo)
4856
4855
4857 if show:
4856 if show:
4858 ui.pager('resolve')
4857 ui.pager('resolve')
4859 fm = ui.formatter('resolve', opts)
4858 fm = ui.formatter('resolve', opts)
4860 ms = mergemod.mergestate.read(repo)
4859 ms = mergemod.mergestate.read(repo)
4861 wctx = repo[None]
4860 wctx = repo[None]
4862 m = scmutil.match(wctx, pats, opts)
4861 m = scmutil.match(wctx, pats, opts)
4863
4862
4864 # Labels and keys based on merge state. Unresolved path conflicts show
4863 # Labels and keys based on merge state. Unresolved path conflicts show
4865 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4864 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4866 # resolved conflicts.
4865 # resolved conflicts.
4867 mergestateinfo = {
4866 mergestateinfo = {
4868 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4867 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4869 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4868 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4870 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4869 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4871 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4870 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4872 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4871 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4873 'D'),
4872 'D'),
4874 }
4873 }
4875
4874
4876 for f in ms:
4875 for f in ms:
4877 if not m(f):
4876 if not m(f):
4878 continue
4877 continue
4879
4878
4880 label, key = mergestateinfo[ms[f]]
4879 label, key = mergestateinfo[ms[f]]
4881 fm.startitem()
4880 fm.startitem()
4882 fm.context(ctx=wctx)
4881 fm.context(ctx=wctx)
4883 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4882 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4884 fm.data(path=f)
4883 fm.data(path=f)
4885 fm.plain('%s\n' % uipathfn(f), label=label)
4884 fm.plain('%s\n' % uipathfn(f), label=label)
4886 fm.end()
4885 fm.end()
4887 return 0
4886 return 0
4888
4887
4889 with repo.wlock():
4888 with repo.wlock():
4890 ms = mergemod.mergestate.read(repo)
4889 ms = mergemod.mergestate.read(repo)
4891
4890
4892 if not (ms.active() or repo.dirstate.p2() != nullid):
4891 if not (ms.active() or repo.dirstate.p2() != nullid):
4893 raise error.Abort(
4892 raise error.Abort(
4894 _('resolve command not applicable when not merging'))
4893 _('resolve command not applicable when not merging'))
4895
4894
4896 wctx = repo[None]
4895 wctx = repo[None]
4897
4896
4898 if (ms.mergedriver
4897 if (ms.mergedriver
4899 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4898 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4900 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4899 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4901 ms.commit()
4900 ms.commit()
4902 # allow mark and unmark to go through
4901 # allow mark and unmark to go through
4903 if not mark and not unmark and not proceed:
4902 if not mark and not unmark and not proceed:
4904 return 1
4903 return 1
4905
4904
4906 m = scmutil.match(wctx, pats, opts)
4905 m = scmutil.match(wctx, pats, opts)
4907 ret = 0
4906 ret = 0
4908 didwork = False
4907 didwork = False
4909 runconclude = False
4908 runconclude = False
4910
4909
4911 tocomplete = []
4910 tocomplete = []
4912 hasconflictmarkers = []
4911 hasconflictmarkers = []
4913 if mark:
4912 if mark:
4914 markcheck = ui.config('commands', 'resolve.mark-check')
4913 markcheck = ui.config('commands', 'resolve.mark-check')
4915 if markcheck not in ['warn', 'abort']:
4914 if markcheck not in ['warn', 'abort']:
4916 # Treat all invalid / unrecognized values as 'none'.
4915 # Treat all invalid / unrecognized values as 'none'.
4917 markcheck = False
4916 markcheck = False
4918 for f in ms:
4917 for f in ms:
4919 if not m(f):
4918 if not m(f):
4920 continue
4919 continue
4921
4920
4922 didwork = True
4921 didwork = True
4923
4922
4924 # don't let driver-resolved files be marked, and run the conclude
4923 # don't let driver-resolved files be marked, and run the conclude
4925 # step if asked to resolve
4924 # step if asked to resolve
4926 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4925 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4927 exact = m.exact(f)
4926 exact = m.exact(f)
4928 if mark:
4927 if mark:
4929 if exact:
4928 if exact:
4930 ui.warn(_('not marking %s as it is driver-resolved\n')
4929 ui.warn(_('not marking %s as it is driver-resolved\n')
4931 % uipathfn(f))
4930 % uipathfn(f))
4932 elif unmark:
4931 elif unmark:
4933 if exact:
4932 if exact:
4934 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4933 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4935 % uipathfn(f))
4934 % uipathfn(f))
4936 else:
4935 else:
4937 runconclude = True
4936 runconclude = True
4938 continue
4937 continue
4939
4938
4940 # path conflicts must be resolved manually
4939 # path conflicts must be resolved manually
4941 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4940 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4942 mergemod.MERGE_RECORD_RESOLVED_PATH):
4941 mergemod.MERGE_RECORD_RESOLVED_PATH):
4943 if mark:
4942 if mark:
4944 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4943 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4945 elif unmark:
4944 elif unmark:
4946 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4945 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4947 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4946 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4948 ui.warn(_('%s: path conflict must be resolved manually\n')
4947 ui.warn(_('%s: path conflict must be resolved manually\n')
4949 % uipathfn(f))
4948 % uipathfn(f))
4950 continue
4949 continue
4951
4950
4952 if mark:
4951 if mark:
4953 if markcheck:
4952 if markcheck:
4954 fdata = repo.wvfs.tryread(f)
4953 fdata = repo.wvfs.tryread(f)
4955 if (filemerge.hasconflictmarkers(fdata) and
4954 if (filemerge.hasconflictmarkers(fdata) and
4956 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4955 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4957 hasconflictmarkers.append(f)
4956 hasconflictmarkers.append(f)
4958 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4957 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4959 elif unmark:
4958 elif unmark:
4960 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4959 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4961 else:
4960 else:
4962 # backup pre-resolve (merge uses .orig for its own purposes)
4961 # backup pre-resolve (merge uses .orig for its own purposes)
4963 a = repo.wjoin(f)
4962 a = repo.wjoin(f)
4964 try:
4963 try:
4965 util.copyfile(a, a + ".resolve")
4964 util.copyfile(a, a + ".resolve")
4966 except (IOError, OSError) as inst:
4965 except (IOError, OSError) as inst:
4967 if inst.errno != errno.ENOENT:
4966 if inst.errno != errno.ENOENT:
4968 raise
4967 raise
4969
4968
4970 try:
4969 try:
4971 # preresolve file
4970 # preresolve file
4972 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4971 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4973 with ui.configoverride(overrides, 'resolve'):
4972 with ui.configoverride(overrides, 'resolve'):
4974 complete, r = ms.preresolve(f, wctx)
4973 complete, r = ms.preresolve(f, wctx)
4975 if not complete:
4974 if not complete:
4976 tocomplete.append(f)
4975 tocomplete.append(f)
4977 elif r:
4976 elif r:
4978 ret = 1
4977 ret = 1
4979 finally:
4978 finally:
4980 ms.commit()
4979 ms.commit()
4981
4980
4982 # replace filemerge's .orig file with our resolve file, but only
4981 # replace filemerge's .orig file with our resolve file, but only
4983 # for merges that are complete
4982 # for merges that are complete
4984 if complete:
4983 if complete:
4985 try:
4984 try:
4986 util.rename(a + ".resolve",
4985 util.rename(a + ".resolve",
4987 scmutil.backuppath(ui, repo, f))
4986 scmutil.backuppath(ui, repo, f))
4988 except OSError as inst:
4987 except OSError as inst:
4989 if inst.errno != errno.ENOENT:
4988 if inst.errno != errno.ENOENT:
4990 raise
4989 raise
4991
4990
4992 if hasconflictmarkers:
4991 if hasconflictmarkers:
4993 ui.warn(_('warning: the following files still have conflict '
4992 ui.warn(_('warning: the following files still have conflict '
4994 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
4993 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
4995 for f in hasconflictmarkers))
4994 for f in hasconflictmarkers))
4996 if markcheck == 'abort' and not all and not pats:
4995 if markcheck == 'abort' and not all and not pats:
4997 raise error.Abort(_('conflict markers detected'),
4996 raise error.Abort(_('conflict markers detected'),
4998 hint=_('use --all to mark anyway'))
4997 hint=_('use --all to mark anyway'))
4999
4998
5000 for f in tocomplete:
4999 for f in tocomplete:
5001 try:
5000 try:
5002 # resolve file
5001 # resolve file
5003 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5002 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5004 with ui.configoverride(overrides, 'resolve'):
5003 with ui.configoverride(overrides, 'resolve'):
5005 r = ms.resolve(f, wctx)
5004 r = ms.resolve(f, wctx)
5006 if r:
5005 if r:
5007 ret = 1
5006 ret = 1
5008 finally:
5007 finally:
5009 ms.commit()
5008 ms.commit()
5010
5009
5011 # replace filemerge's .orig file with our resolve file
5010 # replace filemerge's .orig file with our resolve file
5012 a = repo.wjoin(f)
5011 a = repo.wjoin(f)
5013 try:
5012 try:
5014 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5013 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5015 except OSError as inst:
5014 except OSError as inst:
5016 if inst.errno != errno.ENOENT:
5015 if inst.errno != errno.ENOENT:
5017 raise
5016 raise
5018
5017
5019 ms.commit()
5018 ms.commit()
5020 ms.recordactions()
5019 ms.recordactions()
5021
5020
5022 if not didwork and pats:
5021 if not didwork and pats:
5023 hint = None
5022 hint = None
5024 if not any([p for p in pats if p.find(':') >= 0]):
5023 if not any([p for p in pats if p.find(':') >= 0]):
5025 pats = ['path:%s' % p for p in pats]
5024 pats = ['path:%s' % p for p in pats]
5026 m = scmutil.match(wctx, pats, opts)
5025 m = scmutil.match(wctx, pats, opts)
5027 for f in ms:
5026 for f in ms:
5028 if not m(f):
5027 if not m(f):
5029 continue
5028 continue
5030 def flag(o):
5029 def flag(o):
5031 if o == 're_merge':
5030 if o == 're_merge':
5032 return '--re-merge '
5031 return '--re-merge '
5033 return '-%s ' % o[0:1]
5032 return '-%s ' % o[0:1]
5034 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5033 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5035 hint = _("(try: hg resolve %s%s)\n") % (
5034 hint = _("(try: hg resolve %s%s)\n") % (
5036 flags,
5035 flags,
5037 ' '.join(pats))
5036 ' '.join(pats))
5038 break
5037 break
5039 ui.warn(_("arguments do not match paths that need resolving\n"))
5038 ui.warn(_("arguments do not match paths that need resolving\n"))
5040 if hint:
5039 if hint:
5041 ui.warn(hint)
5040 ui.warn(hint)
5042 elif ms.mergedriver and ms.mdstate() != 's':
5041 elif ms.mergedriver and ms.mdstate() != 's':
5043 # run conclude step when either a driver-resolved file is requested
5042 # run conclude step when either a driver-resolved file is requested
5044 # or there are no driver-resolved files
5043 # or there are no driver-resolved files
5045 # we can't use 'ret' to determine whether any files are unresolved
5044 # we can't use 'ret' to determine whether any files are unresolved
5046 # because we might not have tried to resolve some
5045 # because we might not have tried to resolve some
5047 if ((runconclude or not list(ms.driverresolved()))
5046 if ((runconclude or not list(ms.driverresolved()))
5048 and not list(ms.unresolved())):
5047 and not list(ms.unresolved())):
5049 proceed = mergemod.driverconclude(repo, ms, wctx)
5048 proceed = mergemod.driverconclude(repo, ms, wctx)
5050 ms.commit()
5049 ms.commit()
5051 if not proceed:
5050 if not proceed:
5052 return 1
5051 return 1
5053
5052
5054 # Nudge users into finishing an unfinished operation
5053 # Nudge users into finishing an unfinished operation
5055 unresolvedf = list(ms.unresolved())
5054 unresolvedf = list(ms.unresolved())
5056 driverresolvedf = list(ms.driverresolved())
5055 driverresolvedf = list(ms.driverresolved())
5057 if not unresolvedf and not driverresolvedf:
5056 if not unresolvedf and not driverresolvedf:
5058 ui.status(_('(no more unresolved files)\n'))
5057 ui.status(_('(no more unresolved files)\n'))
5059 cmdutil.checkafterresolved(repo)
5058 cmdutil.checkafterresolved(repo)
5060 elif not unresolvedf:
5059 elif not unresolvedf:
5061 ui.status(_('(no more unresolved files -- '
5060 ui.status(_('(no more unresolved files -- '
5062 'run "hg resolve --all" to conclude)\n'))
5061 'run "hg resolve --all" to conclude)\n'))
5063
5062
5064 return ret
5063 return ret
5065
5064
5066 @command('revert',
5065 @command('revert',
5067 [('a', 'all', None, _('revert all changes when no arguments given')),
5066 [('a', 'all', None, _('revert all changes when no arguments given')),
5068 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5067 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5069 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5068 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5070 ('C', 'no-backup', None, _('do not save backup copies of files')),
5069 ('C', 'no-backup', None, _('do not save backup copies of files')),
5071 ('i', 'interactive', None, _('interactively select the changes')),
5070 ('i', 'interactive', None, _('interactively select the changes')),
5072 ] + walkopts + dryrunopts,
5071 ] + walkopts + dryrunopts,
5073 _('[OPTION]... [-r REV] [NAME]...'),
5072 _('[OPTION]... [-r REV] [NAME]...'),
5074 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5073 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5075 def revert(ui, repo, *pats, **opts):
5074 def revert(ui, repo, *pats, **opts):
5076 """restore files to their checkout state
5075 """restore files to their checkout state
5077
5076
5078 .. note::
5077 .. note::
5079
5078
5080 To check out earlier revisions, you should use :hg:`update REV`.
5079 To check out earlier revisions, you should use :hg:`update REV`.
5081 To cancel an uncommitted merge (and lose your changes),
5080 To cancel an uncommitted merge (and lose your changes),
5082 use :hg:`merge --abort`.
5081 use :hg:`merge --abort`.
5083
5082
5084 With no revision specified, revert the specified files or directories
5083 With no revision specified, revert the specified files or directories
5085 to the contents they had in the parent of the working directory.
5084 to the contents they had in the parent of the working directory.
5086 This restores the contents of files to an unmodified
5085 This restores the contents of files to an unmodified
5087 state and unschedules adds, removes, copies, and renames. If the
5086 state and unschedules adds, removes, copies, and renames. If the
5088 working directory has two parents, you must explicitly specify a
5087 working directory has two parents, you must explicitly specify a
5089 revision.
5088 revision.
5090
5089
5091 Using the -r/--rev or -d/--date options, revert the given files or
5090 Using the -r/--rev or -d/--date options, revert the given files or
5092 directories to their states as of a specific revision. Because
5091 directories to their states as of a specific revision. Because
5093 revert does not change the working directory parents, this will
5092 revert does not change the working directory parents, this will
5094 cause these files to appear modified. This can be helpful to "back
5093 cause these files to appear modified. This can be helpful to "back
5095 out" some or all of an earlier change. See :hg:`backout` for a
5094 out" some or all of an earlier change. See :hg:`backout` for a
5096 related method.
5095 related method.
5097
5096
5098 Modified files are saved with a .orig suffix before reverting.
5097 Modified files are saved with a .orig suffix before reverting.
5099 To disable these backups, use --no-backup. It is possible to store
5098 To disable these backups, use --no-backup. It is possible to store
5100 the backup files in a custom directory relative to the root of the
5099 the backup files in a custom directory relative to the root of the
5101 repository by setting the ``ui.origbackuppath`` configuration
5100 repository by setting the ``ui.origbackuppath`` configuration
5102 option.
5101 option.
5103
5102
5104 See :hg:`help dates` for a list of formats valid for -d/--date.
5103 See :hg:`help dates` for a list of formats valid for -d/--date.
5105
5104
5106 See :hg:`help backout` for a way to reverse the effect of an
5105 See :hg:`help backout` for a way to reverse the effect of an
5107 earlier changeset.
5106 earlier changeset.
5108
5107
5109 Returns 0 on success.
5108 Returns 0 on success.
5110 """
5109 """
5111
5110
5112 opts = pycompat.byteskwargs(opts)
5111 opts = pycompat.byteskwargs(opts)
5113 if opts.get("date"):
5112 if opts.get("date"):
5114 if opts.get("rev"):
5113 if opts.get("rev"):
5115 raise error.Abort(_("you can't specify a revision and a date"))
5114 raise error.Abort(_("you can't specify a revision and a date"))
5116 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5115 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5117
5116
5118 parent, p2 = repo.dirstate.parents()
5117 parent, p2 = repo.dirstate.parents()
5119 if not opts.get('rev') and p2 != nullid:
5118 if not opts.get('rev') and p2 != nullid:
5120 # revert after merge is a trap for new users (issue2915)
5119 # revert after merge is a trap for new users (issue2915)
5121 raise error.Abort(_('uncommitted merge with no revision specified'),
5120 raise error.Abort(_('uncommitted merge with no revision specified'),
5122 hint=_("use 'hg update' or see 'hg help revert'"))
5121 hint=_("use 'hg update' or see 'hg help revert'"))
5123
5122
5124 rev = opts.get('rev')
5123 rev = opts.get('rev')
5125 if rev:
5124 if rev:
5126 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5125 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5127 ctx = scmutil.revsingle(repo, rev)
5126 ctx = scmutil.revsingle(repo, rev)
5128
5127
5129 if (not (pats or opts.get('include') or opts.get('exclude') or
5128 if (not (pats or opts.get('include') or opts.get('exclude') or
5130 opts.get('all') or opts.get('interactive'))):
5129 opts.get('all') or opts.get('interactive'))):
5131 msg = _("no files or directories specified")
5130 msg = _("no files or directories specified")
5132 if p2 != nullid:
5131 if p2 != nullid:
5133 hint = _("uncommitted merge, use --all to discard all changes,"
5132 hint = _("uncommitted merge, use --all to discard all changes,"
5134 " or 'hg update -C .' to abort the merge")
5133 " or 'hg update -C .' to abort the merge")
5135 raise error.Abort(msg, hint=hint)
5134 raise error.Abort(msg, hint=hint)
5136 dirty = any(repo.status())
5135 dirty = any(repo.status())
5137 node = ctx.node()
5136 node = ctx.node()
5138 if node != parent:
5137 if node != parent:
5139 if dirty:
5138 if dirty:
5140 hint = _("uncommitted changes, use --all to discard all"
5139 hint = _("uncommitted changes, use --all to discard all"
5141 " changes, or 'hg update %d' to update") % ctx.rev()
5140 " changes, or 'hg update %d' to update") % ctx.rev()
5142 else:
5141 else:
5143 hint = _("use --all to revert all files,"
5142 hint = _("use --all to revert all files,"
5144 " or 'hg update %d' to update") % ctx.rev()
5143 " or 'hg update %d' to update") % ctx.rev()
5145 elif dirty:
5144 elif dirty:
5146 hint = _("uncommitted changes, use --all to discard all changes")
5145 hint = _("uncommitted changes, use --all to discard all changes")
5147 else:
5146 else:
5148 hint = _("use --all to revert all files")
5147 hint = _("use --all to revert all files")
5149 raise error.Abort(msg, hint=hint)
5148 raise error.Abort(msg, hint=hint)
5150
5149
5151 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5150 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5152 **pycompat.strkwargs(opts))
5151 **pycompat.strkwargs(opts))
5153
5152
5154 @command(
5153 @command(
5155 'rollback',
5154 'rollback',
5156 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5155 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5157 helpcategory=command.CATEGORY_MAINTENANCE)
5156 helpcategory=command.CATEGORY_MAINTENANCE)
5158 def rollback(ui, repo, **opts):
5157 def rollback(ui, repo, **opts):
5159 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5158 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5160
5159
5161 Please use :hg:`commit --amend` instead of rollback to correct
5160 Please use :hg:`commit --amend` instead of rollback to correct
5162 mistakes in the last commit.
5161 mistakes in the last commit.
5163
5162
5164 This command should be used with care. There is only one level of
5163 This command should be used with care. There is only one level of
5165 rollback, and there is no way to undo a rollback. It will also
5164 rollback, and there is no way to undo a rollback. It will also
5166 restore the dirstate at the time of the last transaction, losing
5165 restore the dirstate at the time of the last transaction, losing
5167 any dirstate changes since that time. This command does not alter
5166 any dirstate changes since that time. This command does not alter
5168 the working directory.
5167 the working directory.
5169
5168
5170 Transactions are used to encapsulate the effects of all commands
5169 Transactions are used to encapsulate the effects of all commands
5171 that create new changesets or propagate existing changesets into a
5170 that create new changesets or propagate existing changesets into a
5172 repository.
5171 repository.
5173
5172
5174 .. container:: verbose
5173 .. container:: verbose
5175
5174
5176 For example, the following commands are transactional, and their
5175 For example, the following commands are transactional, and their
5177 effects can be rolled back:
5176 effects can be rolled back:
5178
5177
5179 - commit
5178 - commit
5180 - import
5179 - import
5181 - pull
5180 - pull
5182 - push (with this repository as the destination)
5181 - push (with this repository as the destination)
5183 - unbundle
5182 - unbundle
5184
5183
5185 To avoid permanent data loss, rollback will refuse to rollback a
5184 To avoid permanent data loss, rollback will refuse to rollback a
5186 commit transaction if it isn't checked out. Use --force to
5185 commit transaction if it isn't checked out. Use --force to
5187 override this protection.
5186 override this protection.
5188
5187
5189 The rollback command can be entirely disabled by setting the
5188 The rollback command can be entirely disabled by setting the
5190 ``ui.rollback`` configuration setting to false. If you're here
5189 ``ui.rollback`` configuration setting to false. If you're here
5191 because you want to use rollback and it's disabled, you can
5190 because you want to use rollback and it's disabled, you can
5192 re-enable the command by setting ``ui.rollback`` to true.
5191 re-enable the command by setting ``ui.rollback`` to true.
5193
5192
5194 This command is not intended for use on public repositories. Once
5193 This command is not intended for use on public repositories. Once
5195 changes are visible for pull by other users, rolling a transaction
5194 changes are visible for pull by other users, rolling a transaction
5196 back locally is ineffective (someone else may already have pulled
5195 back locally is ineffective (someone else may already have pulled
5197 the changes). Furthermore, a race is possible with readers of the
5196 the changes). Furthermore, a race is possible with readers of the
5198 repository; for example an in-progress pull from the repository
5197 repository; for example an in-progress pull from the repository
5199 may fail if a rollback is performed.
5198 may fail if a rollback is performed.
5200
5199
5201 Returns 0 on success, 1 if no rollback data is available.
5200 Returns 0 on success, 1 if no rollback data is available.
5202 """
5201 """
5203 if not ui.configbool('ui', 'rollback'):
5202 if not ui.configbool('ui', 'rollback'):
5204 raise error.Abort(_('rollback is disabled because it is unsafe'),
5203 raise error.Abort(_('rollback is disabled because it is unsafe'),
5205 hint=('see `hg help -v rollback` for information'))
5204 hint=('see `hg help -v rollback` for information'))
5206 return repo.rollback(dryrun=opts.get(r'dry_run'),
5205 return repo.rollback(dryrun=opts.get(r'dry_run'),
5207 force=opts.get(r'force'))
5206 force=opts.get(r'force'))
5208
5207
5209 @command(
5208 @command(
5210 'root', [], intents={INTENT_READONLY},
5209 'root', [], intents={INTENT_READONLY},
5211 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5210 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5212 def root(ui, repo):
5211 def root(ui, repo):
5213 """print the root (top) of the current working directory
5212 """print the root (top) of the current working directory
5214
5213
5215 Print the root directory of the current repository.
5214 Print the root directory of the current repository.
5216
5215
5217 Returns 0 on success.
5216 Returns 0 on success.
5218 """
5217 """
5219 ui.write(repo.root + "\n")
5218 ui.write(repo.root + "\n")
5220
5219
5221 @command('serve',
5220 @command('serve',
5222 [('A', 'accesslog', '', _('name of access log file to write to'),
5221 [('A', 'accesslog', '', _('name of access log file to write to'),
5223 _('FILE')),
5222 _('FILE')),
5224 ('d', 'daemon', None, _('run server in background')),
5223 ('d', 'daemon', None, _('run server in background')),
5225 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5224 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5226 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5225 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5227 # use string type, then we can check if something was passed
5226 # use string type, then we can check if something was passed
5228 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5227 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5229 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5228 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5230 _('ADDR')),
5229 _('ADDR')),
5231 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5230 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5232 _('PREFIX')),
5231 _('PREFIX')),
5233 ('n', 'name', '',
5232 ('n', 'name', '',
5234 _('name to show in web pages (default: working directory)'), _('NAME')),
5233 _('name to show in web pages (default: working directory)'), _('NAME')),
5235 ('', 'web-conf', '',
5234 ('', 'web-conf', '',
5236 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5235 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5237 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5236 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5238 _('FILE')),
5237 _('FILE')),
5239 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5238 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5240 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5239 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5241 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5240 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5242 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5241 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5243 ('', 'style', '', _('template style to use'), _('STYLE')),
5242 ('', 'style', '', _('template style to use'), _('STYLE')),
5244 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5243 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5245 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5244 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5246 ('', 'print-url', None, _('start and print only the URL'))]
5245 ('', 'print-url', None, _('start and print only the URL'))]
5247 + subrepoopts,
5246 + subrepoopts,
5248 _('[OPTION]...'),
5247 _('[OPTION]...'),
5249 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5248 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5250 helpbasic=True, optionalrepo=True)
5249 helpbasic=True, optionalrepo=True)
5251 def serve(ui, repo, **opts):
5250 def serve(ui, repo, **opts):
5252 """start stand-alone webserver
5251 """start stand-alone webserver
5253
5252
5254 Start a local HTTP repository browser and pull server. You can use
5253 Start a local HTTP repository browser and pull server. You can use
5255 this for ad-hoc sharing and browsing of repositories. It is
5254 this for ad-hoc sharing and browsing of repositories. It is
5256 recommended to use a real web server to serve a repository for
5255 recommended to use a real web server to serve a repository for
5257 longer periods of time.
5256 longer periods of time.
5258
5257
5259 Please note that the server does not implement access control.
5258 Please note that the server does not implement access control.
5260 This means that, by default, anybody can read from the server and
5259 This means that, by default, anybody can read from the server and
5261 nobody can write to it by default. Set the ``web.allow-push``
5260 nobody can write to it by default. Set the ``web.allow-push``
5262 option to ``*`` to allow everybody to push to the server. You
5261 option to ``*`` to allow everybody to push to the server. You
5263 should use a real web server if you need to authenticate users.
5262 should use a real web server if you need to authenticate users.
5264
5263
5265 By default, the server logs accesses to stdout and errors to
5264 By default, the server logs accesses to stdout and errors to
5266 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5265 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5267 files.
5266 files.
5268
5267
5269 To have the server choose a free port number to listen on, specify
5268 To have the server choose a free port number to listen on, specify
5270 a port number of 0; in this case, the server will print the port
5269 a port number of 0; in this case, the server will print the port
5271 number it uses.
5270 number it uses.
5272
5271
5273 Returns 0 on success.
5272 Returns 0 on success.
5274 """
5273 """
5275
5274
5276 opts = pycompat.byteskwargs(opts)
5275 opts = pycompat.byteskwargs(opts)
5277 if opts["stdio"] and opts["cmdserver"]:
5276 if opts["stdio"] and opts["cmdserver"]:
5278 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5277 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5279 if opts["print_url"] and ui.verbose:
5278 if opts["print_url"] and ui.verbose:
5280 raise error.Abort(_("cannot use --print-url with --verbose"))
5279 raise error.Abort(_("cannot use --print-url with --verbose"))
5281
5280
5282 if opts["stdio"]:
5281 if opts["stdio"]:
5283 if repo is None:
5282 if repo is None:
5284 raise error.RepoError(_("there is no Mercurial repository here"
5283 raise error.RepoError(_("there is no Mercurial repository here"
5285 " (.hg not found)"))
5284 " (.hg not found)"))
5286 s = wireprotoserver.sshserver(ui, repo)
5285 s = wireprotoserver.sshserver(ui, repo)
5287 s.serve_forever()
5286 s.serve_forever()
5288
5287
5289 service = server.createservice(ui, repo, opts)
5288 service = server.createservice(ui, repo, opts)
5290 return server.runservice(opts, initfn=service.init, runfn=service.run)
5289 return server.runservice(opts, initfn=service.init, runfn=service.run)
5291
5290
5292 _NOTTERSE = 'nothing'
5291 _NOTTERSE = 'nothing'
5293
5292
5294 @command('status|st',
5293 @command('status|st',
5295 [('A', 'all', None, _('show status of all files')),
5294 [('A', 'all', None, _('show status of all files')),
5296 ('m', 'modified', None, _('show only modified files')),
5295 ('m', 'modified', None, _('show only modified files')),
5297 ('a', 'added', None, _('show only added files')),
5296 ('a', 'added', None, _('show only added files')),
5298 ('r', 'removed', None, _('show only removed files')),
5297 ('r', 'removed', None, _('show only removed files')),
5299 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5298 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5300 ('c', 'clean', None, _('show only files without changes')),
5299 ('c', 'clean', None, _('show only files without changes')),
5301 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5300 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5302 ('i', 'ignored', None, _('show only ignored files')),
5301 ('i', 'ignored', None, _('show only ignored files')),
5303 ('n', 'no-status', None, _('hide status prefix')),
5302 ('n', 'no-status', None, _('hide status prefix')),
5304 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5303 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5305 ('C', 'copies', None, _('show source of copied files')),
5304 ('C', 'copies', None, _('show source of copied files')),
5306 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5305 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5307 ('', 'rev', [], _('show difference from revision'), _('REV')),
5306 ('', 'rev', [], _('show difference from revision'), _('REV')),
5308 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5307 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5309 ] + walkopts + subrepoopts + formatteropts,
5308 ] + walkopts + subrepoopts + formatteropts,
5310 _('[OPTION]... [FILE]...'),
5309 _('[OPTION]... [FILE]...'),
5311 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5310 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5312 helpbasic=True, inferrepo=True,
5311 helpbasic=True, inferrepo=True,
5313 intents={INTENT_READONLY})
5312 intents={INTENT_READONLY})
5314 def status(ui, repo, *pats, **opts):
5313 def status(ui, repo, *pats, **opts):
5315 """show changed files in the working directory
5314 """show changed files in the working directory
5316
5315
5317 Show status of files in the repository. If names are given, only
5316 Show status of files in the repository. If names are given, only
5318 files that match are shown. Files that are clean or ignored or
5317 files that match are shown. Files that are clean or ignored or
5319 the source of a copy/move operation, are not listed unless
5318 the source of a copy/move operation, are not listed unless
5320 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5319 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5321 Unless options described with "show only ..." are given, the
5320 Unless options described with "show only ..." are given, the
5322 options -mardu are used.
5321 options -mardu are used.
5323
5322
5324 Option -q/--quiet hides untracked (unknown and ignored) files
5323 Option -q/--quiet hides untracked (unknown and ignored) files
5325 unless explicitly requested with -u/--unknown or -i/--ignored.
5324 unless explicitly requested with -u/--unknown or -i/--ignored.
5326
5325
5327 .. note::
5326 .. note::
5328
5327
5329 :hg:`status` may appear to disagree with diff if permissions have
5328 :hg:`status` may appear to disagree with diff if permissions have
5330 changed or a merge has occurred. The standard diff format does
5329 changed or a merge has occurred. The standard diff format does
5331 not report permission changes and diff only reports changes
5330 not report permission changes and diff only reports changes
5332 relative to one merge parent.
5331 relative to one merge parent.
5333
5332
5334 If one revision is given, it is used as the base revision.
5333 If one revision is given, it is used as the base revision.
5335 If two revisions are given, the differences between them are
5334 If two revisions are given, the differences between them are
5336 shown. The --change option can also be used as a shortcut to list
5335 shown. The --change option can also be used as a shortcut to list
5337 the changed files of a revision from its first parent.
5336 the changed files of a revision from its first parent.
5338
5337
5339 The codes used to show the status of files are::
5338 The codes used to show the status of files are::
5340
5339
5341 M = modified
5340 M = modified
5342 A = added
5341 A = added
5343 R = removed
5342 R = removed
5344 C = clean
5343 C = clean
5345 ! = missing (deleted by non-hg command, but still tracked)
5344 ! = missing (deleted by non-hg command, but still tracked)
5346 ? = not tracked
5345 ? = not tracked
5347 I = ignored
5346 I = ignored
5348 = origin of the previous file (with --copies)
5347 = origin of the previous file (with --copies)
5349
5348
5350 .. container:: verbose
5349 .. container:: verbose
5351
5350
5352 The -t/--terse option abbreviates the output by showing only the directory
5351 The -t/--terse option abbreviates the output by showing only the directory
5353 name if all the files in it share the same status. The option takes an
5352 name if all the files in it share the same status. The option takes an
5354 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5353 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5355 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5354 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5356 for 'ignored' and 'c' for clean.
5355 for 'ignored' and 'c' for clean.
5357
5356
5358 It abbreviates only those statuses which are passed. Note that clean and
5357 It abbreviates only those statuses which are passed. Note that clean and
5359 ignored files are not displayed with '--terse ic' unless the -c/--clean
5358 ignored files are not displayed with '--terse ic' unless the -c/--clean
5360 and -i/--ignored options are also used.
5359 and -i/--ignored options are also used.
5361
5360
5362 The -v/--verbose option shows information when the repository is in an
5361 The -v/--verbose option shows information when the repository is in an
5363 unfinished merge, shelve, rebase state etc. You can have this behavior
5362 unfinished merge, shelve, rebase state etc. You can have this behavior
5364 turned on by default by enabling the ``commands.status.verbose`` option.
5363 turned on by default by enabling the ``commands.status.verbose`` option.
5365
5364
5366 You can skip displaying some of these states by setting
5365 You can skip displaying some of these states by setting
5367 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5366 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5368 'histedit', 'merge', 'rebase', or 'unshelve'.
5367 'histedit', 'merge', 'rebase', or 'unshelve'.
5369
5368
5370 Template:
5369 Template:
5371
5370
5372 The following keywords are supported in addition to the common template
5371 The following keywords are supported in addition to the common template
5373 keywords and functions. See also :hg:`help templates`.
5372 keywords and functions. See also :hg:`help templates`.
5374
5373
5375 :path: String. Repository-absolute path of the file.
5374 :path: String. Repository-absolute path of the file.
5376 :source: String. Repository-absolute path of the file originated from.
5375 :source: String. Repository-absolute path of the file originated from.
5377 Available if ``--copies`` is specified.
5376 Available if ``--copies`` is specified.
5378 :status: String. Character denoting file's status.
5377 :status: String. Character denoting file's status.
5379
5378
5380 Examples:
5379 Examples:
5381
5380
5382 - show changes in the working directory relative to a
5381 - show changes in the working directory relative to a
5383 changeset::
5382 changeset::
5384
5383
5385 hg status --rev 9353
5384 hg status --rev 9353
5386
5385
5387 - show changes in the working directory relative to the
5386 - show changes in the working directory relative to the
5388 current directory (see :hg:`help patterns` for more information)::
5387 current directory (see :hg:`help patterns` for more information)::
5389
5388
5390 hg status re:
5389 hg status re:
5391
5390
5392 - show all changes including copies in an existing changeset::
5391 - show all changes including copies in an existing changeset::
5393
5392
5394 hg status --copies --change 9353
5393 hg status --copies --change 9353
5395
5394
5396 - get a NUL separated list of added files, suitable for xargs::
5395 - get a NUL separated list of added files, suitable for xargs::
5397
5396
5398 hg status -an0
5397 hg status -an0
5399
5398
5400 - show more information about the repository status, abbreviating
5399 - show more information about the repository status, abbreviating
5401 added, removed, modified, deleted, and untracked paths::
5400 added, removed, modified, deleted, and untracked paths::
5402
5401
5403 hg status -v -t mardu
5402 hg status -v -t mardu
5404
5403
5405 Returns 0 on success.
5404 Returns 0 on success.
5406
5405
5407 """
5406 """
5408
5407
5409 opts = pycompat.byteskwargs(opts)
5408 opts = pycompat.byteskwargs(opts)
5410 revs = opts.get('rev')
5409 revs = opts.get('rev')
5411 change = opts.get('change')
5410 change = opts.get('change')
5412 terse = opts.get('terse')
5411 terse = opts.get('terse')
5413 if terse is _NOTTERSE:
5412 if terse is _NOTTERSE:
5414 if revs:
5413 if revs:
5415 terse = ''
5414 terse = ''
5416 else:
5415 else:
5417 terse = ui.config('commands', 'status.terse')
5416 terse = ui.config('commands', 'status.terse')
5418
5417
5419 if revs and change:
5418 if revs and change:
5420 msg = _('cannot specify --rev and --change at the same time')
5419 msg = _('cannot specify --rev and --change at the same time')
5421 raise error.Abort(msg)
5420 raise error.Abort(msg)
5422 elif revs and terse:
5421 elif revs and terse:
5423 msg = _('cannot use --terse with --rev')
5422 msg = _('cannot use --terse with --rev')
5424 raise error.Abort(msg)
5423 raise error.Abort(msg)
5425 elif change:
5424 elif change:
5426 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5425 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5427 ctx2 = scmutil.revsingle(repo, change, None)
5426 ctx2 = scmutil.revsingle(repo, change, None)
5428 ctx1 = ctx2.p1()
5427 ctx1 = ctx2.p1()
5429 else:
5428 else:
5430 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5429 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5431 ctx1, ctx2 = scmutil.revpair(repo, revs)
5430 ctx1, ctx2 = scmutil.revpair(repo, revs)
5432
5431
5433 forcerelativevalue = None
5432 forcerelativevalue = None
5434 if ui.hasconfig('commands', 'status.relative'):
5433 if ui.hasconfig('commands', 'status.relative'):
5435 forcerelativevalue = ui.configbool('commands', 'status.relative')
5434 forcerelativevalue = ui.configbool('commands', 'status.relative')
5436 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5435 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5437 forcerelativevalue=forcerelativevalue)
5436 forcerelativevalue=forcerelativevalue)
5438
5437
5439 if opts.get('print0'):
5438 if opts.get('print0'):
5440 end = '\0'
5439 end = '\0'
5441 else:
5440 else:
5442 end = '\n'
5441 end = '\n'
5443 copy = {}
5442 copy = {}
5444 states = 'modified added removed deleted unknown ignored clean'.split()
5443 states = 'modified added removed deleted unknown ignored clean'.split()
5445 show = [k for k in states if opts.get(k)]
5444 show = [k for k in states if opts.get(k)]
5446 if opts.get('all'):
5445 if opts.get('all'):
5447 show += ui.quiet and (states[:4] + ['clean']) or states
5446 show += ui.quiet and (states[:4] + ['clean']) or states
5448
5447
5449 if not show:
5448 if not show:
5450 if ui.quiet:
5449 if ui.quiet:
5451 show = states[:4]
5450 show = states[:4]
5452 else:
5451 else:
5453 show = states[:5]
5452 show = states[:5]
5454
5453
5455 m = scmutil.match(ctx2, pats, opts)
5454 m = scmutil.match(ctx2, pats, opts)
5456 if terse:
5455 if terse:
5457 # we need to compute clean and unknown to terse
5456 # we need to compute clean and unknown to terse
5458 stat = repo.status(ctx1.node(), ctx2.node(), m,
5457 stat = repo.status(ctx1.node(), ctx2.node(), m,
5459 'ignored' in show or 'i' in terse,
5458 'ignored' in show or 'i' in terse,
5460 clean=True, unknown=True,
5459 clean=True, unknown=True,
5461 listsubrepos=opts.get('subrepos'))
5460 listsubrepos=opts.get('subrepos'))
5462
5461
5463 stat = cmdutil.tersedir(stat, terse)
5462 stat = cmdutil.tersedir(stat, terse)
5464 else:
5463 else:
5465 stat = repo.status(ctx1.node(), ctx2.node(), m,
5464 stat = repo.status(ctx1.node(), ctx2.node(), m,
5466 'ignored' in show, 'clean' in show,
5465 'ignored' in show, 'clean' in show,
5467 'unknown' in show, opts.get('subrepos'))
5466 'unknown' in show, opts.get('subrepos'))
5468
5467
5469 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5468 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5470
5469
5471 if (opts.get('all') or opts.get('copies')
5470 if (opts.get('all') or opts.get('copies')
5472 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5471 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5473 copy = copies.pathcopies(ctx1, ctx2, m)
5472 copy = copies.pathcopies(ctx1, ctx2, m)
5474
5473
5475 ui.pager('status')
5474 ui.pager('status')
5476 fm = ui.formatter('status', opts)
5475 fm = ui.formatter('status', opts)
5477 fmt = '%s' + end
5476 fmt = '%s' + end
5478 showchar = not opts.get('no_status')
5477 showchar = not opts.get('no_status')
5479
5478
5480 for state, char, files in changestates:
5479 for state, char, files in changestates:
5481 if state in show:
5480 if state in show:
5482 label = 'status.' + state
5481 label = 'status.' + state
5483 for f in files:
5482 for f in files:
5484 fm.startitem()
5483 fm.startitem()
5485 fm.context(ctx=ctx2)
5484 fm.context(ctx=ctx2)
5486 fm.data(path=f)
5485 fm.data(path=f)
5487 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5486 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5488 fm.plain(fmt % uipathfn(f), label=label)
5487 fm.plain(fmt % uipathfn(f), label=label)
5489 if f in copy:
5488 if f in copy:
5490 fm.data(source=copy[f])
5489 fm.data(source=copy[f])
5491 fm.plain((' %s' + end) % uipathfn(copy[f]),
5490 fm.plain((' %s' + end) % uipathfn(copy[f]),
5492 label='status.copied')
5491 label='status.copied')
5493
5492
5494 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5493 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5495 and not ui.plain()):
5494 and not ui.plain()):
5496 cmdutil.morestatus(repo, fm)
5495 cmdutil.morestatus(repo, fm)
5497 fm.end()
5496 fm.end()
5498
5497
5499 @command('summary|sum',
5498 @command('summary|sum',
5500 [('', 'remote', None, _('check for push and pull'))],
5499 [('', 'remote', None, _('check for push and pull'))],
5501 '[--remote]',
5500 '[--remote]',
5502 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5501 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5503 helpbasic=True,
5502 helpbasic=True,
5504 intents={INTENT_READONLY})
5503 intents={INTENT_READONLY})
5505 def summary(ui, repo, **opts):
5504 def summary(ui, repo, **opts):
5506 """summarize working directory state
5505 """summarize working directory state
5507
5506
5508 This generates a brief summary of the working directory state,
5507 This generates a brief summary of the working directory state,
5509 including parents, branch, commit status, phase and available updates.
5508 including parents, branch, commit status, phase and available updates.
5510
5509
5511 With the --remote option, this will check the default paths for
5510 With the --remote option, this will check the default paths for
5512 incoming and outgoing changes. This can be time-consuming.
5511 incoming and outgoing changes. This can be time-consuming.
5513
5512
5514 Returns 0 on success.
5513 Returns 0 on success.
5515 """
5514 """
5516
5515
5517 opts = pycompat.byteskwargs(opts)
5516 opts = pycompat.byteskwargs(opts)
5518 ui.pager('summary')
5517 ui.pager('summary')
5519 ctx = repo[None]
5518 ctx = repo[None]
5520 parents = ctx.parents()
5519 parents = ctx.parents()
5521 pnode = parents[0].node()
5520 pnode = parents[0].node()
5522 marks = []
5521 marks = []
5523
5522
5524 try:
5523 try:
5525 ms = mergemod.mergestate.read(repo)
5524 ms = mergemod.mergestate.read(repo)
5526 except error.UnsupportedMergeRecords as e:
5525 except error.UnsupportedMergeRecords as e:
5527 s = ' '.join(e.recordtypes)
5526 s = ' '.join(e.recordtypes)
5528 ui.warn(
5527 ui.warn(
5529 _('warning: merge state has unsupported record types: %s\n') % s)
5528 _('warning: merge state has unsupported record types: %s\n') % s)
5530 unresolved = []
5529 unresolved = []
5531 else:
5530 else:
5532 unresolved = list(ms.unresolved())
5531 unresolved = list(ms.unresolved())
5533
5532
5534 for p in parents:
5533 for p in parents:
5535 # label with log.changeset (instead of log.parent) since this
5534 # label with log.changeset (instead of log.parent) since this
5536 # shows a working directory parent *changeset*:
5535 # shows a working directory parent *changeset*:
5537 # i18n: column positioning for "hg summary"
5536 # i18n: column positioning for "hg summary"
5538 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5537 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5539 label=logcmdutil.changesetlabels(p))
5538 label=logcmdutil.changesetlabels(p))
5540 ui.write(' '.join(p.tags()), label='log.tag')
5539 ui.write(' '.join(p.tags()), label='log.tag')
5541 if p.bookmarks():
5540 if p.bookmarks():
5542 marks.extend(p.bookmarks())
5541 marks.extend(p.bookmarks())
5543 if p.rev() == -1:
5542 if p.rev() == -1:
5544 if not len(repo):
5543 if not len(repo):
5545 ui.write(_(' (empty repository)'))
5544 ui.write(_(' (empty repository)'))
5546 else:
5545 else:
5547 ui.write(_(' (no revision checked out)'))
5546 ui.write(_(' (no revision checked out)'))
5548 if p.obsolete():
5547 if p.obsolete():
5549 ui.write(_(' (obsolete)'))
5548 ui.write(_(' (obsolete)'))
5550 if p.isunstable():
5549 if p.isunstable():
5551 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5550 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5552 for instability in p.instabilities())
5551 for instability in p.instabilities())
5553 ui.write(' ('
5552 ui.write(' ('
5554 + ', '.join(instabilities)
5553 + ', '.join(instabilities)
5555 + ')')
5554 + ')')
5556 ui.write('\n')
5555 ui.write('\n')
5557 if p.description():
5556 if p.description():
5558 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5557 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5559 label='log.summary')
5558 label='log.summary')
5560
5559
5561 branch = ctx.branch()
5560 branch = ctx.branch()
5562 bheads = repo.branchheads(branch)
5561 bheads = repo.branchheads(branch)
5563 # i18n: column positioning for "hg summary"
5562 # i18n: column positioning for "hg summary"
5564 m = _('branch: %s\n') % branch
5563 m = _('branch: %s\n') % branch
5565 if branch != 'default':
5564 if branch != 'default':
5566 ui.write(m, label='log.branch')
5565 ui.write(m, label='log.branch')
5567 else:
5566 else:
5568 ui.status(m, label='log.branch')
5567 ui.status(m, label='log.branch')
5569
5568
5570 if marks:
5569 if marks:
5571 active = repo._activebookmark
5570 active = repo._activebookmark
5572 # i18n: column positioning for "hg summary"
5571 # i18n: column positioning for "hg summary"
5573 ui.write(_('bookmarks:'), label='log.bookmark')
5572 ui.write(_('bookmarks:'), label='log.bookmark')
5574 if active is not None:
5573 if active is not None:
5575 if active in marks:
5574 if active in marks:
5576 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5575 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5577 marks.remove(active)
5576 marks.remove(active)
5578 else:
5577 else:
5579 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5578 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5580 for m in marks:
5579 for m in marks:
5581 ui.write(' ' + m, label='log.bookmark')
5580 ui.write(' ' + m, label='log.bookmark')
5582 ui.write('\n', label='log.bookmark')
5581 ui.write('\n', label='log.bookmark')
5583
5582
5584 status = repo.status(unknown=True)
5583 status = repo.status(unknown=True)
5585
5584
5586 c = repo.dirstate.copies()
5585 c = repo.dirstate.copies()
5587 copied, renamed = [], []
5586 copied, renamed = [], []
5588 for d, s in c.iteritems():
5587 for d, s in c.iteritems():
5589 if s in status.removed:
5588 if s in status.removed:
5590 status.removed.remove(s)
5589 status.removed.remove(s)
5591 renamed.append(d)
5590 renamed.append(d)
5592 else:
5591 else:
5593 copied.append(d)
5592 copied.append(d)
5594 if d in status.added:
5593 if d in status.added:
5595 status.added.remove(d)
5594 status.added.remove(d)
5596
5595
5597 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5596 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5598
5597
5599 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5598 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5600 (ui.label(_('%d added'), 'status.added'), status.added),
5599 (ui.label(_('%d added'), 'status.added'), status.added),
5601 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5600 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5602 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5601 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5603 (ui.label(_('%d copied'), 'status.copied'), copied),
5602 (ui.label(_('%d copied'), 'status.copied'), copied),
5604 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5603 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5605 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5604 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5606 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5605 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5607 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5606 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5608 t = []
5607 t = []
5609 for l, s in labels:
5608 for l, s in labels:
5610 if s:
5609 if s:
5611 t.append(l % len(s))
5610 t.append(l % len(s))
5612
5611
5613 t = ', '.join(t)
5612 t = ', '.join(t)
5614 cleanworkdir = False
5613 cleanworkdir = False
5615
5614
5616 if repo.vfs.exists('graftstate'):
5615 if repo.vfs.exists('graftstate'):
5617 t += _(' (graft in progress)')
5616 t += _(' (graft in progress)')
5618 if repo.vfs.exists('updatestate'):
5617 if repo.vfs.exists('updatestate'):
5619 t += _(' (interrupted update)')
5618 t += _(' (interrupted update)')
5620 elif len(parents) > 1:
5619 elif len(parents) > 1:
5621 t += _(' (merge)')
5620 t += _(' (merge)')
5622 elif branch != parents[0].branch():
5621 elif branch != parents[0].branch():
5623 t += _(' (new branch)')
5622 t += _(' (new branch)')
5624 elif (parents[0].closesbranch() and
5623 elif (parents[0].closesbranch() and
5625 pnode in repo.branchheads(branch, closed=True)):
5624 pnode in repo.branchheads(branch, closed=True)):
5626 t += _(' (head closed)')
5625 t += _(' (head closed)')
5627 elif not (status.modified or status.added or status.removed or renamed or
5626 elif not (status.modified or status.added or status.removed or renamed or
5628 copied or subs):
5627 copied or subs):
5629 t += _(' (clean)')
5628 t += _(' (clean)')
5630 cleanworkdir = True
5629 cleanworkdir = True
5631 elif pnode not in bheads:
5630 elif pnode not in bheads:
5632 t += _(' (new branch head)')
5631 t += _(' (new branch head)')
5633
5632
5634 if parents:
5633 if parents:
5635 pendingphase = max(p.phase() for p in parents)
5634 pendingphase = max(p.phase() for p in parents)
5636 else:
5635 else:
5637 pendingphase = phases.public
5636 pendingphase = phases.public
5638
5637
5639 if pendingphase > phases.newcommitphase(ui):
5638 if pendingphase > phases.newcommitphase(ui):
5640 t += ' (%s)' % phases.phasenames[pendingphase]
5639 t += ' (%s)' % phases.phasenames[pendingphase]
5641
5640
5642 if cleanworkdir:
5641 if cleanworkdir:
5643 # i18n: column positioning for "hg summary"
5642 # i18n: column positioning for "hg summary"
5644 ui.status(_('commit: %s\n') % t.strip())
5643 ui.status(_('commit: %s\n') % t.strip())
5645 else:
5644 else:
5646 # i18n: column positioning for "hg summary"
5645 # i18n: column positioning for "hg summary"
5647 ui.write(_('commit: %s\n') % t.strip())
5646 ui.write(_('commit: %s\n') % t.strip())
5648
5647
5649 # all ancestors of branch heads - all ancestors of parent = new csets
5648 # all ancestors of branch heads - all ancestors of parent = new csets
5650 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5649 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5651 bheads))
5650 bheads))
5652
5651
5653 if new == 0:
5652 if new == 0:
5654 # i18n: column positioning for "hg summary"
5653 # i18n: column positioning for "hg summary"
5655 ui.status(_('update: (current)\n'))
5654 ui.status(_('update: (current)\n'))
5656 elif pnode not in bheads:
5655 elif pnode not in bheads:
5657 # i18n: column positioning for "hg summary"
5656 # i18n: column positioning for "hg summary"
5658 ui.write(_('update: %d new changesets (update)\n') % new)
5657 ui.write(_('update: %d new changesets (update)\n') % new)
5659 else:
5658 else:
5660 # i18n: column positioning for "hg summary"
5659 # i18n: column positioning for "hg summary"
5661 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5660 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5662 (new, len(bheads)))
5661 (new, len(bheads)))
5663
5662
5664 t = []
5663 t = []
5665 draft = len(repo.revs('draft()'))
5664 draft = len(repo.revs('draft()'))
5666 if draft:
5665 if draft:
5667 t.append(_('%d draft') % draft)
5666 t.append(_('%d draft') % draft)
5668 secret = len(repo.revs('secret()'))
5667 secret = len(repo.revs('secret()'))
5669 if secret:
5668 if secret:
5670 t.append(_('%d secret') % secret)
5669 t.append(_('%d secret') % secret)
5671
5670
5672 if draft or secret:
5671 if draft or secret:
5673 ui.status(_('phases: %s\n') % ', '.join(t))
5672 ui.status(_('phases: %s\n') % ', '.join(t))
5674
5673
5675 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5674 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5676 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5675 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5677 numtrouble = len(repo.revs(trouble + "()"))
5676 numtrouble = len(repo.revs(trouble + "()"))
5678 # We write all the possibilities to ease translation
5677 # We write all the possibilities to ease translation
5679 troublemsg = {
5678 troublemsg = {
5680 "orphan": _("orphan: %d changesets"),
5679 "orphan": _("orphan: %d changesets"),
5681 "contentdivergent": _("content-divergent: %d changesets"),
5680 "contentdivergent": _("content-divergent: %d changesets"),
5682 "phasedivergent": _("phase-divergent: %d changesets"),
5681 "phasedivergent": _("phase-divergent: %d changesets"),
5683 }
5682 }
5684 if numtrouble > 0:
5683 if numtrouble > 0:
5685 ui.status(troublemsg[trouble] % numtrouble + "\n")
5684 ui.status(troublemsg[trouble] % numtrouble + "\n")
5686
5685
5687 cmdutil.summaryhooks(ui, repo)
5686 cmdutil.summaryhooks(ui, repo)
5688
5687
5689 if opts.get('remote'):
5688 if opts.get('remote'):
5690 needsincoming, needsoutgoing = True, True
5689 needsincoming, needsoutgoing = True, True
5691 else:
5690 else:
5692 needsincoming, needsoutgoing = False, False
5691 needsincoming, needsoutgoing = False, False
5693 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5692 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5694 if i:
5693 if i:
5695 needsincoming = True
5694 needsincoming = True
5696 if o:
5695 if o:
5697 needsoutgoing = True
5696 needsoutgoing = True
5698 if not needsincoming and not needsoutgoing:
5697 if not needsincoming and not needsoutgoing:
5699 return
5698 return
5700
5699
5701 def getincoming():
5700 def getincoming():
5702 source, branches = hg.parseurl(ui.expandpath('default'))
5701 source, branches = hg.parseurl(ui.expandpath('default'))
5703 sbranch = branches[0]
5702 sbranch = branches[0]
5704 try:
5703 try:
5705 other = hg.peer(repo, {}, source)
5704 other = hg.peer(repo, {}, source)
5706 except error.RepoError:
5705 except error.RepoError:
5707 if opts.get('remote'):
5706 if opts.get('remote'):
5708 raise
5707 raise
5709 return source, sbranch, None, None, None
5708 return source, sbranch, None, None, None
5710 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5709 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5711 if revs:
5710 if revs:
5712 revs = [other.lookup(rev) for rev in revs]
5711 revs = [other.lookup(rev) for rev in revs]
5713 ui.debug('comparing with %s\n' % util.hidepassword(source))
5712 ui.debug('comparing with %s\n' % util.hidepassword(source))
5714 repo.ui.pushbuffer()
5713 repo.ui.pushbuffer()
5715 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5714 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5716 repo.ui.popbuffer()
5715 repo.ui.popbuffer()
5717 return source, sbranch, other, commoninc, commoninc[1]
5716 return source, sbranch, other, commoninc, commoninc[1]
5718
5717
5719 if needsincoming:
5718 if needsincoming:
5720 source, sbranch, sother, commoninc, incoming = getincoming()
5719 source, sbranch, sother, commoninc, incoming = getincoming()
5721 else:
5720 else:
5722 source = sbranch = sother = commoninc = incoming = None
5721 source = sbranch = sother = commoninc = incoming = None
5723
5722
5724 def getoutgoing():
5723 def getoutgoing():
5725 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5724 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5726 dbranch = branches[0]
5725 dbranch = branches[0]
5727 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5726 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5728 if source != dest:
5727 if source != dest:
5729 try:
5728 try:
5730 dother = hg.peer(repo, {}, dest)
5729 dother = hg.peer(repo, {}, dest)
5731 except error.RepoError:
5730 except error.RepoError:
5732 if opts.get('remote'):
5731 if opts.get('remote'):
5733 raise
5732 raise
5734 return dest, dbranch, None, None
5733 return dest, dbranch, None, None
5735 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5734 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5736 elif sother is None:
5735 elif sother is None:
5737 # there is no explicit destination peer, but source one is invalid
5736 # there is no explicit destination peer, but source one is invalid
5738 return dest, dbranch, None, None
5737 return dest, dbranch, None, None
5739 else:
5738 else:
5740 dother = sother
5739 dother = sother
5741 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5740 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5742 common = None
5741 common = None
5743 else:
5742 else:
5744 common = commoninc
5743 common = commoninc
5745 if revs:
5744 if revs:
5746 revs = [repo.lookup(rev) for rev in revs]
5745 revs = [repo.lookup(rev) for rev in revs]
5747 repo.ui.pushbuffer()
5746 repo.ui.pushbuffer()
5748 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5747 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5749 commoninc=common)
5748 commoninc=common)
5750 repo.ui.popbuffer()
5749 repo.ui.popbuffer()
5751 return dest, dbranch, dother, outgoing
5750 return dest, dbranch, dother, outgoing
5752
5751
5753 if needsoutgoing:
5752 if needsoutgoing:
5754 dest, dbranch, dother, outgoing = getoutgoing()
5753 dest, dbranch, dother, outgoing = getoutgoing()
5755 else:
5754 else:
5756 dest = dbranch = dother = outgoing = None
5755 dest = dbranch = dother = outgoing = None
5757
5756
5758 if opts.get('remote'):
5757 if opts.get('remote'):
5759 t = []
5758 t = []
5760 if incoming:
5759 if incoming:
5761 t.append(_('1 or more incoming'))
5760 t.append(_('1 or more incoming'))
5762 o = outgoing.missing
5761 o = outgoing.missing
5763 if o:
5762 if o:
5764 t.append(_('%d outgoing') % len(o))
5763 t.append(_('%d outgoing') % len(o))
5765 other = dother or sother
5764 other = dother or sother
5766 if 'bookmarks' in other.listkeys('namespaces'):
5765 if 'bookmarks' in other.listkeys('namespaces'):
5767 counts = bookmarks.summary(repo, other)
5766 counts = bookmarks.summary(repo, other)
5768 if counts[0] > 0:
5767 if counts[0] > 0:
5769 t.append(_('%d incoming bookmarks') % counts[0])
5768 t.append(_('%d incoming bookmarks') % counts[0])
5770 if counts[1] > 0:
5769 if counts[1] > 0:
5771 t.append(_('%d outgoing bookmarks') % counts[1])
5770 t.append(_('%d outgoing bookmarks') % counts[1])
5772
5771
5773 if t:
5772 if t:
5774 # i18n: column positioning for "hg summary"
5773 # i18n: column positioning for "hg summary"
5775 ui.write(_('remote: %s\n') % (', '.join(t)))
5774 ui.write(_('remote: %s\n') % (', '.join(t)))
5776 else:
5775 else:
5777 # i18n: column positioning for "hg summary"
5776 # i18n: column positioning for "hg summary"
5778 ui.status(_('remote: (synced)\n'))
5777 ui.status(_('remote: (synced)\n'))
5779
5778
5780 cmdutil.summaryremotehooks(ui, repo, opts,
5779 cmdutil.summaryremotehooks(ui, repo, opts,
5781 ((source, sbranch, sother, commoninc),
5780 ((source, sbranch, sother, commoninc),
5782 (dest, dbranch, dother, outgoing)))
5781 (dest, dbranch, dother, outgoing)))
5783
5782
5784 @command('tag',
5783 @command('tag',
5785 [('f', 'force', None, _('force tag')),
5784 [('f', 'force', None, _('force tag')),
5786 ('l', 'local', None, _('make the tag local')),
5785 ('l', 'local', None, _('make the tag local')),
5787 ('r', 'rev', '', _('revision to tag'), _('REV')),
5786 ('r', 'rev', '', _('revision to tag'), _('REV')),
5788 ('', 'remove', None, _('remove a tag')),
5787 ('', 'remove', None, _('remove a tag')),
5789 # -l/--local is already there, commitopts cannot be used
5788 # -l/--local is already there, commitopts cannot be used
5790 ('e', 'edit', None, _('invoke editor on commit messages')),
5789 ('e', 'edit', None, _('invoke editor on commit messages')),
5791 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5790 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5792 ] + commitopts2,
5791 ] + commitopts2,
5793 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5792 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5794 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5793 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5795 def tag(ui, repo, name1, *names, **opts):
5794 def tag(ui, repo, name1, *names, **opts):
5796 """add one or more tags for the current or given revision
5795 """add one or more tags for the current or given revision
5797
5796
5798 Name a particular revision using <name>.
5797 Name a particular revision using <name>.
5799
5798
5800 Tags are used to name particular revisions of the repository and are
5799 Tags are used to name particular revisions of the repository and are
5801 very useful to compare different revisions, to go back to significant
5800 very useful to compare different revisions, to go back to significant
5802 earlier versions or to mark branch points as releases, etc. Changing
5801 earlier versions or to mark branch points as releases, etc. Changing
5803 an existing tag is normally disallowed; use -f/--force to override.
5802 an existing tag is normally disallowed; use -f/--force to override.
5804
5803
5805 If no revision is given, the parent of the working directory is
5804 If no revision is given, the parent of the working directory is
5806 used.
5805 used.
5807
5806
5808 To facilitate version control, distribution, and merging of tags,
5807 To facilitate version control, distribution, and merging of tags,
5809 they are stored as a file named ".hgtags" which is managed similarly
5808 they are stored as a file named ".hgtags" which is managed similarly
5810 to other project files and can be hand-edited if necessary. This
5809 to other project files and can be hand-edited if necessary. This
5811 also means that tagging creates a new commit. The file
5810 also means that tagging creates a new commit. The file
5812 ".hg/localtags" is used for local tags (not shared among
5811 ".hg/localtags" is used for local tags (not shared among
5813 repositories).
5812 repositories).
5814
5813
5815 Tag commits are usually made at the head of a branch. If the parent
5814 Tag commits are usually made at the head of a branch. If the parent
5816 of the working directory is not a branch head, :hg:`tag` aborts; use
5815 of the working directory is not a branch head, :hg:`tag` aborts; use
5817 -f/--force to force the tag commit to be based on a non-head
5816 -f/--force to force the tag commit to be based on a non-head
5818 changeset.
5817 changeset.
5819
5818
5820 See :hg:`help dates` for a list of formats valid for -d/--date.
5819 See :hg:`help dates` for a list of formats valid for -d/--date.
5821
5820
5822 Since tag names have priority over branch names during revision
5821 Since tag names have priority over branch names during revision
5823 lookup, using an existing branch name as a tag name is discouraged.
5822 lookup, using an existing branch name as a tag name is discouraged.
5824
5823
5825 Returns 0 on success.
5824 Returns 0 on success.
5826 """
5825 """
5827 opts = pycompat.byteskwargs(opts)
5826 opts = pycompat.byteskwargs(opts)
5828 with repo.wlock(), repo.lock():
5827 with repo.wlock(), repo.lock():
5829 rev_ = "."
5828 rev_ = "."
5830 names = [t.strip() for t in (name1,) + names]
5829 names = [t.strip() for t in (name1,) + names]
5831 if len(names) != len(set(names)):
5830 if len(names) != len(set(names)):
5832 raise error.Abort(_('tag names must be unique'))
5831 raise error.Abort(_('tag names must be unique'))
5833 for n in names:
5832 for n in names:
5834 scmutil.checknewlabel(repo, n, 'tag')
5833 scmutil.checknewlabel(repo, n, 'tag')
5835 if not n:
5834 if not n:
5836 raise error.Abort(_('tag names cannot consist entirely of '
5835 raise error.Abort(_('tag names cannot consist entirely of '
5837 'whitespace'))
5836 'whitespace'))
5838 if opts.get('rev') and opts.get('remove'):
5837 if opts.get('rev') and opts.get('remove'):
5839 raise error.Abort(_("--rev and --remove are incompatible"))
5838 raise error.Abort(_("--rev and --remove are incompatible"))
5840 if opts.get('rev'):
5839 if opts.get('rev'):
5841 rev_ = opts['rev']
5840 rev_ = opts['rev']
5842 message = opts.get('message')
5841 message = opts.get('message')
5843 if opts.get('remove'):
5842 if opts.get('remove'):
5844 if opts.get('local'):
5843 if opts.get('local'):
5845 expectedtype = 'local'
5844 expectedtype = 'local'
5846 else:
5845 else:
5847 expectedtype = 'global'
5846 expectedtype = 'global'
5848
5847
5849 for n in names:
5848 for n in names:
5850 if repo.tagtype(n) == 'global':
5849 if repo.tagtype(n) == 'global':
5851 alltags = tagsmod.findglobaltags(ui, repo)
5850 alltags = tagsmod.findglobaltags(ui, repo)
5852 if alltags[n][0] == nullid:
5851 if alltags[n][0] == nullid:
5853 raise error.Abort(_("tag '%s' is already removed") % n)
5852 raise error.Abort(_("tag '%s' is already removed") % n)
5854 if not repo.tagtype(n):
5853 if not repo.tagtype(n):
5855 raise error.Abort(_("tag '%s' does not exist") % n)
5854 raise error.Abort(_("tag '%s' does not exist") % n)
5856 if repo.tagtype(n) != expectedtype:
5855 if repo.tagtype(n) != expectedtype:
5857 if expectedtype == 'global':
5856 if expectedtype == 'global':
5858 raise error.Abort(_("tag '%s' is not a global tag") % n)
5857 raise error.Abort(_("tag '%s' is not a global tag") % n)
5859 else:
5858 else:
5860 raise error.Abort(_("tag '%s' is not a local tag") % n)
5859 raise error.Abort(_("tag '%s' is not a local tag") % n)
5861 rev_ = 'null'
5860 rev_ = 'null'
5862 if not message:
5861 if not message:
5863 # we don't translate commit messages
5862 # we don't translate commit messages
5864 message = 'Removed tag %s' % ', '.join(names)
5863 message = 'Removed tag %s' % ', '.join(names)
5865 elif not opts.get('force'):
5864 elif not opts.get('force'):
5866 for n in names:
5865 for n in names:
5867 if n in repo.tags():
5866 if n in repo.tags():
5868 raise error.Abort(_("tag '%s' already exists "
5867 raise error.Abort(_("tag '%s' already exists "
5869 "(use -f to force)") % n)
5868 "(use -f to force)") % n)
5870 if not opts.get('local'):
5869 if not opts.get('local'):
5871 p1, p2 = repo.dirstate.parents()
5870 p1, p2 = repo.dirstate.parents()
5872 if p2 != nullid:
5871 if p2 != nullid:
5873 raise error.Abort(_('uncommitted merge'))
5872 raise error.Abort(_('uncommitted merge'))
5874 bheads = repo.branchheads()
5873 bheads = repo.branchheads()
5875 if not opts.get('force') and bheads and p1 not in bheads:
5874 if not opts.get('force') and bheads and p1 not in bheads:
5876 raise error.Abort(_('working directory is not at a branch head '
5875 raise error.Abort(_('working directory is not at a branch head '
5877 '(use -f to force)'))
5876 '(use -f to force)'))
5878 node = scmutil.revsingle(repo, rev_).node()
5877 node = scmutil.revsingle(repo, rev_).node()
5879
5878
5880 if not message:
5879 if not message:
5881 # we don't translate commit messages
5880 # we don't translate commit messages
5882 message = ('Added tag %s for changeset %s' %
5881 message = ('Added tag %s for changeset %s' %
5883 (', '.join(names), short(node)))
5882 (', '.join(names), short(node)))
5884
5883
5885 date = opts.get('date')
5884 date = opts.get('date')
5886 if date:
5885 if date:
5887 date = dateutil.parsedate(date)
5886 date = dateutil.parsedate(date)
5888
5887
5889 if opts.get('remove'):
5888 if opts.get('remove'):
5890 editform = 'tag.remove'
5889 editform = 'tag.remove'
5891 else:
5890 else:
5892 editform = 'tag.add'
5891 editform = 'tag.add'
5893 editor = cmdutil.getcommiteditor(editform=editform,
5892 editor = cmdutil.getcommiteditor(editform=editform,
5894 **pycompat.strkwargs(opts))
5893 **pycompat.strkwargs(opts))
5895
5894
5896 # don't allow tagging the null rev
5895 # don't allow tagging the null rev
5897 if (not opts.get('remove') and
5896 if (not opts.get('remove') and
5898 scmutil.revsingle(repo, rev_).rev() == nullrev):
5897 scmutil.revsingle(repo, rev_).rev() == nullrev):
5899 raise error.Abort(_("cannot tag null revision"))
5898 raise error.Abort(_("cannot tag null revision"))
5900
5899
5901 tagsmod.tag(repo, names, node, message, opts.get('local'),
5900 tagsmod.tag(repo, names, node, message, opts.get('local'),
5902 opts.get('user'), date, editor=editor)
5901 opts.get('user'), date, editor=editor)
5903
5902
5904 @command(
5903 @command(
5905 'tags', formatteropts, '',
5904 'tags', formatteropts, '',
5906 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5905 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5907 intents={INTENT_READONLY})
5906 intents={INTENT_READONLY})
5908 def tags(ui, repo, **opts):
5907 def tags(ui, repo, **opts):
5909 """list repository tags
5908 """list repository tags
5910
5909
5911 This lists both regular and local tags. When the -v/--verbose
5910 This lists both regular and local tags. When the -v/--verbose
5912 switch is used, a third column "local" is printed for local tags.
5911 switch is used, a third column "local" is printed for local tags.
5913 When the -q/--quiet switch is used, only the tag name is printed.
5912 When the -q/--quiet switch is used, only the tag name is printed.
5914
5913
5915 .. container:: verbose
5914 .. container:: verbose
5916
5915
5917 Template:
5916 Template:
5918
5917
5919 The following keywords are supported in addition to the common template
5918 The following keywords are supported in addition to the common template
5920 keywords and functions such as ``{tag}``. See also
5919 keywords and functions such as ``{tag}``. See also
5921 :hg:`help templates`.
5920 :hg:`help templates`.
5922
5921
5923 :type: String. ``local`` for local tags.
5922 :type: String. ``local`` for local tags.
5924
5923
5925 Returns 0 on success.
5924 Returns 0 on success.
5926 """
5925 """
5927
5926
5928 opts = pycompat.byteskwargs(opts)
5927 opts = pycompat.byteskwargs(opts)
5929 ui.pager('tags')
5928 ui.pager('tags')
5930 fm = ui.formatter('tags', opts)
5929 fm = ui.formatter('tags', opts)
5931 hexfunc = fm.hexfunc
5930 hexfunc = fm.hexfunc
5932
5931
5933 for t, n in reversed(repo.tagslist()):
5932 for t, n in reversed(repo.tagslist()):
5934 hn = hexfunc(n)
5933 hn = hexfunc(n)
5935 label = 'tags.normal'
5934 label = 'tags.normal'
5936 tagtype = ''
5935 tagtype = ''
5937 if repo.tagtype(t) == 'local':
5936 if repo.tagtype(t) == 'local':
5938 label = 'tags.local'
5937 label = 'tags.local'
5939 tagtype = 'local'
5938 tagtype = 'local'
5940
5939
5941 fm.startitem()
5940 fm.startitem()
5942 fm.context(repo=repo)
5941 fm.context(repo=repo)
5943 fm.write('tag', '%s', t, label=label)
5942 fm.write('tag', '%s', t, label=label)
5944 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5943 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5945 fm.condwrite(not ui.quiet, 'rev node', fmt,
5944 fm.condwrite(not ui.quiet, 'rev node', fmt,
5946 repo.changelog.rev(n), hn, label=label)
5945 repo.changelog.rev(n), hn, label=label)
5947 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5946 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5948 tagtype, label=label)
5947 tagtype, label=label)
5949 fm.plain('\n')
5948 fm.plain('\n')
5950 fm.end()
5949 fm.end()
5951
5950
5952 @command('tip',
5951 @command('tip',
5953 [('p', 'patch', None, _('show patch')),
5952 [('p', 'patch', None, _('show patch')),
5954 ('g', 'git', None, _('use git extended diff format')),
5953 ('g', 'git', None, _('use git extended diff format')),
5955 ] + templateopts,
5954 ] + templateopts,
5956 _('[-p] [-g]'),
5955 _('[-p] [-g]'),
5957 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5956 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5958 def tip(ui, repo, **opts):
5957 def tip(ui, repo, **opts):
5959 """show the tip revision (DEPRECATED)
5958 """show the tip revision (DEPRECATED)
5960
5959
5961 The tip revision (usually just called the tip) is the changeset
5960 The tip revision (usually just called the tip) is the changeset
5962 most recently added to the repository (and therefore the most
5961 most recently added to the repository (and therefore the most
5963 recently changed head).
5962 recently changed head).
5964
5963
5965 If you have just made a commit, that commit will be the tip. If
5964 If you have just made a commit, that commit will be the tip. If
5966 you have just pulled changes from another repository, the tip of
5965 you have just pulled changes from another repository, the tip of
5967 that repository becomes the current tip. The "tip" tag is special
5966 that repository becomes the current tip. The "tip" tag is special
5968 and cannot be renamed or assigned to a different changeset.
5967 and cannot be renamed or assigned to a different changeset.
5969
5968
5970 This command is deprecated, please use :hg:`heads` instead.
5969 This command is deprecated, please use :hg:`heads` instead.
5971
5970
5972 Returns 0 on success.
5971 Returns 0 on success.
5973 """
5972 """
5974 opts = pycompat.byteskwargs(opts)
5973 opts = pycompat.byteskwargs(opts)
5975 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5974 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5976 displayer.show(repo['tip'])
5975 displayer.show(repo['tip'])
5977 displayer.close()
5976 displayer.close()
5978
5977
5979 @command('unbundle',
5978 @command('unbundle',
5980 [('u', 'update', None,
5979 [('u', 'update', None,
5981 _('update to new branch head if changesets were unbundled'))],
5980 _('update to new branch head if changesets were unbundled'))],
5982 _('[-u] FILE...'),
5981 _('[-u] FILE...'),
5983 helpcategory=command.CATEGORY_IMPORT_EXPORT)
5982 helpcategory=command.CATEGORY_IMPORT_EXPORT)
5984 def unbundle(ui, repo, fname1, *fnames, **opts):
5983 def unbundle(ui, repo, fname1, *fnames, **opts):
5985 """apply one or more bundle files
5984 """apply one or more bundle files
5986
5985
5987 Apply one or more bundle files generated by :hg:`bundle`.
5986 Apply one or more bundle files generated by :hg:`bundle`.
5988
5987
5989 Returns 0 on success, 1 if an update has unresolved files.
5988 Returns 0 on success, 1 if an update has unresolved files.
5990 """
5989 """
5991 fnames = (fname1,) + fnames
5990 fnames = (fname1,) + fnames
5992
5991
5993 with repo.lock():
5992 with repo.lock():
5994 for fname in fnames:
5993 for fname in fnames:
5995 f = hg.openpath(ui, fname)
5994 f = hg.openpath(ui, fname)
5996 gen = exchange.readbundle(ui, f, fname)
5995 gen = exchange.readbundle(ui, f, fname)
5997 if isinstance(gen, streamclone.streamcloneapplier):
5996 if isinstance(gen, streamclone.streamcloneapplier):
5998 raise error.Abort(
5997 raise error.Abort(
5999 _('packed bundles cannot be applied with '
5998 _('packed bundles cannot be applied with '
6000 '"hg unbundle"'),
5999 '"hg unbundle"'),
6001 hint=_('use "hg debugapplystreamclonebundle"'))
6000 hint=_('use "hg debugapplystreamclonebundle"'))
6002 url = 'bundle:' + fname
6001 url = 'bundle:' + fname
6003 try:
6002 try:
6004 txnname = 'unbundle'
6003 txnname = 'unbundle'
6005 if not isinstance(gen, bundle2.unbundle20):
6004 if not isinstance(gen, bundle2.unbundle20):
6006 txnname = 'unbundle\n%s' % util.hidepassword(url)
6005 txnname = 'unbundle\n%s' % util.hidepassword(url)
6007 with repo.transaction(txnname) as tr:
6006 with repo.transaction(txnname) as tr:
6008 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6007 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6009 url=url)
6008 url=url)
6010 except error.BundleUnknownFeatureError as exc:
6009 except error.BundleUnknownFeatureError as exc:
6011 raise error.Abort(
6010 raise error.Abort(
6012 _('%s: unknown bundle feature, %s') % (fname, exc),
6011 _('%s: unknown bundle feature, %s') % (fname, exc),
6013 hint=_("see https://mercurial-scm.org/"
6012 hint=_("see https://mercurial-scm.org/"
6014 "wiki/BundleFeature for more "
6013 "wiki/BundleFeature for more "
6015 "information"))
6014 "information"))
6016 modheads = bundle2.combinechangegroupresults(op)
6015 modheads = bundle2.combinechangegroupresults(op)
6017
6016
6018 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6017 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6019
6018
6020 @command('update|up|checkout|co',
6019 @command('update|up|checkout|co',
6021 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6020 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6022 ('c', 'check', None, _('require clean working directory')),
6021 ('c', 'check', None, _('require clean working directory')),
6023 ('m', 'merge', None, _('merge uncommitted changes')),
6022 ('m', 'merge', None, _('merge uncommitted changes')),
6024 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6023 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6025 ('r', 'rev', '', _('revision'), _('REV'))
6024 ('r', 'rev', '', _('revision'), _('REV'))
6026 ] + mergetoolopts,
6025 ] + mergetoolopts,
6027 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6026 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6028 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6027 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6029 helpbasic=True)
6028 helpbasic=True)
6030 def update(ui, repo, node=None, **opts):
6029 def update(ui, repo, node=None, **opts):
6031 """update working directory (or switch revisions)
6030 """update working directory (or switch revisions)
6032
6031
6033 Update the repository's working directory to the specified
6032 Update the repository's working directory to the specified
6034 changeset. If no changeset is specified, update to the tip of the
6033 changeset. If no changeset is specified, update to the tip of the
6035 current named branch and move the active bookmark (see :hg:`help
6034 current named branch and move the active bookmark (see :hg:`help
6036 bookmarks`).
6035 bookmarks`).
6037
6036
6038 Update sets the working directory's parent revision to the specified
6037 Update sets the working directory's parent revision to the specified
6039 changeset (see :hg:`help parents`).
6038 changeset (see :hg:`help parents`).
6040
6039
6041 If the changeset is not a descendant or ancestor of the working
6040 If the changeset is not a descendant or ancestor of the working
6042 directory's parent and there are uncommitted changes, the update is
6041 directory's parent and there are uncommitted changes, the update is
6043 aborted. With the -c/--check option, the working directory is checked
6042 aborted. With the -c/--check option, the working directory is checked
6044 for uncommitted changes; if none are found, the working directory is
6043 for uncommitted changes; if none are found, the working directory is
6045 updated to the specified changeset.
6044 updated to the specified changeset.
6046
6045
6047 .. container:: verbose
6046 .. container:: verbose
6048
6047
6049 The -C/--clean, -c/--check, and -m/--merge options control what
6048 The -C/--clean, -c/--check, and -m/--merge options control what
6050 happens if the working directory contains uncommitted changes.
6049 happens if the working directory contains uncommitted changes.
6051 At most of one of them can be specified.
6050 At most of one of them can be specified.
6052
6051
6053 1. If no option is specified, and if
6052 1. If no option is specified, and if
6054 the requested changeset is an ancestor or descendant of
6053 the requested changeset is an ancestor or descendant of
6055 the working directory's parent, the uncommitted changes
6054 the working directory's parent, the uncommitted changes
6056 are merged into the requested changeset and the merged
6055 are merged into the requested changeset and the merged
6057 result is left uncommitted. If the requested changeset is
6056 result is left uncommitted. If the requested changeset is
6058 not an ancestor or descendant (that is, it is on another
6057 not an ancestor or descendant (that is, it is on another
6059 branch), the update is aborted and the uncommitted changes
6058 branch), the update is aborted and the uncommitted changes
6060 are preserved.
6059 are preserved.
6061
6060
6062 2. With the -m/--merge option, the update is allowed even if the
6061 2. With the -m/--merge option, the update is allowed even if the
6063 requested changeset is not an ancestor or descendant of
6062 requested changeset is not an ancestor or descendant of
6064 the working directory's parent.
6063 the working directory's parent.
6065
6064
6066 3. With the -c/--check option, the update is aborted and the
6065 3. With the -c/--check option, the update is aborted and the
6067 uncommitted changes are preserved.
6066 uncommitted changes are preserved.
6068
6067
6069 4. With the -C/--clean option, uncommitted changes are discarded and
6068 4. With the -C/--clean option, uncommitted changes are discarded and
6070 the working directory is updated to the requested changeset.
6069 the working directory is updated to the requested changeset.
6071
6070
6072 To cancel an uncommitted merge (and lose your changes), use
6071 To cancel an uncommitted merge (and lose your changes), use
6073 :hg:`merge --abort`.
6072 :hg:`merge --abort`.
6074
6073
6075 Use null as the changeset to remove the working directory (like
6074 Use null as the changeset to remove the working directory (like
6076 :hg:`clone -U`).
6075 :hg:`clone -U`).
6077
6076
6078 If you want to revert just one file to an older revision, use
6077 If you want to revert just one file to an older revision, use
6079 :hg:`revert [-r REV] NAME`.
6078 :hg:`revert [-r REV] NAME`.
6080
6079
6081 See :hg:`help dates` for a list of formats valid for -d/--date.
6080 See :hg:`help dates` for a list of formats valid for -d/--date.
6082
6081
6083 Returns 0 on success, 1 if there are unresolved files.
6082 Returns 0 on success, 1 if there are unresolved files.
6084 """
6083 """
6085 rev = opts.get(r'rev')
6084 rev = opts.get(r'rev')
6086 date = opts.get(r'date')
6085 date = opts.get(r'date')
6087 clean = opts.get(r'clean')
6086 clean = opts.get(r'clean')
6088 check = opts.get(r'check')
6087 check = opts.get(r'check')
6089 merge = opts.get(r'merge')
6088 merge = opts.get(r'merge')
6090 if rev and node:
6089 if rev and node:
6091 raise error.Abort(_("please specify just one revision"))
6090 raise error.Abort(_("please specify just one revision"))
6092
6091
6093 if ui.configbool('commands', 'update.requiredest'):
6092 if ui.configbool('commands', 'update.requiredest'):
6094 if not node and not rev and not date:
6093 if not node and not rev and not date:
6095 raise error.Abort(_('you must specify a destination'),
6094 raise error.Abort(_('you must specify a destination'),
6096 hint=_('for example: hg update ".::"'))
6095 hint=_('for example: hg update ".::"'))
6097
6096
6098 if rev is None or rev == '':
6097 if rev is None or rev == '':
6099 rev = node
6098 rev = node
6100
6099
6101 if date and rev is not None:
6100 if date and rev is not None:
6102 raise error.Abort(_("you can't specify a revision and a date"))
6101 raise error.Abort(_("you can't specify a revision and a date"))
6103
6102
6104 if len([x for x in (clean, check, merge) if x]) > 1:
6103 if len([x for x in (clean, check, merge) if x]) > 1:
6105 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6104 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6106 "or -m/--merge"))
6105 "or -m/--merge"))
6107
6106
6108 updatecheck = None
6107 updatecheck = None
6109 if check:
6108 if check:
6110 updatecheck = 'abort'
6109 updatecheck = 'abort'
6111 elif merge:
6110 elif merge:
6112 updatecheck = 'none'
6111 updatecheck = 'none'
6113
6112
6114 with repo.wlock():
6113 with repo.wlock():
6115 cmdutil.clearunfinished(repo)
6114 cmdutil.clearunfinished(repo)
6116
6115
6117 if date:
6116 if date:
6118 rev = cmdutil.finddate(ui, repo, date)
6117 rev = cmdutil.finddate(ui, repo, date)
6119
6118
6120 # if we defined a bookmark, we have to remember the original name
6119 # if we defined a bookmark, we have to remember the original name
6121 brev = rev
6120 brev = rev
6122 if rev:
6121 if rev:
6123 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6122 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6124 ctx = scmutil.revsingle(repo, rev, default=None)
6123 ctx = scmutil.revsingle(repo, rev, default=None)
6125 rev = ctx.rev()
6124 rev = ctx.rev()
6126 hidden = ctx.hidden()
6125 hidden = ctx.hidden()
6127 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6126 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6128 with ui.configoverride(overrides, 'update'):
6127 with ui.configoverride(overrides, 'update'):
6129 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6128 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6130 updatecheck=updatecheck)
6129 updatecheck=updatecheck)
6131 if hidden:
6130 if hidden:
6132 ctxstr = ctx.hex()[:12]
6131 ctxstr = ctx.hex()[:12]
6133 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6132 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6134
6133
6135 if ctx.obsolete():
6134 if ctx.obsolete():
6136 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6135 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6137 ui.warn("(%s)\n" % obsfatemsg)
6136 ui.warn("(%s)\n" % obsfatemsg)
6138 return ret
6137 return ret
6139
6138
6140 @command('verify', [], helpcategory=command.CATEGORY_MAINTENANCE)
6139 @command('verify', [], helpcategory=command.CATEGORY_MAINTENANCE)
6141 def verify(ui, repo):
6140 def verify(ui, repo):
6142 """verify the integrity of the repository
6141 """verify the integrity of the repository
6143
6142
6144 Verify the integrity of the current repository.
6143 Verify the integrity of the current repository.
6145
6144
6146 This will perform an extensive check of the repository's
6145 This will perform an extensive check of the repository's
6147 integrity, validating the hashes and checksums of each entry in
6146 integrity, validating the hashes and checksums of each entry in
6148 the changelog, manifest, and tracked files, as well as the
6147 the changelog, manifest, and tracked files, as well as the
6149 integrity of their crosslinks and indices.
6148 integrity of their crosslinks and indices.
6150
6149
6151 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6150 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6152 for more information about recovery from corruption of the
6151 for more information about recovery from corruption of the
6153 repository.
6152 repository.
6154
6153
6155 Returns 0 on success, 1 if errors are encountered.
6154 Returns 0 on success, 1 if errors are encountered.
6156 """
6155 """
6157 return hg.verify(repo)
6156 return hg.verify(repo)
6158
6157
6159 @command(
6158 @command(
6160 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6159 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6161 norepo=True, intents={INTENT_READONLY})
6160 norepo=True, intents={INTENT_READONLY})
6162 def version_(ui, **opts):
6161 def version_(ui, **opts):
6163 """output version and copyright information
6162 """output version and copyright information
6164
6163
6165 .. container:: verbose
6164 .. container:: verbose
6166
6165
6167 Template:
6166 Template:
6168
6167
6169 The following keywords are supported. See also :hg:`help templates`.
6168 The following keywords are supported. See also :hg:`help templates`.
6170
6169
6171 :extensions: List of extensions.
6170 :extensions: List of extensions.
6172 :ver: String. Version number.
6171 :ver: String. Version number.
6173
6172
6174 And each entry of ``{extensions}`` provides the following sub-keywords
6173 And each entry of ``{extensions}`` provides the following sub-keywords
6175 in addition to ``{ver}``.
6174 in addition to ``{ver}``.
6176
6175
6177 :bundled: Boolean. True if included in the release.
6176 :bundled: Boolean. True if included in the release.
6178 :name: String. Extension name.
6177 :name: String. Extension name.
6179 """
6178 """
6180 opts = pycompat.byteskwargs(opts)
6179 opts = pycompat.byteskwargs(opts)
6181 if ui.verbose:
6180 if ui.verbose:
6182 ui.pager('version')
6181 ui.pager('version')
6183 fm = ui.formatter("version", opts)
6182 fm = ui.formatter("version", opts)
6184 fm.startitem()
6183 fm.startitem()
6185 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6184 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6186 util.version())
6185 util.version())
6187 license = _(
6186 license = _(
6188 "(see https://mercurial-scm.org for more information)\n"
6187 "(see https://mercurial-scm.org for more information)\n"
6189 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6188 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6190 "This is free software; see the source for copying conditions. "
6189 "This is free software; see the source for copying conditions. "
6191 "There is NO\nwarranty; "
6190 "There is NO\nwarranty; "
6192 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6191 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6193 )
6192 )
6194 if not ui.quiet:
6193 if not ui.quiet:
6195 fm.plain(license)
6194 fm.plain(license)
6196
6195
6197 if ui.verbose:
6196 if ui.verbose:
6198 fm.plain(_("\nEnabled extensions:\n\n"))
6197 fm.plain(_("\nEnabled extensions:\n\n"))
6199 # format names and versions into columns
6198 # format names and versions into columns
6200 names = []
6199 names = []
6201 vers = []
6200 vers = []
6202 isinternals = []
6201 isinternals = []
6203 for name, module in extensions.extensions():
6202 for name, module in extensions.extensions():
6204 names.append(name)
6203 names.append(name)
6205 vers.append(extensions.moduleversion(module) or None)
6204 vers.append(extensions.moduleversion(module) or None)
6206 isinternals.append(extensions.ismoduleinternal(module))
6205 isinternals.append(extensions.ismoduleinternal(module))
6207 fn = fm.nested("extensions", tmpl='{name}\n')
6206 fn = fm.nested("extensions", tmpl='{name}\n')
6208 if names:
6207 if names:
6209 namefmt = " %%-%ds " % max(len(n) for n in names)
6208 namefmt = " %%-%ds " % max(len(n) for n in names)
6210 places = [_("external"), _("internal")]
6209 places = [_("external"), _("internal")]
6211 for n, v, p in zip(names, vers, isinternals):
6210 for n, v, p in zip(names, vers, isinternals):
6212 fn.startitem()
6211 fn.startitem()
6213 fn.condwrite(ui.verbose, "name", namefmt, n)
6212 fn.condwrite(ui.verbose, "name", namefmt, n)
6214 if ui.verbose:
6213 if ui.verbose:
6215 fn.plain("%s " % places[p])
6214 fn.plain("%s " % places[p])
6216 fn.data(bundled=p)
6215 fn.data(bundled=p)
6217 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6216 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6218 if ui.verbose:
6217 if ui.verbose:
6219 fn.plain("\n")
6218 fn.plain("\n")
6220 fn.end()
6219 fn.end()
6221 fm.end()
6220 fm.end()
6222
6221
6223 def loadcmdtable(ui, name, cmdtable):
6222 def loadcmdtable(ui, name, cmdtable):
6224 """Load command functions from specified cmdtable
6223 """Load command functions from specified cmdtable
6225 """
6224 """
6226 cmdtable = cmdtable.copy()
6225 cmdtable = cmdtable.copy()
6227 for cmd in list(cmdtable):
6226 for cmd in list(cmdtable):
6228 if not cmd.startswith('^'):
6227 if not cmd.startswith('^'):
6229 continue
6228 continue
6230 ui.deprecwarn("old-style command registration '%s' in extension '%s'"
6229 ui.deprecwarn("old-style command registration '%s' in extension '%s'"
6231 % (cmd, name), '4.8')
6230 % (cmd, name), '4.8')
6232 entry = cmdtable.pop(cmd)
6231 entry = cmdtable.pop(cmd)
6233 entry[0].helpbasic = True
6232 entry[0].helpbasic = True
6234 cmdtable[cmd[1:]] = entry
6233 cmdtable[cmd[1:]] = entry
6235
6234
6236 overrides = [cmd for cmd in cmdtable if cmd in table]
6235 overrides = [cmd for cmd in cmdtable if cmd in table]
6237 if overrides:
6236 if overrides:
6238 ui.warn(_("extension '%s' overrides commands: %s\n")
6237 ui.warn(_("extension '%s' overrides commands: %s\n")
6239 % (name, " ".join(overrides)))
6238 % (name, " ".join(overrides)))
6240 table.update(cmdtable)
6239 table.update(cmdtable)
@@ -1,1860 +1,1891 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import posixpath
14 import posixpath
15 import re
15 import re
16 import subprocess
16 import subprocess
17 import weakref
17 import weakref
18
18
19 from .i18n import _
19 from .i18n import _
20 from .node import (
20 from .node import (
21 bin,
21 bin,
22 hex,
22 hex,
23 nullid,
23 nullid,
24 nullrev,
24 nullrev,
25 short,
25 short,
26 wdirid,
26 wdirid,
27 wdirrev,
27 wdirrev,
28 )
28 )
29
29
30 from . import (
30 from . import (
31 encoding,
31 encoding,
32 error,
32 error,
33 match as matchmod,
33 match as matchmod,
34 obsolete,
34 obsolete,
35 obsutil,
35 obsutil,
36 pathutil,
36 pathutil,
37 phases,
37 phases,
38 policy,
38 policy,
39 pycompat,
39 pycompat,
40 revsetlang,
40 revsetlang,
41 similar,
41 similar,
42 smartset,
42 smartset,
43 url,
43 url,
44 util,
44 util,
45 vfs,
45 vfs,
46 )
46 )
47
47
48 from .utils import (
48 from .utils import (
49 procutil,
49 procutil,
50 stringutil,
50 stringutil,
51 )
51 )
52
52
53 if pycompat.iswindows:
53 if pycompat.iswindows:
54 from . import scmwindows as scmplatform
54 from . import scmwindows as scmplatform
55 else:
55 else:
56 from . import scmposix as scmplatform
56 from . import scmposix as scmplatform
57
57
58 parsers = policy.importmod(r'parsers')
58 parsers = policy.importmod(r'parsers')
59
59
60 termsize = scmplatform.termsize
60 termsize = scmplatform.termsize
61
61
62 class status(tuple):
62 class status(tuple):
63 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
63 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
64 and 'ignored' properties are only relevant to the working copy.
64 and 'ignored' properties are only relevant to the working copy.
65 '''
65 '''
66
66
67 __slots__ = ()
67 __slots__ = ()
68
68
69 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
69 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
70 clean):
70 clean):
71 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
71 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
72 ignored, clean))
72 ignored, clean))
73
73
74 @property
74 @property
75 def modified(self):
75 def modified(self):
76 '''files that have been modified'''
76 '''files that have been modified'''
77 return self[0]
77 return self[0]
78
78
79 @property
79 @property
80 def added(self):
80 def added(self):
81 '''files that have been added'''
81 '''files that have been added'''
82 return self[1]
82 return self[1]
83
83
84 @property
84 @property
85 def removed(self):
85 def removed(self):
86 '''files that have been removed'''
86 '''files that have been removed'''
87 return self[2]
87 return self[2]
88
88
89 @property
89 @property
90 def deleted(self):
90 def deleted(self):
91 '''files that are in the dirstate, but have been deleted from the
91 '''files that are in the dirstate, but have been deleted from the
92 working copy (aka "missing")
92 working copy (aka "missing")
93 '''
93 '''
94 return self[3]
94 return self[3]
95
95
96 @property
96 @property
97 def unknown(self):
97 def unknown(self):
98 '''files not in the dirstate that are not ignored'''
98 '''files not in the dirstate that are not ignored'''
99 return self[4]
99 return self[4]
100
100
101 @property
101 @property
102 def ignored(self):
102 def ignored(self):
103 '''files not in the dirstate that are ignored (by _dirignore())'''
103 '''files not in the dirstate that are ignored (by _dirignore())'''
104 return self[5]
104 return self[5]
105
105
106 @property
106 @property
107 def clean(self):
107 def clean(self):
108 '''files that have not been modified'''
108 '''files that have not been modified'''
109 return self[6]
109 return self[6]
110
110
111 def __repr__(self, *args, **kwargs):
111 def __repr__(self, *args, **kwargs):
112 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
112 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
113 r'unknown=%s, ignored=%s, clean=%s>') %
113 r'unknown=%s, ignored=%s, clean=%s>') %
114 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
114 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
115
115
116 def itersubrepos(ctx1, ctx2):
116 def itersubrepos(ctx1, ctx2):
117 """find subrepos in ctx1 or ctx2"""
117 """find subrepos in ctx1 or ctx2"""
118 # Create a (subpath, ctx) mapping where we prefer subpaths from
118 # Create a (subpath, ctx) mapping where we prefer subpaths from
119 # ctx1. The subpaths from ctx2 are important when the .hgsub file
119 # ctx1. The subpaths from ctx2 are important when the .hgsub file
120 # has been modified (in ctx2) but not yet committed (in ctx1).
120 # has been modified (in ctx2) but not yet committed (in ctx1).
121 subpaths = dict.fromkeys(ctx2.substate, ctx2)
121 subpaths = dict.fromkeys(ctx2.substate, ctx2)
122 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
122 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
123
123
124 missing = set()
124 missing = set()
125
125
126 for subpath in ctx2.substate:
126 for subpath in ctx2.substate:
127 if subpath not in ctx1.substate:
127 if subpath not in ctx1.substate:
128 del subpaths[subpath]
128 del subpaths[subpath]
129 missing.add(subpath)
129 missing.add(subpath)
130
130
131 for subpath, ctx in sorted(subpaths.iteritems()):
131 for subpath, ctx in sorted(subpaths.iteritems()):
132 yield subpath, ctx.sub(subpath)
132 yield subpath, ctx.sub(subpath)
133
133
134 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
134 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
135 # status and diff will have an accurate result when it does
135 # status and diff will have an accurate result when it does
136 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
136 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
137 # against itself.
137 # against itself.
138 for subpath in missing:
138 for subpath in missing:
139 yield subpath, ctx2.nullsub(subpath, ctx1)
139 yield subpath, ctx2.nullsub(subpath, ctx1)
140
140
141 def nochangesfound(ui, repo, excluded=None):
141 def nochangesfound(ui, repo, excluded=None):
142 '''Report no changes for push/pull, excluded is None or a list of
142 '''Report no changes for push/pull, excluded is None or a list of
143 nodes excluded from the push/pull.
143 nodes excluded from the push/pull.
144 '''
144 '''
145 secretlist = []
145 secretlist = []
146 if excluded:
146 if excluded:
147 for n in excluded:
147 for n in excluded:
148 ctx = repo[n]
148 ctx = repo[n]
149 if ctx.phase() >= phases.secret and not ctx.extinct():
149 if ctx.phase() >= phases.secret and not ctx.extinct():
150 secretlist.append(n)
150 secretlist.append(n)
151
151
152 if secretlist:
152 if secretlist:
153 ui.status(_("no changes found (ignored %d secret changesets)\n")
153 ui.status(_("no changes found (ignored %d secret changesets)\n")
154 % len(secretlist))
154 % len(secretlist))
155 else:
155 else:
156 ui.status(_("no changes found\n"))
156 ui.status(_("no changes found\n"))
157
157
158 def callcatch(ui, func):
158 def callcatch(ui, func):
159 """call func() with global exception handling
159 """call func() with global exception handling
160
160
161 return func() if no exception happens. otherwise do some error handling
161 return func() if no exception happens. otherwise do some error handling
162 and return an exit code accordingly. does not handle all exceptions.
162 and return an exit code accordingly. does not handle all exceptions.
163 """
163 """
164 try:
164 try:
165 try:
165 try:
166 return func()
166 return func()
167 except: # re-raises
167 except: # re-raises
168 ui.traceback()
168 ui.traceback()
169 raise
169 raise
170 # Global exception handling, alphabetically
170 # Global exception handling, alphabetically
171 # Mercurial-specific first, followed by built-in and library exceptions
171 # Mercurial-specific first, followed by built-in and library exceptions
172 except error.LockHeld as inst:
172 except error.LockHeld as inst:
173 if inst.errno == errno.ETIMEDOUT:
173 if inst.errno == errno.ETIMEDOUT:
174 reason = _('timed out waiting for lock held by %r') % (
174 reason = _('timed out waiting for lock held by %r') % (
175 pycompat.bytestr(inst.locker))
175 pycompat.bytestr(inst.locker))
176 else:
176 else:
177 reason = _('lock held by %r') % inst.locker
177 reason = _('lock held by %r') % inst.locker
178 ui.error(_("abort: %s: %s\n") % (
178 ui.error(_("abort: %s: %s\n") % (
179 inst.desc or stringutil.forcebytestr(inst.filename), reason))
179 inst.desc or stringutil.forcebytestr(inst.filename), reason))
180 if not inst.locker:
180 if not inst.locker:
181 ui.error(_("(lock might be very busy)\n"))
181 ui.error(_("(lock might be very busy)\n"))
182 except error.LockUnavailable as inst:
182 except error.LockUnavailable as inst:
183 ui.error(_("abort: could not lock %s: %s\n") %
183 ui.error(_("abort: could not lock %s: %s\n") %
184 (inst.desc or stringutil.forcebytestr(inst.filename),
184 (inst.desc or stringutil.forcebytestr(inst.filename),
185 encoding.strtolocal(inst.strerror)))
185 encoding.strtolocal(inst.strerror)))
186 except error.OutOfBandError as inst:
186 except error.OutOfBandError as inst:
187 if inst.args:
187 if inst.args:
188 msg = _("abort: remote error:\n")
188 msg = _("abort: remote error:\n")
189 else:
189 else:
190 msg = _("abort: remote error\n")
190 msg = _("abort: remote error\n")
191 ui.error(msg)
191 ui.error(msg)
192 if inst.args:
192 if inst.args:
193 ui.error(''.join(inst.args))
193 ui.error(''.join(inst.args))
194 if inst.hint:
194 if inst.hint:
195 ui.error('(%s)\n' % inst.hint)
195 ui.error('(%s)\n' % inst.hint)
196 except error.RepoError as inst:
196 except error.RepoError as inst:
197 ui.error(_("abort: %s!\n") % inst)
197 ui.error(_("abort: %s!\n") % inst)
198 if inst.hint:
198 if inst.hint:
199 ui.error(_("(%s)\n") % inst.hint)
199 ui.error(_("(%s)\n") % inst.hint)
200 except error.ResponseError as inst:
200 except error.ResponseError as inst:
201 ui.error(_("abort: %s") % inst.args[0])
201 ui.error(_("abort: %s") % inst.args[0])
202 msg = inst.args[1]
202 msg = inst.args[1]
203 if isinstance(msg, type(u'')):
203 if isinstance(msg, type(u'')):
204 msg = pycompat.sysbytes(msg)
204 msg = pycompat.sysbytes(msg)
205 if not isinstance(msg, bytes):
205 if not isinstance(msg, bytes):
206 ui.error(" %r\n" % (msg,))
206 ui.error(" %r\n" % (msg,))
207 elif not msg:
207 elif not msg:
208 ui.error(_(" empty string\n"))
208 ui.error(_(" empty string\n"))
209 else:
209 else:
210 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
210 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
211 except error.CensoredNodeError as inst:
211 except error.CensoredNodeError as inst:
212 ui.error(_("abort: file censored %s!\n") % inst)
212 ui.error(_("abort: file censored %s!\n") % inst)
213 except error.StorageError as inst:
213 except error.StorageError as inst:
214 ui.error(_("abort: %s!\n") % inst)
214 ui.error(_("abort: %s!\n") % inst)
215 if inst.hint:
215 if inst.hint:
216 ui.error(_("(%s)\n") % inst.hint)
216 ui.error(_("(%s)\n") % inst.hint)
217 except error.InterventionRequired as inst:
217 except error.InterventionRequired as inst:
218 ui.error("%s\n" % inst)
218 ui.error("%s\n" % inst)
219 if inst.hint:
219 if inst.hint:
220 ui.error(_("(%s)\n") % inst.hint)
220 ui.error(_("(%s)\n") % inst.hint)
221 return 1
221 return 1
222 except error.WdirUnsupported:
222 except error.WdirUnsupported:
223 ui.error(_("abort: working directory revision cannot be specified\n"))
223 ui.error(_("abort: working directory revision cannot be specified\n"))
224 except error.Abort as inst:
224 except error.Abort as inst:
225 ui.error(_("abort: %s\n") % inst)
225 ui.error(_("abort: %s\n") % inst)
226 if inst.hint:
226 if inst.hint:
227 ui.error(_("(%s)\n") % inst.hint)
227 ui.error(_("(%s)\n") % inst.hint)
228 except ImportError as inst:
228 except ImportError as inst:
229 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
229 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
230 m = stringutil.forcebytestr(inst).split()[-1]
230 m = stringutil.forcebytestr(inst).split()[-1]
231 if m in "mpatch bdiff".split():
231 if m in "mpatch bdiff".split():
232 ui.error(_("(did you forget to compile extensions?)\n"))
232 ui.error(_("(did you forget to compile extensions?)\n"))
233 elif m in "zlib".split():
233 elif m in "zlib".split():
234 ui.error(_("(is your Python install correct?)\n"))
234 ui.error(_("(is your Python install correct?)\n"))
235 except (IOError, OSError) as inst:
235 except (IOError, OSError) as inst:
236 if util.safehasattr(inst, "code"): # HTTPError
236 if util.safehasattr(inst, "code"): # HTTPError
237 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
237 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
238 elif util.safehasattr(inst, "reason"): # URLError or SSLError
238 elif util.safehasattr(inst, "reason"): # URLError or SSLError
239 try: # usually it is in the form (errno, strerror)
239 try: # usually it is in the form (errno, strerror)
240 reason = inst.reason.args[1]
240 reason = inst.reason.args[1]
241 except (AttributeError, IndexError):
241 except (AttributeError, IndexError):
242 # it might be anything, for example a string
242 # it might be anything, for example a string
243 reason = inst.reason
243 reason = inst.reason
244 if isinstance(reason, pycompat.unicode):
244 if isinstance(reason, pycompat.unicode):
245 # SSLError of Python 2.7.9 contains a unicode
245 # SSLError of Python 2.7.9 contains a unicode
246 reason = encoding.unitolocal(reason)
246 reason = encoding.unitolocal(reason)
247 ui.error(_("abort: error: %s\n") % reason)
247 ui.error(_("abort: error: %s\n") % reason)
248 elif (util.safehasattr(inst, "args")
248 elif (util.safehasattr(inst, "args")
249 and inst.args and inst.args[0] == errno.EPIPE):
249 and inst.args and inst.args[0] == errno.EPIPE):
250 pass
250 pass
251 elif getattr(inst, "strerror", None): # common IOError or OSError
251 elif getattr(inst, "strerror", None): # common IOError or OSError
252 if getattr(inst, "filename", None) is not None:
252 if getattr(inst, "filename", None) is not None:
253 ui.error(_("abort: %s: '%s'\n") % (
253 ui.error(_("abort: %s: '%s'\n") % (
254 encoding.strtolocal(inst.strerror),
254 encoding.strtolocal(inst.strerror),
255 stringutil.forcebytestr(inst.filename)))
255 stringutil.forcebytestr(inst.filename)))
256 else:
256 else:
257 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
257 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
258 else: # suspicious IOError
258 else: # suspicious IOError
259 raise
259 raise
260 except MemoryError:
260 except MemoryError:
261 ui.error(_("abort: out of memory\n"))
261 ui.error(_("abort: out of memory\n"))
262 except SystemExit as inst:
262 except SystemExit as inst:
263 # Commands shouldn't sys.exit directly, but give a return code.
263 # Commands shouldn't sys.exit directly, but give a return code.
264 # Just in case catch this and and pass exit code to caller.
264 # Just in case catch this and and pass exit code to caller.
265 return inst.code
265 return inst.code
266
266
267 return -1
267 return -1
268
268
269 def checknewlabel(repo, lbl, kind):
269 def checknewlabel(repo, lbl, kind):
270 # Do not use the "kind" parameter in ui output.
270 # Do not use the "kind" parameter in ui output.
271 # It makes strings difficult to translate.
271 # It makes strings difficult to translate.
272 if lbl in ['tip', '.', 'null']:
272 if lbl in ['tip', '.', 'null']:
273 raise error.Abort(_("the name '%s' is reserved") % lbl)
273 raise error.Abort(_("the name '%s' is reserved") % lbl)
274 for c in (':', '\0', '\n', '\r'):
274 for c in (':', '\0', '\n', '\r'):
275 if c in lbl:
275 if c in lbl:
276 raise error.Abort(
276 raise error.Abort(
277 _("%r cannot be used in a name") % pycompat.bytestr(c))
277 _("%r cannot be used in a name") % pycompat.bytestr(c))
278 try:
278 try:
279 int(lbl)
279 int(lbl)
280 raise error.Abort(_("cannot use an integer as a name"))
280 raise error.Abort(_("cannot use an integer as a name"))
281 except ValueError:
281 except ValueError:
282 pass
282 pass
283 if lbl.strip() != lbl:
283 if lbl.strip() != lbl:
284 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
284 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
285
285
286 def checkfilename(f):
286 def checkfilename(f):
287 '''Check that the filename f is an acceptable filename for a tracked file'''
287 '''Check that the filename f is an acceptable filename for a tracked file'''
288 if '\r' in f or '\n' in f:
288 if '\r' in f or '\n' in f:
289 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
289 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
290 % pycompat.bytestr(f))
290 % pycompat.bytestr(f))
291
291
292 def checkportable(ui, f):
292 def checkportable(ui, f):
293 '''Check if filename f is portable and warn or abort depending on config'''
293 '''Check if filename f is portable and warn or abort depending on config'''
294 checkfilename(f)
294 checkfilename(f)
295 abort, warn = checkportabilityalert(ui)
295 abort, warn = checkportabilityalert(ui)
296 if abort or warn:
296 if abort or warn:
297 msg = util.checkwinfilename(f)
297 msg = util.checkwinfilename(f)
298 if msg:
298 if msg:
299 msg = "%s: %s" % (msg, procutil.shellquote(f))
299 msg = "%s: %s" % (msg, procutil.shellquote(f))
300 if abort:
300 if abort:
301 raise error.Abort(msg)
301 raise error.Abort(msg)
302 ui.warn(_("warning: %s\n") % msg)
302 ui.warn(_("warning: %s\n") % msg)
303
303
304 def checkportabilityalert(ui):
304 def checkportabilityalert(ui):
305 '''check if the user's config requests nothing, a warning, or abort for
305 '''check if the user's config requests nothing, a warning, or abort for
306 non-portable filenames'''
306 non-portable filenames'''
307 val = ui.config('ui', 'portablefilenames')
307 val = ui.config('ui', 'portablefilenames')
308 lval = val.lower()
308 lval = val.lower()
309 bval = stringutil.parsebool(val)
309 bval = stringutil.parsebool(val)
310 abort = pycompat.iswindows or lval == 'abort'
310 abort = pycompat.iswindows or lval == 'abort'
311 warn = bval or lval == 'warn'
311 warn = bval or lval == 'warn'
312 if bval is None and not (warn or abort or lval == 'ignore'):
312 if bval is None and not (warn or abort or lval == 'ignore'):
313 raise error.ConfigError(
313 raise error.ConfigError(
314 _("ui.portablefilenames value is invalid ('%s')") % val)
314 _("ui.portablefilenames value is invalid ('%s')") % val)
315 return abort, warn
315 return abort, warn
316
316
317 class casecollisionauditor(object):
317 class casecollisionauditor(object):
318 def __init__(self, ui, abort, dirstate):
318 def __init__(self, ui, abort, dirstate):
319 self._ui = ui
319 self._ui = ui
320 self._abort = abort
320 self._abort = abort
321 allfiles = '\0'.join(dirstate._map)
321 allfiles = '\0'.join(dirstate._map)
322 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
322 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
323 self._dirstate = dirstate
323 self._dirstate = dirstate
324 # The purpose of _newfiles is so that we don't complain about
324 # The purpose of _newfiles is so that we don't complain about
325 # case collisions if someone were to call this object with the
325 # case collisions if someone were to call this object with the
326 # same filename twice.
326 # same filename twice.
327 self._newfiles = set()
327 self._newfiles = set()
328
328
329 def __call__(self, f):
329 def __call__(self, f):
330 if f in self._newfiles:
330 if f in self._newfiles:
331 return
331 return
332 fl = encoding.lower(f)
332 fl = encoding.lower(f)
333 if fl in self._loweredfiles and f not in self._dirstate:
333 if fl in self._loweredfiles and f not in self._dirstate:
334 msg = _('possible case-folding collision for %s') % f
334 msg = _('possible case-folding collision for %s') % f
335 if self._abort:
335 if self._abort:
336 raise error.Abort(msg)
336 raise error.Abort(msg)
337 self._ui.warn(_("warning: %s\n") % msg)
337 self._ui.warn(_("warning: %s\n") % msg)
338 self._loweredfiles.add(fl)
338 self._loweredfiles.add(fl)
339 self._newfiles.add(f)
339 self._newfiles.add(f)
340
340
341 def filteredhash(repo, maxrev):
341 def filteredhash(repo, maxrev):
342 """build hash of filtered revisions in the current repoview.
342 """build hash of filtered revisions in the current repoview.
343
343
344 Multiple caches perform up-to-date validation by checking that the
344 Multiple caches perform up-to-date validation by checking that the
345 tiprev and tipnode stored in the cache file match the current repository.
345 tiprev and tipnode stored in the cache file match the current repository.
346 However, this is not sufficient for validating repoviews because the set
346 However, this is not sufficient for validating repoviews because the set
347 of revisions in the view may change without the repository tiprev and
347 of revisions in the view may change without the repository tiprev and
348 tipnode changing.
348 tipnode changing.
349
349
350 This function hashes all the revs filtered from the view and returns
350 This function hashes all the revs filtered from the view and returns
351 that SHA-1 digest.
351 that SHA-1 digest.
352 """
352 """
353 cl = repo.changelog
353 cl = repo.changelog
354 if not cl.filteredrevs:
354 if not cl.filteredrevs:
355 return None
355 return None
356 key = None
356 key = None
357 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
357 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
358 if revs:
358 if revs:
359 s = hashlib.sha1()
359 s = hashlib.sha1()
360 for rev in revs:
360 for rev in revs:
361 s.update('%d;' % rev)
361 s.update('%d;' % rev)
362 key = s.digest()
362 key = s.digest()
363 return key
363 return key
364
364
365 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
365 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
366 '''yield every hg repository under path, always recursively.
366 '''yield every hg repository under path, always recursively.
367 The recurse flag will only control recursion into repo working dirs'''
367 The recurse flag will only control recursion into repo working dirs'''
368 def errhandler(err):
368 def errhandler(err):
369 if err.filename == path:
369 if err.filename == path:
370 raise err
370 raise err
371 samestat = getattr(os.path, 'samestat', None)
371 samestat = getattr(os.path, 'samestat', None)
372 if followsym and samestat is not None:
372 if followsym and samestat is not None:
373 def adddir(dirlst, dirname):
373 def adddir(dirlst, dirname):
374 dirstat = os.stat(dirname)
374 dirstat = os.stat(dirname)
375 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
375 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
376 if not match:
376 if not match:
377 dirlst.append(dirstat)
377 dirlst.append(dirstat)
378 return not match
378 return not match
379 else:
379 else:
380 followsym = False
380 followsym = False
381
381
382 if (seen_dirs is None) and followsym:
382 if (seen_dirs is None) and followsym:
383 seen_dirs = []
383 seen_dirs = []
384 adddir(seen_dirs, path)
384 adddir(seen_dirs, path)
385 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
385 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
386 dirs.sort()
386 dirs.sort()
387 if '.hg' in dirs:
387 if '.hg' in dirs:
388 yield root # found a repository
388 yield root # found a repository
389 qroot = os.path.join(root, '.hg', 'patches')
389 qroot = os.path.join(root, '.hg', 'patches')
390 if os.path.isdir(os.path.join(qroot, '.hg')):
390 if os.path.isdir(os.path.join(qroot, '.hg')):
391 yield qroot # we have a patch queue repo here
391 yield qroot # we have a patch queue repo here
392 if recurse:
392 if recurse:
393 # avoid recursing inside the .hg directory
393 # avoid recursing inside the .hg directory
394 dirs.remove('.hg')
394 dirs.remove('.hg')
395 else:
395 else:
396 dirs[:] = [] # don't descend further
396 dirs[:] = [] # don't descend further
397 elif followsym:
397 elif followsym:
398 newdirs = []
398 newdirs = []
399 for d in dirs:
399 for d in dirs:
400 fname = os.path.join(root, d)
400 fname = os.path.join(root, d)
401 if adddir(seen_dirs, fname):
401 if adddir(seen_dirs, fname):
402 if os.path.islink(fname):
402 if os.path.islink(fname):
403 for hgname in walkrepos(fname, True, seen_dirs):
403 for hgname in walkrepos(fname, True, seen_dirs):
404 yield hgname
404 yield hgname
405 else:
405 else:
406 newdirs.append(d)
406 newdirs.append(d)
407 dirs[:] = newdirs
407 dirs[:] = newdirs
408
408
409 def binnode(ctx):
409 def binnode(ctx):
410 """Return binary node id for a given basectx"""
410 """Return binary node id for a given basectx"""
411 node = ctx.node()
411 node = ctx.node()
412 if node is None:
412 if node is None:
413 return wdirid
413 return wdirid
414 return node
414 return node
415
415
416 def intrev(ctx):
416 def intrev(ctx):
417 """Return integer for a given basectx that can be used in comparison or
417 """Return integer for a given basectx that can be used in comparison or
418 arithmetic operation"""
418 arithmetic operation"""
419 rev = ctx.rev()
419 rev = ctx.rev()
420 if rev is None:
420 if rev is None:
421 return wdirrev
421 return wdirrev
422 return rev
422 return rev
423
423
424 def formatchangeid(ctx):
424 def formatchangeid(ctx):
425 """Format changectx as '{rev}:{node|formatnode}', which is the default
425 """Format changectx as '{rev}:{node|formatnode}', which is the default
426 template provided by logcmdutil.changesettemplater"""
426 template provided by logcmdutil.changesettemplater"""
427 repo = ctx.repo()
427 repo = ctx.repo()
428 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
428 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
429
429
430 def formatrevnode(ui, rev, node):
430 def formatrevnode(ui, rev, node):
431 """Format given revision and node depending on the current verbosity"""
431 """Format given revision and node depending on the current verbosity"""
432 if ui.debugflag:
432 if ui.debugflag:
433 hexfunc = hex
433 hexfunc = hex
434 else:
434 else:
435 hexfunc = short
435 hexfunc = short
436 return '%d:%s' % (rev, hexfunc(node))
436 return '%d:%s' % (rev, hexfunc(node))
437
437
438 def resolvehexnodeidprefix(repo, prefix):
438 def resolvehexnodeidprefix(repo, prefix):
439 if (prefix.startswith('x') and
439 if (prefix.startswith('x') and
440 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
440 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
441 prefix = prefix[1:]
441 prefix = prefix[1:]
442 try:
442 try:
443 # Uses unfiltered repo because it's faster when prefix is ambiguous/
443 # Uses unfiltered repo because it's faster when prefix is ambiguous/
444 # This matches the shortesthexnodeidprefix() function below.
444 # This matches the shortesthexnodeidprefix() function below.
445 node = repo.unfiltered().changelog._partialmatch(prefix)
445 node = repo.unfiltered().changelog._partialmatch(prefix)
446 except error.AmbiguousPrefixLookupError:
446 except error.AmbiguousPrefixLookupError:
447 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
447 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
448 if revset:
448 if revset:
449 # Clear config to avoid infinite recursion
449 # Clear config to avoid infinite recursion
450 configoverrides = {('experimental',
450 configoverrides = {('experimental',
451 'revisions.disambiguatewithin'): None}
451 'revisions.disambiguatewithin'): None}
452 with repo.ui.configoverride(configoverrides):
452 with repo.ui.configoverride(configoverrides):
453 revs = repo.anyrevs([revset], user=True)
453 revs = repo.anyrevs([revset], user=True)
454 matches = []
454 matches = []
455 for rev in revs:
455 for rev in revs:
456 node = repo.changelog.node(rev)
456 node = repo.changelog.node(rev)
457 if hex(node).startswith(prefix):
457 if hex(node).startswith(prefix):
458 matches.append(node)
458 matches.append(node)
459 if len(matches) == 1:
459 if len(matches) == 1:
460 return matches[0]
460 return matches[0]
461 raise
461 raise
462 if node is None:
462 if node is None:
463 return
463 return
464 repo.changelog.rev(node) # make sure node isn't filtered
464 repo.changelog.rev(node) # make sure node isn't filtered
465 return node
465 return node
466
466
467 def mayberevnum(repo, prefix):
467 def mayberevnum(repo, prefix):
468 """Checks if the given prefix may be mistaken for a revision number"""
468 """Checks if the given prefix may be mistaken for a revision number"""
469 try:
469 try:
470 i = int(prefix)
470 i = int(prefix)
471 # if we are a pure int, then starting with zero will not be
471 # if we are a pure int, then starting with zero will not be
472 # confused as a rev; or, obviously, if the int is larger
472 # confused as a rev; or, obviously, if the int is larger
473 # than the value of the tip rev. We still need to disambiguate if
473 # than the value of the tip rev. We still need to disambiguate if
474 # prefix == '0', since that *is* a valid revnum.
474 # prefix == '0', since that *is* a valid revnum.
475 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
475 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
476 return False
476 return False
477 return True
477 return True
478 except ValueError:
478 except ValueError:
479 return False
479 return False
480
480
481 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
481 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
482 """Find the shortest unambiguous prefix that matches hexnode.
482 """Find the shortest unambiguous prefix that matches hexnode.
483
483
484 If "cache" is not None, it must be a dictionary that can be used for
484 If "cache" is not None, it must be a dictionary that can be used for
485 caching between calls to this method.
485 caching between calls to this method.
486 """
486 """
487 # _partialmatch() of filtered changelog could take O(len(repo)) time,
487 # _partialmatch() of filtered changelog could take O(len(repo)) time,
488 # which would be unacceptably slow. so we look for hash collision in
488 # which would be unacceptably slow. so we look for hash collision in
489 # unfiltered space, which means some hashes may be slightly longer.
489 # unfiltered space, which means some hashes may be slightly longer.
490
490
491 minlength=max(minlength, 1)
491 minlength=max(minlength, 1)
492
492
493 def disambiguate(prefix):
493 def disambiguate(prefix):
494 """Disambiguate against revnums."""
494 """Disambiguate against revnums."""
495 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
495 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
496 if mayberevnum(repo, prefix):
496 if mayberevnum(repo, prefix):
497 return 'x' + prefix
497 return 'x' + prefix
498 else:
498 else:
499 return prefix
499 return prefix
500
500
501 hexnode = hex(node)
501 hexnode = hex(node)
502 for length in range(len(prefix), len(hexnode) + 1):
502 for length in range(len(prefix), len(hexnode) + 1):
503 prefix = hexnode[:length]
503 prefix = hexnode[:length]
504 if not mayberevnum(repo, prefix):
504 if not mayberevnum(repo, prefix):
505 return prefix
505 return prefix
506
506
507 cl = repo.unfiltered().changelog
507 cl = repo.unfiltered().changelog
508 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
508 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
509 if revset:
509 if revset:
510 revs = None
510 revs = None
511 if cache is not None:
511 if cache is not None:
512 revs = cache.get('disambiguationrevset')
512 revs = cache.get('disambiguationrevset')
513 if revs is None:
513 if revs is None:
514 revs = repo.anyrevs([revset], user=True)
514 revs = repo.anyrevs([revset], user=True)
515 if cache is not None:
515 if cache is not None:
516 cache['disambiguationrevset'] = revs
516 cache['disambiguationrevset'] = revs
517 if cl.rev(node) in revs:
517 if cl.rev(node) in revs:
518 hexnode = hex(node)
518 hexnode = hex(node)
519 nodetree = None
519 nodetree = None
520 if cache is not None:
520 if cache is not None:
521 nodetree = cache.get('disambiguationnodetree')
521 nodetree = cache.get('disambiguationnodetree')
522 if not nodetree:
522 if not nodetree:
523 try:
523 try:
524 nodetree = parsers.nodetree(cl.index, len(revs))
524 nodetree = parsers.nodetree(cl.index, len(revs))
525 except AttributeError:
525 except AttributeError:
526 # no native nodetree
526 # no native nodetree
527 pass
527 pass
528 else:
528 else:
529 for r in revs:
529 for r in revs:
530 nodetree.insert(r)
530 nodetree.insert(r)
531 if cache is not None:
531 if cache is not None:
532 cache['disambiguationnodetree'] = nodetree
532 cache['disambiguationnodetree'] = nodetree
533 if nodetree is not None:
533 if nodetree is not None:
534 length = max(nodetree.shortest(node), minlength)
534 length = max(nodetree.shortest(node), minlength)
535 prefix = hexnode[:length]
535 prefix = hexnode[:length]
536 return disambiguate(prefix)
536 return disambiguate(prefix)
537 for length in range(minlength, len(hexnode) + 1):
537 for length in range(minlength, len(hexnode) + 1):
538 matches = []
538 matches = []
539 prefix = hexnode[:length]
539 prefix = hexnode[:length]
540 for rev in revs:
540 for rev in revs:
541 otherhexnode = repo[rev].hex()
541 otherhexnode = repo[rev].hex()
542 if prefix == otherhexnode[:length]:
542 if prefix == otherhexnode[:length]:
543 matches.append(otherhexnode)
543 matches.append(otherhexnode)
544 if len(matches) == 1:
544 if len(matches) == 1:
545 return disambiguate(prefix)
545 return disambiguate(prefix)
546
546
547 try:
547 try:
548 return disambiguate(cl.shortest(node, minlength))
548 return disambiguate(cl.shortest(node, minlength))
549 except error.LookupError:
549 except error.LookupError:
550 raise error.RepoLookupError()
550 raise error.RepoLookupError()
551
551
552 def isrevsymbol(repo, symbol):
552 def isrevsymbol(repo, symbol):
553 """Checks if a symbol exists in the repo.
553 """Checks if a symbol exists in the repo.
554
554
555 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
555 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
556 symbol is an ambiguous nodeid prefix.
556 symbol is an ambiguous nodeid prefix.
557 """
557 """
558 try:
558 try:
559 revsymbol(repo, symbol)
559 revsymbol(repo, symbol)
560 return True
560 return True
561 except error.RepoLookupError:
561 except error.RepoLookupError:
562 return False
562 return False
563
563
564 def revsymbol(repo, symbol):
564 def revsymbol(repo, symbol):
565 """Returns a context given a single revision symbol (as string).
565 """Returns a context given a single revision symbol (as string).
566
566
567 This is similar to revsingle(), but accepts only a single revision symbol,
567 This is similar to revsingle(), but accepts only a single revision symbol,
568 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
568 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
569 not "max(public())".
569 not "max(public())".
570 """
570 """
571 if not isinstance(symbol, bytes):
571 if not isinstance(symbol, bytes):
572 msg = ("symbol (%s of type %s) was not a string, did you mean "
572 msg = ("symbol (%s of type %s) was not a string, did you mean "
573 "repo[symbol]?" % (symbol, type(symbol)))
573 "repo[symbol]?" % (symbol, type(symbol)))
574 raise error.ProgrammingError(msg)
574 raise error.ProgrammingError(msg)
575 try:
575 try:
576 if symbol in ('.', 'tip', 'null'):
576 if symbol in ('.', 'tip', 'null'):
577 return repo[symbol]
577 return repo[symbol]
578
578
579 try:
579 try:
580 r = int(symbol)
580 r = int(symbol)
581 if '%d' % r != symbol:
581 if '%d' % r != symbol:
582 raise ValueError
582 raise ValueError
583 l = len(repo.changelog)
583 l = len(repo.changelog)
584 if r < 0:
584 if r < 0:
585 r += l
585 r += l
586 if r < 0 or r >= l and r != wdirrev:
586 if r < 0 or r >= l and r != wdirrev:
587 raise ValueError
587 raise ValueError
588 return repo[r]
588 return repo[r]
589 except error.FilteredIndexError:
589 except error.FilteredIndexError:
590 raise
590 raise
591 except (ValueError, OverflowError, IndexError):
591 except (ValueError, OverflowError, IndexError):
592 pass
592 pass
593
593
594 if len(symbol) == 40:
594 if len(symbol) == 40:
595 try:
595 try:
596 node = bin(symbol)
596 node = bin(symbol)
597 rev = repo.changelog.rev(node)
597 rev = repo.changelog.rev(node)
598 return repo[rev]
598 return repo[rev]
599 except error.FilteredLookupError:
599 except error.FilteredLookupError:
600 raise
600 raise
601 except (TypeError, LookupError):
601 except (TypeError, LookupError):
602 pass
602 pass
603
603
604 # look up bookmarks through the name interface
604 # look up bookmarks through the name interface
605 try:
605 try:
606 node = repo.names.singlenode(repo, symbol)
606 node = repo.names.singlenode(repo, symbol)
607 rev = repo.changelog.rev(node)
607 rev = repo.changelog.rev(node)
608 return repo[rev]
608 return repo[rev]
609 except KeyError:
609 except KeyError:
610 pass
610 pass
611
611
612 node = resolvehexnodeidprefix(repo, symbol)
612 node = resolvehexnodeidprefix(repo, symbol)
613 if node is not None:
613 if node is not None:
614 rev = repo.changelog.rev(node)
614 rev = repo.changelog.rev(node)
615 return repo[rev]
615 return repo[rev]
616
616
617 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
617 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
618
618
619 except error.WdirUnsupported:
619 except error.WdirUnsupported:
620 return repo[None]
620 return repo[None]
621 except (error.FilteredIndexError, error.FilteredLookupError,
621 except (error.FilteredIndexError, error.FilteredLookupError,
622 error.FilteredRepoLookupError):
622 error.FilteredRepoLookupError):
623 raise _filterederror(repo, symbol)
623 raise _filterederror(repo, symbol)
624
624
625 def _filterederror(repo, changeid):
625 def _filterederror(repo, changeid):
626 """build an exception to be raised about a filtered changeid
626 """build an exception to be raised about a filtered changeid
627
627
628 This is extracted in a function to help extensions (eg: evolve) to
628 This is extracted in a function to help extensions (eg: evolve) to
629 experiment with various message variants."""
629 experiment with various message variants."""
630 if repo.filtername.startswith('visible'):
630 if repo.filtername.startswith('visible'):
631
631
632 # Check if the changeset is obsolete
632 # Check if the changeset is obsolete
633 unfilteredrepo = repo.unfiltered()
633 unfilteredrepo = repo.unfiltered()
634 ctx = revsymbol(unfilteredrepo, changeid)
634 ctx = revsymbol(unfilteredrepo, changeid)
635
635
636 # If the changeset is obsolete, enrich the message with the reason
636 # If the changeset is obsolete, enrich the message with the reason
637 # that made this changeset not visible
637 # that made this changeset not visible
638 if ctx.obsolete():
638 if ctx.obsolete():
639 msg = obsutil._getfilteredreason(repo, changeid, ctx)
639 msg = obsutil._getfilteredreason(repo, changeid, ctx)
640 else:
640 else:
641 msg = _("hidden revision '%s'") % changeid
641 msg = _("hidden revision '%s'") % changeid
642
642
643 hint = _('use --hidden to access hidden revisions')
643 hint = _('use --hidden to access hidden revisions')
644
644
645 return error.FilteredRepoLookupError(msg, hint=hint)
645 return error.FilteredRepoLookupError(msg, hint=hint)
646 msg = _("filtered revision '%s' (not in '%s' subset)")
646 msg = _("filtered revision '%s' (not in '%s' subset)")
647 msg %= (changeid, repo.filtername)
647 msg %= (changeid, repo.filtername)
648 return error.FilteredRepoLookupError(msg)
648 return error.FilteredRepoLookupError(msg)
649
649
650 def revsingle(repo, revspec, default='.', localalias=None):
650 def revsingle(repo, revspec, default='.', localalias=None):
651 if not revspec and revspec != 0:
651 if not revspec and revspec != 0:
652 return repo[default]
652 return repo[default]
653
653
654 l = revrange(repo, [revspec], localalias=localalias)
654 l = revrange(repo, [revspec], localalias=localalias)
655 if not l:
655 if not l:
656 raise error.Abort(_('empty revision set'))
656 raise error.Abort(_('empty revision set'))
657 return repo[l.last()]
657 return repo[l.last()]
658
658
659 def _pairspec(revspec):
659 def _pairspec(revspec):
660 tree = revsetlang.parse(revspec)
660 tree = revsetlang.parse(revspec)
661 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
661 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
662
662
663 def revpair(repo, revs):
663 def revpair(repo, revs):
664 if not revs:
664 if not revs:
665 return repo['.'], repo[None]
665 return repo['.'], repo[None]
666
666
667 l = revrange(repo, revs)
667 l = revrange(repo, revs)
668
668
669 if not l:
669 if not l:
670 raise error.Abort(_('empty revision range'))
670 raise error.Abort(_('empty revision range'))
671
671
672 first = l.first()
672 first = l.first()
673 second = l.last()
673 second = l.last()
674
674
675 if (first == second and len(revs) >= 2
675 if (first == second and len(revs) >= 2
676 and not all(revrange(repo, [r]) for r in revs)):
676 and not all(revrange(repo, [r]) for r in revs)):
677 raise error.Abort(_('empty revision on one side of range'))
677 raise error.Abort(_('empty revision on one side of range'))
678
678
679 # if top-level is range expression, the result must always be a pair
679 # if top-level is range expression, the result must always be a pair
680 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
680 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
681 return repo[first], repo[None]
681 return repo[first], repo[None]
682
682
683 return repo[first], repo[second]
683 return repo[first], repo[second]
684
684
685 def revrange(repo, specs, localalias=None):
685 def revrange(repo, specs, localalias=None):
686 """Execute 1 to many revsets and return the union.
686 """Execute 1 to many revsets and return the union.
687
687
688 This is the preferred mechanism for executing revsets using user-specified
688 This is the preferred mechanism for executing revsets using user-specified
689 config options, such as revset aliases.
689 config options, such as revset aliases.
690
690
691 The revsets specified by ``specs`` will be executed via a chained ``OR``
691 The revsets specified by ``specs`` will be executed via a chained ``OR``
692 expression. If ``specs`` is empty, an empty result is returned.
692 expression. If ``specs`` is empty, an empty result is returned.
693
693
694 ``specs`` can contain integers, in which case they are assumed to be
694 ``specs`` can contain integers, in which case they are assumed to be
695 revision numbers.
695 revision numbers.
696
696
697 It is assumed the revsets are already formatted. If you have arguments
697 It is assumed the revsets are already formatted. If you have arguments
698 that need to be expanded in the revset, call ``revsetlang.formatspec()``
698 that need to be expanded in the revset, call ``revsetlang.formatspec()``
699 and pass the result as an element of ``specs``.
699 and pass the result as an element of ``specs``.
700
700
701 Specifying a single revset is allowed.
701 Specifying a single revset is allowed.
702
702
703 Returns a ``revset.abstractsmartset`` which is a list-like interface over
703 Returns a ``revset.abstractsmartset`` which is a list-like interface over
704 integer revisions.
704 integer revisions.
705 """
705 """
706 allspecs = []
706 allspecs = []
707 for spec in specs:
707 for spec in specs:
708 if isinstance(spec, int):
708 if isinstance(spec, int):
709 spec = revsetlang.formatspec('%d', spec)
709 spec = revsetlang.formatspec('%d', spec)
710 allspecs.append(spec)
710 allspecs.append(spec)
711 return repo.anyrevs(allspecs, user=True, localalias=localalias)
711 return repo.anyrevs(allspecs, user=True, localalias=localalias)
712
712
713 def meaningfulparents(repo, ctx):
713 def meaningfulparents(repo, ctx):
714 """Return list of meaningful (or all if debug) parentrevs for rev.
714 """Return list of meaningful (or all if debug) parentrevs for rev.
715
715
716 For merges (two non-nullrev revisions) both parents are meaningful.
716 For merges (two non-nullrev revisions) both parents are meaningful.
717 Otherwise the first parent revision is considered meaningful if it
717 Otherwise the first parent revision is considered meaningful if it
718 is not the preceding revision.
718 is not the preceding revision.
719 """
719 """
720 parents = ctx.parents()
720 parents = ctx.parents()
721 if len(parents) > 1:
721 if len(parents) > 1:
722 return parents
722 return parents
723 if repo.ui.debugflag:
723 if repo.ui.debugflag:
724 return [parents[0], repo[nullrev]]
724 return [parents[0], repo[nullrev]]
725 if parents[0].rev() >= intrev(ctx) - 1:
725 if parents[0].rev() >= intrev(ctx) - 1:
726 return []
726 return []
727 return parents
727 return parents
728
728
729 def getuipathfn(repo, legacyrelativevalue=False, forcerelativevalue=None):
729 def getuipathfn(repo, legacyrelativevalue=False, forcerelativevalue=None):
730 """Return a function that produced paths for presenting to the user.
730 """Return a function that produced paths for presenting to the user.
731
731
732 The returned function takes a repo-relative path and produces a path
732 The returned function takes a repo-relative path and produces a path
733 that can be presented in the UI.
733 that can be presented in the UI.
734
734
735 Depending on the value of ui.relative-paths, either a repo-relative or
735 Depending on the value of ui.relative-paths, either a repo-relative or
736 cwd-relative path will be produced.
736 cwd-relative path will be produced.
737
737
738 legacyrelativevalue is the value to use if ui.relative-paths=legacy
738 legacyrelativevalue is the value to use if ui.relative-paths=legacy
739
739
740 If forcerelativevalue is not None, then that value will be used regardless
740 If forcerelativevalue is not None, then that value will be used regardless
741 of what ui.relative-paths is set to.
741 of what ui.relative-paths is set to.
742 """
742 """
743 if forcerelativevalue is not None:
743 if forcerelativevalue is not None:
744 relative = forcerelativevalue
744 relative = forcerelativevalue
745 else:
745 else:
746 config = repo.ui.config('ui', 'relative-paths')
746 config = repo.ui.config('ui', 'relative-paths')
747 if config == 'legacy':
747 if config == 'legacy':
748 relative = legacyrelativevalue
748 relative = legacyrelativevalue
749 else:
749 else:
750 relative = stringutil.parsebool(config)
750 relative = stringutil.parsebool(config)
751 if relative is None:
751 if relative is None:
752 raise error.ConfigError(
752 raise error.ConfigError(
753 _("ui.relative-paths is not a boolean ('%s')") % config)
753 _("ui.relative-paths is not a boolean ('%s')") % config)
754
754
755 if relative:
755 if relative:
756 cwd = repo.getcwd()
756 cwd = repo.getcwd()
757 pathto = repo.pathto
757 pathto = repo.pathto
758 return lambda f: pathto(f, cwd)
758 return lambda f: pathto(f, cwd)
759 elif repo.ui.configbool('ui', 'slash'):
759 elif repo.ui.configbool('ui', 'slash'):
760 return lambda f: f
760 return lambda f: f
761 else:
761 else:
762 return util.localpath
762 return util.localpath
763
763
764 def subdiruipathfn(subpath, uipathfn):
764 def subdiruipathfn(subpath, uipathfn):
765 '''Create a new uipathfn that treats the file as relative to subpath.'''
765 '''Create a new uipathfn that treats the file as relative to subpath.'''
766 return lambda f: uipathfn(posixpath.join(subpath, f))
766 return lambda f: uipathfn(posixpath.join(subpath, f))
767
767
768 def anypats(pats, opts):
768 def anypats(pats, opts):
769 '''Checks if any patterns, including --include and --exclude were given.
769 '''Checks if any patterns, including --include and --exclude were given.
770
770
771 Some commands (e.g. addremove) use this condition for deciding whether to
771 Some commands (e.g. addremove) use this condition for deciding whether to
772 print absolute or relative paths.
772 print absolute or relative paths.
773 '''
773 '''
774 return bool(pats or opts.get('include') or opts.get('exclude'))
774 return bool(pats or opts.get('include') or opts.get('exclude'))
775
775
776 def expandpats(pats):
776 def expandpats(pats):
777 '''Expand bare globs when running on windows.
777 '''Expand bare globs when running on windows.
778 On posix we assume it already has already been done by sh.'''
778 On posix we assume it already has already been done by sh.'''
779 if not util.expandglobs:
779 if not util.expandglobs:
780 return list(pats)
780 return list(pats)
781 ret = []
781 ret = []
782 for kindpat in pats:
782 for kindpat in pats:
783 kind, pat = matchmod._patsplit(kindpat, None)
783 kind, pat = matchmod._patsplit(kindpat, None)
784 if kind is None:
784 if kind is None:
785 try:
785 try:
786 globbed = glob.glob(pat)
786 globbed = glob.glob(pat)
787 except re.error:
787 except re.error:
788 globbed = [pat]
788 globbed = [pat]
789 if globbed:
789 if globbed:
790 ret.extend(globbed)
790 ret.extend(globbed)
791 continue
791 continue
792 ret.append(kindpat)
792 ret.append(kindpat)
793 return ret
793 return ret
794
794
795 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
795 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
796 badfn=None):
796 badfn=None):
797 '''Return a matcher and the patterns that were used.
797 '''Return a matcher and the patterns that were used.
798 The matcher will warn about bad matches, unless an alternate badfn callback
798 The matcher will warn about bad matches, unless an alternate badfn callback
799 is provided.'''
799 is provided.'''
800 if opts is None:
800 if opts is None:
801 opts = {}
801 opts = {}
802 if not globbed and default == 'relpath':
802 if not globbed and default == 'relpath':
803 pats = expandpats(pats or [])
803 pats = expandpats(pats or [])
804
804
805 uipathfn = getuipathfn(ctx.repo(), legacyrelativevalue=True)
805 uipathfn = getuipathfn(ctx.repo(), legacyrelativevalue=True)
806 def bad(f, msg):
806 def bad(f, msg):
807 ctx.repo().ui.warn("%s: %s\n" % (uipathfn(f), msg))
807 ctx.repo().ui.warn("%s: %s\n" % (uipathfn(f), msg))
808
808
809 if badfn is None:
809 if badfn is None:
810 badfn = bad
810 badfn = bad
811
811
812 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
812 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
813 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
813 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
814
814
815 if m.always():
815 if m.always():
816 pats = []
816 pats = []
817 return m, pats
817 return m, pats
818
818
819 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
819 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
820 badfn=None):
820 badfn=None):
821 '''Return a matcher that will warn about bad matches.'''
821 '''Return a matcher that will warn about bad matches.'''
822 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
822 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
823
823
824 def matchall(repo):
824 def matchall(repo):
825 '''Return a matcher that will efficiently match everything.'''
825 '''Return a matcher that will efficiently match everything.'''
826 return matchmod.always()
826 return matchmod.always()
827
827
828 def matchfiles(repo, files, badfn=None):
828 def matchfiles(repo, files, badfn=None):
829 '''Return a matcher that will efficiently match exactly these files.'''
829 '''Return a matcher that will efficiently match exactly these files.'''
830 return matchmod.exact(files, badfn=badfn)
830 return matchmod.exact(files, badfn=badfn)
831
831
832 def parsefollowlinespattern(repo, rev, pat, msg):
832 def parsefollowlinespattern(repo, rev, pat, msg):
833 """Return a file name from `pat` pattern suitable for usage in followlines
833 """Return a file name from `pat` pattern suitable for usage in followlines
834 logic.
834 logic.
835 """
835 """
836 if not matchmod.patkind(pat):
836 if not matchmod.patkind(pat):
837 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
837 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
838 else:
838 else:
839 ctx = repo[rev]
839 ctx = repo[rev]
840 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
840 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
841 files = [f for f in ctx if m(f)]
841 files = [f for f in ctx if m(f)]
842 if len(files) != 1:
842 if len(files) != 1:
843 raise error.ParseError(msg)
843 raise error.ParseError(msg)
844 return files[0]
844 return files[0]
845
845
846 def getorigvfs(ui, repo):
846 def getorigvfs(ui, repo):
847 """return a vfs suitable to save 'orig' file
847 """return a vfs suitable to save 'orig' file
848
848
849 return None if no special directory is configured"""
849 return None if no special directory is configured"""
850 origbackuppath = ui.config('ui', 'origbackuppath')
850 origbackuppath = ui.config('ui', 'origbackuppath')
851 if not origbackuppath:
851 if not origbackuppath:
852 return None
852 return None
853 return vfs.vfs(repo.wvfs.join(origbackuppath))
853 return vfs.vfs(repo.wvfs.join(origbackuppath))
854
854
855 def backuppath(ui, repo, filepath):
855 def backuppath(ui, repo, filepath):
856 '''customize where working copy backup files (.orig files) are created
856 '''customize where working copy backup files (.orig files) are created
857
857
858 Fetch user defined path from config file: [ui] origbackuppath = <path>
858 Fetch user defined path from config file: [ui] origbackuppath = <path>
859 Fall back to default (filepath with .orig suffix) if not specified
859 Fall back to default (filepath with .orig suffix) if not specified
860
860
861 filepath is repo-relative
861 filepath is repo-relative
862
862
863 Returns an absolute path
863 Returns an absolute path
864 '''
864 '''
865 origvfs = getorigvfs(ui, repo)
865 origvfs = getorigvfs(ui, repo)
866 if origvfs is None:
866 if origvfs is None:
867 return repo.wjoin(filepath + ".orig")
867 return repo.wjoin(filepath + ".orig")
868
868
869 origbackupdir = origvfs.dirname(filepath)
869 origbackupdir = origvfs.dirname(filepath)
870 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
870 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
871 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
871 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
872
872
873 # Remove any files that conflict with the backup file's path
873 # Remove any files that conflict with the backup file's path
874 for f in reversed(list(util.finddirs(filepath))):
874 for f in reversed(list(util.finddirs(filepath))):
875 if origvfs.isfileorlink(f):
875 if origvfs.isfileorlink(f):
876 ui.note(_('removing conflicting file: %s\n')
876 ui.note(_('removing conflicting file: %s\n')
877 % origvfs.join(f))
877 % origvfs.join(f))
878 origvfs.unlink(f)
878 origvfs.unlink(f)
879 break
879 break
880
880
881 origvfs.makedirs(origbackupdir)
881 origvfs.makedirs(origbackupdir)
882
882
883 if origvfs.isdir(filepath) and not origvfs.islink(filepath):
883 if origvfs.isdir(filepath) and not origvfs.islink(filepath):
884 ui.note(_('removing conflicting directory: %s\n')
884 ui.note(_('removing conflicting directory: %s\n')
885 % origvfs.join(filepath))
885 % origvfs.join(filepath))
886 origvfs.rmtree(filepath, forcibly=True)
886 origvfs.rmtree(filepath, forcibly=True)
887
887
888 return origvfs.join(filepath)
888 return origvfs.join(filepath)
889
889
890 class _containsnode(object):
890 class _containsnode(object):
891 """proxy __contains__(node) to container.__contains__ which accepts revs"""
891 """proxy __contains__(node) to container.__contains__ which accepts revs"""
892
892
893 def __init__(self, repo, revcontainer):
893 def __init__(self, repo, revcontainer):
894 self._torev = repo.changelog.rev
894 self._torev = repo.changelog.rev
895 self._revcontains = revcontainer.__contains__
895 self._revcontains = revcontainer.__contains__
896
896
897 def __contains__(self, node):
897 def __contains__(self, node):
898 return self._revcontains(self._torev(node))
898 return self._revcontains(self._torev(node))
899
899
900 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
900 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
901 fixphase=False, targetphase=None, backup=True):
901 fixphase=False, targetphase=None, backup=True):
902 """do common cleanups when old nodes are replaced by new nodes
902 """do common cleanups when old nodes are replaced by new nodes
903
903
904 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
904 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
905 (we might also want to move working directory parent in the future)
905 (we might also want to move working directory parent in the future)
906
906
907 By default, bookmark moves are calculated automatically from 'replacements',
907 By default, bookmark moves are calculated automatically from 'replacements',
908 but 'moves' can be used to override that. Also, 'moves' may include
908 but 'moves' can be used to override that. Also, 'moves' may include
909 additional bookmark moves that should not have associated obsmarkers.
909 additional bookmark moves that should not have associated obsmarkers.
910
910
911 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
911 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
912 have replacements. operation is a string, like "rebase".
912 have replacements. operation is a string, like "rebase".
913
913
914 metadata is dictionary containing metadata to be stored in obsmarker if
914 metadata is dictionary containing metadata to be stored in obsmarker if
915 obsolescence is enabled.
915 obsolescence is enabled.
916 """
916 """
917 assert fixphase or targetphase is None
917 assert fixphase or targetphase is None
918 if not replacements and not moves:
918 if not replacements and not moves:
919 return
919 return
920
920
921 # translate mapping's other forms
921 # translate mapping's other forms
922 if not util.safehasattr(replacements, 'items'):
922 if not util.safehasattr(replacements, 'items'):
923 replacements = {(n,): () for n in replacements}
923 replacements = {(n,): () for n in replacements}
924 else:
924 else:
925 # upgrading non tuple "source" to tuple ones for BC
925 # upgrading non tuple "source" to tuple ones for BC
926 repls = {}
926 repls = {}
927 for key, value in replacements.items():
927 for key, value in replacements.items():
928 if not isinstance(key, tuple):
928 if not isinstance(key, tuple):
929 key = (key,)
929 key = (key,)
930 repls[key] = value
930 repls[key] = value
931 replacements = repls
931 replacements = repls
932
932
933 # Unfiltered repo is needed since nodes in replacements might be hidden.
933 # Unfiltered repo is needed since nodes in replacements might be hidden.
934 unfi = repo.unfiltered()
934 unfi = repo.unfiltered()
935
935
936 # Calculate bookmark movements
936 # Calculate bookmark movements
937 if moves is None:
937 if moves is None:
938 moves = {}
938 moves = {}
939 for oldnodes, newnodes in replacements.items():
939 for oldnodes, newnodes in replacements.items():
940 for oldnode in oldnodes:
940 for oldnode in oldnodes:
941 if oldnode in moves:
941 if oldnode in moves:
942 continue
942 continue
943 if len(newnodes) > 1:
943 if len(newnodes) > 1:
944 # usually a split, take the one with biggest rev number
944 # usually a split, take the one with biggest rev number
945 newnode = next(unfi.set('max(%ln)', newnodes)).node()
945 newnode = next(unfi.set('max(%ln)', newnodes)).node()
946 elif len(newnodes) == 0:
946 elif len(newnodes) == 0:
947 # move bookmark backwards
947 # move bookmark backwards
948 allreplaced = []
948 allreplaced = []
949 for rep in replacements:
949 for rep in replacements:
950 allreplaced.extend(rep)
950 allreplaced.extend(rep)
951 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
951 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
952 allreplaced))
952 allreplaced))
953 if roots:
953 if roots:
954 newnode = roots[0].node()
954 newnode = roots[0].node()
955 else:
955 else:
956 newnode = nullid
956 newnode = nullid
957 else:
957 else:
958 newnode = newnodes[0]
958 newnode = newnodes[0]
959 moves[oldnode] = newnode
959 moves[oldnode] = newnode
960
960
961 allnewnodes = [n for ns in replacements.values() for n in ns]
961 allnewnodes = [n for ns in replacements.values() for n in ns]
962 toretract = {}
962 toretract = {}
963 toadvance = {}
963 toadvance = {}
964 if fixphase:
964 if fixphase:
965 precursors = {}
965 precursors = {}
966 for oldnodes, newnodes in replacements.items():
966 for oldnodes, newnodes in replacements.items():
967 for oldnode in oldnodes:
967 for oldnode in oldnodes:
968 for newnode in newnodes:
968 for newnode in newnodes:
969 precursors.setdefault(newnode, []).append(oldnode)
969 precursors.setdefault(newnode, []).append(oldnode)
970
970
971 allnewnodes.sort(key=lambda n: unfi[n].rev())
971 allnewnodes.sort(key=lambda n: unfi[n].rev())
972 newphases = {}
972 newphases = {}
973 def phase(ctx):
973 def phase(ctx):
974 return newphases.get(ctx.node(), ctx.phase())
974 return newphases.get(ctx.node(), ctx.phase())
975 for newnode in allnewnodes:
975 for newnode in allnewnodes:
976 ctx = unfi[newnode]
976 ctx = unfi[newnode]
977 parentphase = max(phase(p) for p in ctx.parents())
977 parentphase = max(phase(p) for p in ctx.parents())
978 if targetphase is None:
978 if targetphase is None:
979 oldphase = max(unfi[oldnode].phase()
979 oldphase = max(unfi[oldnode].phase()
980 for oldnode in precursors[newnode])
980 for oldnode in precursors[newnode])
981 newphase = max(oldphase, parentphase)
981 newphase = max(oldphase, parentphase)
982 else:
982 else:
983 newphase = max(targetphase, parentphase)
983 newphase = max(targetphase, parentphase)
984 newphases[newnode] = newphase
984 newphases[newnode] = newphase
985 if newphase > ctx.phase():
985 if newphase > ctx.phase():
986 toretract.setdefault(newphase, []).append(newnode)
986 toretract.setdefault(newphase, []).append(newnode)
987 elif newphase < ctx.phase():
987 elif newphase < ctx.phase():
988 toadvance.setdefault(newphase, []).append(newnode)
988 toadvance.setdefault(newphase, []).append(newnode)
989
989
990 with repo.transaction('cleanup') as tr:
990 with repo.transaction('cleanup') as tr:
991 # Move bookmarks
991 # Move bookmarks
992 bmarks = repo._bookmarks
992 bmarks = repo._bookmarks
993 bmarkchanges = []
993 bmarkchanges = []
994 for oldnode, newnode in moves.items():
994 for oldnode, newnode in moves.items():
995 oldbmarks = repo.nodebookmarks(oldnode)
995 oldbmarks = repo.nodebookmarks(oldnode)
996 if not oldbmarks:
996 if not oldbmarks:
997 continue
997 continue
998 from . import bookmarks # avoid import cycle
998 from . import bookmarks # avoid import cycle
999 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
999 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
1000 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
1000 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
1001 hex(oldnode), hex(newnode)))
1001 hex(oldnode), hex(newnode)))
1002 # Delete divergent bookmarks being parents of related newnodes
1002 # Delete divergent bookmarks being parents of related newnodes
1003 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
1003 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
1004 allnewnodes, newnode, oldnode)
1004 allnewnodes, newnode, oldnode)
1005 deletenodes = _containsnode(repo, deleterevs)
1005 deletenodes = _containsnode(repo, deleterevs)
1006 for name in oldbmarks:
1006 for name in oldbmarks:
1007 bmarkchanges.append((name, newnode))
1007 bmarkchanges.append((name, newnode))
1008 for b in bookmarks.divergent2delete(repo, deletenodes, name):
1008 for b in bookmarks.divergent2delete(repo, deletenodes, name):
1009 bmarkchanges.append((b, None))
1009 bmarkchanges.append((b, None))
1010
1010
1011 if bmarkchanges:
1011 if bmarkchanges:
1012 bmarks.applychanges(repo, tr, bmarkchanges)
1012 bmarks.applychanges(repo, tr, bmarkchanges)
1013
1013
1014 for phase, nodes in toretract.items():
1014 for phase, nodes in toretract.items():
1015 phases.retractboundary(repo, tr, phase, nodes)
1015 phases.retractboundary(repo, tr, phase, nodes)
1016 for phase, nodes in toadvance.items():
1016 for phase, nodes in toadvance.items():
1017 phases.advanceboundary(repo, tr, phase, nodes)
1017 phases.advanceboundary(repo, tr, phase, nodes)
1018
1018
1019 # Obsolete or strip nodes
1019 # Obsolete or strip nodes
1020 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1020 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1021 # If a node is already obsoleted, and we want to obsolete it
1021 # If a node is already obsoleted, and we want to obsolete it
1022 # without a successor, skip that obssolete request since it's
1022 # without a successor, skip that obssolete request since it's
1023 # unnecessary. That's the "if s or not isobs(n)" check below.
1023 # unnecessary. That's the "if s or not isobs(n)" check below.
1024 # Also sort the node in topology order, that might be useful for
1024 # Also sort the node in topology order, that might be useful for
1025 # some obsstore logic.
1025 # some obsstore logic.
1026 # NOTE: the sorting might belong to createmarkers.
1026 # NOTE: the sorting might belong to createmarkers.
1027 torev = unfi.changelog.rev
1027 torev = unfi.changelog.rev
1028 sortfunc = lambda ns: torev(ns[0][0])
1028 sortfunc = lambda ns: torev(ns[0][0])
1029 rels = []
1029 rels = []
1030 for ns, s in sorted(replacements.items(), key=sortfunc):
1030 for ns, s in sorted(replacements.items(), key=sortfunc):
1031 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
1031 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
1032 rels.append(rel)
1032 rels.append(rel)
1033 if rels:
1033 if rels:
1034 obsolete.createmarkers(repo, rels, operation=operation,
1034 obsolete.createmarkers(repo, rels, operation=operation,
1035 metadata=metadata)
1035 metadata=metadata)
1036 else:
1036 else:
1037 from . import repair # avoid import cycle
1037 from . import repair # avoid import cycle
1038 tostrip = list(n for ns in replacements for n in ns)
1038 tostrip = list(n for ns in replacements for n in ns)
1039 if tostrip:
1039 if tostrip:
1040 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1040 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1041 backup=backup)
1041 backup=backup)
1042
1042
1043 def addremove(repo, matcher, prefix, uipathfn, opts=None):
1043 def addremove(repo, matcher, prefix, uipathfn, opts=None):
1044 if opts is None:
1044 if opts is None:
1045 opts = {}
1045 opts = {}
1046 m = matcher
1046 m = matcher
1047 dry_run = opts.get('dry_run')
1047 dry_run = opts.get('dry_run')
1048 try:
1048 try:
1049 similarity = float(opts.get('similarity') or 0)
1049 similarity = float(opts.get('similarity') or 0)
1050 except ValueError:
1050 except ValueError:
1051 raise error.Abort(_('similarity must be a number'))
1051 raise error.Abort(_('similarity must be a number'))
1052 if similarity < 0 or similarity > 100:
1052 if similarity < 0 or similarity > 100:
1053 raise error.Abort(_('similarity must be between 0 and 100'))
1053 raise error.Abort(_('similarity must be between 0 and 100'))
1054 similarity /= 100.0
1054 similarity /= 100.0
1055
1055
1056 ret = 0
1056 ret = 0
1057
1057
1058 wctx = repo[None]
1058 wctx = repo[None]
1059 for subpath in sorted(wctx.substate):
1059 for subpath in sorted(wctx.substate):
1060 submatch = matchmod.subdirmatcher(subpath, m)
1060 submatch = matchmod.subdirmatcher(subpath, m)
1061 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1061 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1062 sub = wctx.sub(subpath)
1062 sub = wctx.sub(subpath)
1063 subprefix = repo.wvfs.reljoin(prefix, subpath)
1063 subprefix = repo.wvfs.reljoin(prefix, subpath)
1064 subuipathfn = subdiruipathfn(subpath, uipathfn)
1064 subuipathfn = subdiruipathfn(subpath, uipathfn)
1065 try:
1065 try:
1066 if sub.addremove(submatch, subprefix, subuipathfn, opts):
1066 if sub.addremove(submatch, subprefix, subuipathfn, opts):
1067 ret = 1
1067 ret = 1
1068 except error.LookupError:
1068 except error.LookupError:
1069 repo.ui.status(_("skipping missing subrepository: %s\n")
1069 repo.ui.status(_("skipping missing subrepository: %s\n")
1070 % uipathfn(subpath))
1070 % uipathfn(subpath))
1071
1071
1072 rejected = []
1072 rejected = []
1073 def badfn(f, msg):
1073 def badfn(f, msg):
1074 if f in m.files():
1074 if f in m.files():
1075 m.bad(f, msg)
1075 m.bad(f, msg)
1076 rejected.append(f)
1076 rejected.append(f)
1077
1077
1078 badmatch = matchmod.badmatch(m, badfn)
1078 badmatch = matchmod.badmatch(m, badfn)
1079 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1079 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1080 badmatch)
1080 badmatch)
1081
1081
1082 unknownset = set(unknown + forgotten)
1082 unknownset = set(unknown + forgotten)
1083 toprint = unknownset.copy()
1083 toprint = unknownset.copy()
1084 toprint.update(deleted)
1084 toprint.update(deleted)
1085 for abs in sorted(toprint):
1085 for abs in sorted(toprint):
1086 if repo.ui.verbose or not m.exact(abs):
1086 if repo.ui.verbose or not m.exact(abs):
1087 if abs in unknownset:
1087 if abs in unknownset:
1088 status = _('adding %s\n') % uipathfn(abs)
1088 status = _('adding %s\n') % uipathfn(abs)
1089 label = 'ui.addremove.added'
1089 label = 'ui.addremove.added'
1090 else:
1090 else:
1091 status = _('removing %s\n') % uipathfn(abs)
1091 status = _('removing %s\n') % uipathfn(abs)
1092 label = 'ui.addremove.removed'
1092 label = 'ui.addremove.removed'
1093 repo.ui.status(status, label=label)
1093 repo.ui.status(status, label=label)
1094
1094
1095 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1095 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1096 similarity, uipathfn)
1096 similarity, uipathfn)
1097
1097
1098 if not dry_run:
1098 if not dry_run:
1099 _markchanges(repo, unknown + forgotten, deleted, renames)
1099 _markchanges(repo, unknown + forgotten, deleted, renames)
1100
1100
1101 for f in rejected:
1101 for f in rejected:
1102 if f in m.files():
1102 if f in m.files():
1103 return 1
1103 return 1
1104 return ret
1104 return ret
1105
1105
1106 def marktouched(repo, files, similarity=0.0):
1106 def marktouched(repo, files, similarity=0.0):
1107 '''Assert that files have somehow been operated upon. files are relative to
1107 '''Assert that files have somehow been operated upon. files are relative to
1108 the repo root.'''
1108 the repo root.'''
1109 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1109 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1110 rejected = []
1110 rejected = []
1111
1111
1112 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1112 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1113
1113
1114 if repo.ui.verbose:
1114 if repo.ui.verbose:
1115 unknownset = set(unknown + forgotten)
1115 unknownset = set(unknown + forgotten)
1116 toprint = unknownset.copy()
1116 toprint = unknownset.copy()
1117 toprint.update(deleted)
1117 toprint.update(deleted)
1118 for abs in sorted(toprint):
1118 for abs in sorted(toprint):
1119 if abs in unknownset:
1119 if abs in unknownset:
1120 status = _('adding %s\n') % abs
1120 status = _('adding %s\n') % abs
1121 else:
1121 else:
1122 status = _('removing %s\n') % abs
1122 status = _('removing %s\n') % abs
1123 repo.ui.status(status)
1123 repo.ui.status(status)
1124
1124
1125 # TODO: We should probably have the caller pass in uipathfn and apply it to
1125 # TODO: We should probably have the caller pass in uipathfn and apply it to
1126 # the messages above too. legacyrelativevalue=True is consistent with how
1126 # the messages above too. legacyrelativevalue=True is consistent with how
1127 # it used to work.
1127 # it used to work.
1128 uipathfn = getuipathfn(repo, legacyrelativevalue=True)
1128 uipathfn = getuipathfn(repo, legacyrelativevalue=True)
1129 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1129 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1130 similarity, uipathfn)
1130 similarity, uipathfn)
1131
1131
1132 _markchanges(repo, unknown + forgotten, deleted, renames)
1132 _markchanges(repo, unknown + forgotten, deleted, renames)
1133
1133
1134 for f in rejected:
1134 for f in rejected:
1135 if f in m.files():
1135 if f in m.files():
1136 return 1
1136 return 1
1137 return 0
1137 return 0
1138
1138
1139 def _interestingfiles(repo, matcher):
1139 def _interestingfiles(repo, matcher):
1140 '''Walk dirstate with matcher, looking for files that addremove would care
1140 '''Walk dirstate with matcher, looking for files that addremove would care
1141 about.
1141 about.
1142
1142
1143 This is different from dirstate.status because it doesn't care about
1143 This is different from dirstate.status because it doesn't care about
1144 whether files are modified or clean.'''
1144 whether files are modified or clean.'''
1145 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1145 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1146 audit_path = pathutil.pathauditor(repo.root, cached=True)
1146 audit_path = pathutil.pathauditor(repo.root, cached=True)
1147
1147
1148 ctx = repo[None]
1148 ctx = repo[None]
1149 dirstate = repo.dirstate
1149 dirstate = repo.dirstate
1150 matcher = repo.narrowmatch(matcher, includeexact=True)
1150 matcher = repo.narrowmatch(matcher, includeexact=True)
1151 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1151 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1152 unknown=True, ignored=False, full=False)
1152 unknown=True, ignored=False, full=False)
1153 for abs, st in walkresults.iteritems():
1153 for abs, st in walkresults.iteritems():
1154 dstate = dirstate[abs]
1154 dstate = dirstate[abs]
1155 if dstate == '?' and audit_path.check(abs):
1155 if dstate == '?' and audit_path.check(abs):
1156 unknown.append(abs)
1156 unknown.append(abs)
1157 elif dstate != 'r' and not st:
1157 elif dstate != 'r' and not st:
1158 deleted.append(abs)
1158 deleted.append(abs)
1159 elif dstate == 'r' and st:
1159 elif dstate == 'r' and st:
1160 forgotten.append(abs)
1160 forgotten.append(abs)
1161 # for finding renames
1161 # for finding renames
1162 elif dstate == 'r' and not st:
1162 elif dstate == 'r' and not st:
1163 removed.append(abs)
1163 removed.append(abs)
1164 elif dstate == 'a':
1164 elif dstate == 'a':
1165 added.append(abs)
1165 added.append(abs)
1166
1166
1167 return added, unknown, deleted, removed, forgotten
1167 return added, unknown, deleted, removed, forgotten
1168
1168
1169 def _findrenames(repo, matcher, added, removed, similarity, uipathfn):
1169 def _findrenames(repo, matcher, added, removed, similarity, uipathfn):
1170 '''Find renames from removed files to added ones.'''
1170 '''Find renames from removed files to added ones.'''
1171 renames = {}
1171 renames = {}
1172 if similarity > 0:
1172 if similarity > 0:
1173 for old, new, score in similar.findrenames(repo, added, removed,
1173 for old, new, score in similar.findrenames(repo, added, removed,
1174 similarity):
1174 similarity):
1175 if (repo.ui.verbose or not matcher.exact(old)
1175 if (repo.ui.verbose or not matcher.exact(old)
1176 or not matcher.exact(new)):
1176 or not matcher.exact(new)):
1177 repo.ui.status(_('recording removal of %s as rename to %s '
1177 repo.ui.status(_('recording removal of %s as rename to %s '
1178 '(%d%% similar)\n') %
1178 '(%d%% similar)\n') %
1179 (uipathfn(old), uipathfn(new),
1179 (uipathfn(old), uipathfn(new),
1180 score * 100))
1180 score * 100))
1181 renames[new] = old
1181 renames[new] = old
1182 return renames
1182 return renames
1183
1183
1184 def _markchanges(repo, unknown, deleted, renames):
1184 def _markchanges(repo, unknown, deleted, renames):
1185 '''Marks the files in unknown as added, the files in deleted as removed,
1185 '''Marks the files in unknown as added, the files in deleted as removed,
1186 and the files in renames as copied.'''
1186 and the files in renames as copied.'''
1187 wctx = repo[None]
1187 wctx = repo[None]
1188 with repo.wlock():
1188 with repo.wlock():
1189 wctx.forget(deleted)
1189 wctx.forget(deleted)
1190 wctx.add(unknown)
1190 wctx.add(unknown)
1191 for new, old in renames.iteritems():
1191 for new, old in renames.iteritems():
1192 wctx.copy(old, new)
1192 wctx.copy(old, new)
1193
1193
1194 def getrenamedfn(repo, endrev=None):
1195 rcache = {}
1196 if endrev is None:
1197 endrev = len(repo)
1198
1199 def getrenamed(fn, rev):
1200 '''looks up all renames for a file (up to endrev) the first
1201 time the file is given. It indexes on the changerev and only
1202 parses the manifest if linkrev != changerev.
1203 Returns rename info for fn at changerev rev.'''
1204 if fn not in rcache:
1205 rcache[fn] = {}
1206 fl = repo.file(fn)
1207 for i in fl:
1208 lr = fl.linkrev(i)
1209 renamed = fl.renamed(fl.node(i))
1210 rcache[fn][lr] = renamed and renamed[0]
1211 if lr >= endrev:
1212 break
1213 if rev in rcache[fn]:
1214 return rcache[fn][rev]
1215
1216 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1217 # filectx logic.
1218 try:
1219 return repo[rev][fn].copysource()
1220 except error.LookupError:
1221 return None
1222
1223 return getrenamed
1224
1194 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1225 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1195 """Update the dirstate to reflect the intent of copying src to dst. For
1226 """Update the dirstate to reflect the intent of copying src to dst. For
1196 different reasons it might not end with dst being marked as copied from src.
1227 different reasons it might not end with dst being marked as copied from src.
1197 """
1228 """
1198 origsrc = repo.dirstate.copied(src) or src
1229 origsrc = repo.dirstate.copied(src) or src
1199 if dst == origsrc: # copying back a copy?
1230 if dst == origsrc: # copying back a copy?
1200 if repo.dirstate[dst] not in 'mn' and not dryrun:
1231 if repo.dirstate[dst] not in 'mn' and not dryrun:
1201 repo.dirstate.normallookup(dst)
1232 repo.dirstate.normallookup(dst)
1202 else:
1233 else:
1203 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1234 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1204 if not ui.quiet:
1235 if not ui.quiet:
1205 ui.warn(_("%s has not been committed yet, so no copy "
1236 ui.warn(_("%s has not been committed yet, so no copy "
1206 "data will be stored for %s.\n")
1237 "data will be stored for %s.\n")
1207 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1238 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1208 if repo.dirstate[dst] in '?r' and not dryrun:
1239 if repo.dirstate[dst] in '?r' and not dryrun:
1209 wctx.add([dst])
1240 wctx.add([dst])
1210 elif not dryrun:
1241 elif not dryrun:
1211 wctx.copy(origsrc, dst)
1242 wctx.copy(origsrc, dst)
1212
1243
1213 def writerequires(opener, requirements):
1244 def writerequires(opener, requirements):
1214 with opener('requires', 'w', atomictemp=True) as fp:
1245 with opener('requires', 'w', atomictemp=True) as fp:
1215 for r in sorted(requirements):
1246 for r in sorted(requirements):
1216 fp.write("%s\n" % r)
1247 fp.write("%s\n" % r)
1217
1248
1218 class filecachesubentry(object):
1249 class filecachesubentry(object):
1219 def __init__(self, path, stat):
1250 def __init__(self, path, stat):
1220 self.path = path
1251 self.path = path
1221 self.cachestat = None
1252 self.cachestat = None
1222 self._cacheable = None
1253 self._cacheable = None
1223
1254
1224 if stat:
1255 if stat:
1225 self.cachestat = filecachesubentry.stat(self.path)
1256 self.cachestat = filecachesubentry.stat(self.path)
1226
1257
1227 if self.cachestat:
1258 if self.cachestat:
1228 self._cacheable = self.cachestat.cacheable()
1259 self._cacheable = self.cachestat.cacheable()
1229 else:
1260 else:
1230 # None means we don't know yet
1261 # None means we don't know yet
1231 self._cacheable = None
1262 self._cacheable = None
1232
1263
1233 def refresh(self):
1264 def refresh(self):
1234 if self.cacheable():
1265 if self.cacheable():
1235 self.cachestat = filecachesubentry.stat(self.path)
1266 self.cachestat = filecachesubentry.stat(self.path)
1236
1267
1237 def cacheable(self):
1268 def cacheable(self):
1238 if self._cacheable is not None:
1269 if self._cacheable is not None:
1239 return self._cacheable
1270 return self._cacheable
1240
1271
1241 # we don't know yet, assume it is for now
1272 # we don't know yet, assume it is for now
1242 return True
1273 return True
1243
1274
1244 def changed(self):
1275 def changed(self):
1245 # no point in going further if we can't cache it
1276 # no point in going further if we can't cache it
1246 if not self.cacheable():
1277 if not self.cacheable():
1247 return True
1278 return True
1248
1279
1249 newstat = filecachesubentry.stat(self.path)
1280 newstat = filecachesubentry.stat(self.path)
1250
1281
1251 # we may not know if it's cacheable yet, check again now
1282 # we may not know if it's cacheable yet, check again now
1252 if newstat and self._cacheable is None:
1283 if newstat and self._cacheable is None:
1253 self._cacheable = newstat.cacheable()
1284 self._cacheable = newstat.cacheable()
1254
1285
1255 # check again
1286 # check again
1256 if not self._cacheable:
1287 if not self._cacheable:
1257 return True
1288 return True
1258
1289
1259 if self.cachestat != newstat:
1290 if self.cachestat != newstat:
1260 self.cachestat = newstat
1291 self.cachestat = newstat
1261 return True
1292 return True
1262 else:
1293 else:
1263 return False
1294 return False
1264
1295
1265 @staticmethod
1296 @staticmethod
1266 def stat(path):
1297 def stat(path):
1267 try:
1298 try:
1268 return util.cachestat(path)
1299 return util.cachestat(path)
1269 except OSError as e:
1300 except OSError as e:
1270 if e.errno != errno.ENOENT:
1301 if e.errno != errno.ENOENT:
1271 raise
1302 raise
1272
1303
1273 class filecacheentry(object):
1304 class filecacheentry(object):
1274 def __init__(self, paths, stat=True):
1305 def __init__(self, paths, stat=True):
1275 self._entries = []
1306 self._entries = []
1276 for path in paths:
1307 for path in paths:
1277 self._entries.append(filecachesubentry(path, stat))
1308 self._entries.append(filecachesubentry(path, stat))
1278
1309
1279 def changed(self):
1310 def changed(self):
1280 '''true if any entry has changed'''
1311 '''true if any entry has changed'''
1281 for entry in self._entries:
1312 for entry in self._entries:
1282 if entry.changed():
1313 if entry.changed():
1283 return True
1314 return True
1284 return False
1315 return False
1285
1316
1286 def refresh(self):
1317 def refresh(self):
1287 for entry in self._entries:
1318 for entry in self._entries:
1288 entry.refresh()
1319 entry.refresh()
1289
1320
1290 class filecache(object):
1321 class filecache(object):
1291 """A property like decorator that tracks files under .hg/ for updates.
1322 """A property like decorator that tracks files under .hg/ for updates.
1292
1323
1293 On first access, the files defined as arguments are stat()ed and the
1324 On first access, the files defined as arguments are stat()ed and the
1294 results cached. The decorated function is called. The results are stashed
1325 results cached. The decorated function is called. The results are stashed
1295 away in a ``_filecache`` dict on the object whose method is decorated.
1326 away in a ``_filecache`` dict on the object whose method is decorated.
1296
1327
1297 On subsequent access, the cached result is used as it is set to the
1328 On subsequent access, the cached result is used as it is set to the
1298 instance dictionary.
1329 instance dictionary.
1299
1330
1300 On external property set/delete operations, the caller must update the
1331 On external property set/delete operations, the caller must update the
1301 corresponding _filecache entry appropriately. Use __class__.<attr>.set()
1332 corresponding _filecache entry appropriately. Use __class__.<attr>.set()
1302 instead of directly setting <attr>.
1333 instead of directly setting <attr>.
1303
1334
1304 When using the property API, the cached data is always used if available.
1335 When using the property API, the cached data is always used if available.
1305 No stat() is performed to check if the file has changed.
1336 No stat() is performed to check if the file has changed.
1306
1337
1307 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1338 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1308 can populate an entry before the property's getter is called. In this case,
1339 can populate an entry before the property's getter is called. In this case,
1309 entries in ``_filecache`` will be used during property operations,
1340 entries in ``_filecache`` will be used during property operations,
1310 if available. If the underlying file changes, it is up to external callers
1341 if available. If the underlying file changes, it is up to external callers
1311 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1342 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1312 method result as well as possibly calling ``del obj._filecache[attr]`` to
1343 method result as well as possibly calling ``del obj._filecache[attr]`` to
1313 remove the ``filecacheentry``.
1344 remove the ``filecacheentry``.
1314 """
1345 """
1315
1346
1316 def __init__(self, *paths):
1347 def __init__(self, *paths):
1317 self.paths = paths
1348 self.paths = paths
1318
1349
1319 def join(self, obj, fname):
1350 def join(self, obj, fname):
1320 """Used to compute the runtime path of a cached file.
1351 """Used to compute the runtime path of a cached file.
1321
1352
1322 Users should subclass filecache and provide their own version of this
1353 Users should subclass filecache and provide their own version of this
1323 function to call the appropriate join function on 'obj' (an instance
1354 function to call the appropriate join function on 'obj' (an instance
1324 of the class that its member function was decorated).
1355 of the class that its member function was decorated).
1325 """
1356 """
1326 raise NotImplementedError
1357 raise NotImplementedError
1327
1358
1328 def __call__(self, func):
1359 def __call__(self, func):
1329 self.func = func
1360 self.func = func
1330 self.sname = func.__name__
1361 self.sname = func.__name__
1331 self.name = pycompat.sysbytes(self.sname)
1362 self.name = pycompat.sysbytes(self.sname)
1332 return self
1363 return self
1333
1364
1334 def __get__(self, obj, type=None):
1365 def __get__(self, obj, type=None):
1335 # if accessed on the class, return the descriptor itself.
1366 # if accessed on the class, return the descriptor itself.
1336 if obj is None:
1367 if obj is None:
1337 return self
1368 return self
1338
1369
1339 assert self.sname not in obj.__dict__
1370 assert self.sname not in obj.__dict__
1340
1371
1341 entry = obj._filecache.get(self.name)
1372 entry = obj._filecache.get(self.name)
1342
1373
1343 if entry:
1374 if entry:
1344 if entry.changed():
1375 if entry.changed():
1345 entry.obj = self.func(obj)
1376 entry.obj = self.func(obj)
1346 else:
1377 else:
1347 paths = [self.join(obj, path) for path in self.paths]
1378 paths = [self.join(obj, path) for path in self.paths]
1348
1379
1349 # We stat -before- creating the object so our cache doesn't lie if
1380 # We stat -before- creating the object so our cache doesn't lie if
1350 # a writer modified between the time we read and stat
1381 # a writer modified between the time we read and stat
1351 entry = filecacheentry(paths, True)
1382 entry = filecacheentry(paths, True)
1352 entry.obj = self.func(obj)
1383 entry.obj = self.func(obj)
1353
1384
1354 obj._filecache[self.name] = entry
1385 obj._filecache[self.name] = entry
1355
1386
1356 obj.__dict__[self.sname] = entry.obj
1387 obj.__dict__[self.sname] = entry.obj
1357 return entry.obj
1388 return entry.obj
1358
1389
1359 # don't implement __set__(), which would make __dict__ lookup as slow as
1390 # don't implement __set__(), which would make __dict__ lookup as slow as
1360 # function call.
1391 # function call.
1361
1392
1362 def set(self, obj, value):
1393 def set(self, obj, value):
1363 if self.name not in obj._filecache:
1394 if self.name not in obj._filecache:
1364 # we add an entry for the missing value because X in __dict__
1395 # we add an entry for the missing value because X in __dict__
1365 # implies X in _filecache
1396 # implies X in _filecache
1366 paths = [self.join(obj, path) for path in self.paths]
1397 paths = [self.join(obj, path) for path in self.paths]
1367 ce = filecacheentry(paths, False)
1398 ce = filecacheentry(paths, False)
1368 obj._filecache[self.name] = ce
1399 obj._filecache[self.name] = ce
1369 else:
1400 else:
1370 ce = obj._filecache[self.name]
1401 ce = obj._filecache[self.name]
1371
1402
1372 ce.obj = value # update cached copy
1403 ce.obj = value # update cached copy
1373 obj.__dict__[self.sname] = value # update copy returned by obj.x
1404 obj.__dict__[self.sname] = value # update copy returned by obj.x
1374
1405
1375 def extdatasource(repo, source):
1406 def extdatasource(repo, source):
1376 """Gather a map of rev -> value dict from the specified source
1407 """Gather a map of rev -> value dict from the specified source
1377
1408
1378 A source spec is treated as a URL, with a special case shell: type
1409 A source spec is treated as a URL, with a special case shell: type
1379 for parsing the output from a shell command.
1410 for parsing the output from a shell command.
1380
1411
1381 The data is parsed as a series of newline-separated records where
1412 The data is parsed as a series of newline-separated records where
1382 each record is a revision specifier optionally followed by a space
1413 each record is a revision specifier optionally followed by a space
1383 and a freeform string value. If the revision is known locally, it
1414 and a freeform string value. If the revision is known locally, it
1384 is converted to a rev, otherwise the record is skipped.
1415 is converted to a rev, otherwise the record is skipped.
1385
1416
1386 Note that both key and value are treated as UTF-8 and converted to
1417 Note that both key and value are treated as UTF-8 and converted to
1387 the local encoding. This allows uniformity between local and
1418 the local encoding. This allows uniformity between local and
1388 remote data sources.
1419 remote data sources.
1389 """
1420 """
1390
1421
1391 spec = repo.ui.config("extdata", source)
1422 spec = repo.ui.config("extdata", source)
1392 if not spec:
1423 if not spec:
1393 raise error.Abort(_("unknown extdata source '%s'") % source)
1424 raise error.Abort(_("unknown extdata source '%s'") % source)
1394
1425
1395 data = {}
1426 data = {}
1396 src = proc = None
1427 src = proc = None
1397 try:
1428 try:
1398 if spec.startswith("shell:"):
1429 if spec.startswith("shell:"):
1399 # external commands should be run relative to the repo root
1430 # external commands should be run relative to the repo root
1400 cmd = spec[6:]
1431 cmd = spec[6:]
1401 proc = subprocess.Popen(procutil.tonativestr(cmd),
1432 proc = subprocess.Popen(procutil.tonativestr(cmd),
1402 shell=True, bufsize=-1,
1433 shell=True, bufsize=-1,
1403 close_fds=procutil.closefds,
1434 close_fds=procutil.closefds,
1404 stdout=subprocess.PIPE,
1435 stdout=subprocess.PIPE,
1405 cwd=procutil.tonativestr(repo.root))
1436 cwd=procutil.tonativestr(repo.root))
1406 src = proc.stdout
1437 src = proc.stdout
1407 else:
1438 else:
1408 # treat as a URL or file
1439 # treat as a URL or file
1409 src = url.open(repo.ui, spec)
1440 src = url.open(repo.ui, spec)
1410 for l in src:
1441 for l in src:
1411 if " " in l:
1442 if " " in l:
1412 k, v = l.strip().split(" ", 1)
1443 k, v = l.strip().split(" ", 1)
1413 else:
1444 else:
1414 k, v = l.strip(), ""
1445 k, v = l.strip(), ""
1415
1446
1416 k = encoding.tolocal(k)
1447 k = encoding.tolocal(k)
1417 try:
1448 try:
1418 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1449 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1419 except (error.LookupError, error.RepoLookupError):
1450 except (error.LookupError, error.RepoLookupError):
1420 pass # we ignore data for nodes that don't exist locally
1451 pass # we ignore data for nodes that don't exist locally
1421 finally:
1452 finally:
1422 if proc:
1453 if proc:
1423 proc.communicate()
1454 proc.communicate()
1424 if src:
1455 if src:
1425 src.close()
1456 src.close()
1426 if proc and proc.returncode != 0:
1457 if proc and proc.returncode != 0:
1427 raise error.Abort(_("extdata command '%s' failed: %s")
1458 raise error.Abort(_("extdata command '%s' failed: %s")
1428 % (cmd, procutil.explainexit(proc.returncode)))
1459 % (cmd, procutil.explainexit(proc.returncode)))
1429
1460
1430 return data
1461 return data
1431
1462
1432 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1463 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1433 if lock is None:
1464 if lock is None:
1434 raise error.LockInheritanceContractViolation(
1465 raise error.LockInheritanceContractViolation(
1435 'lock can only be inherited while held')
1466 'lock can only be inherited while held')
1436 if environ is None:
1467 if environ is None:
1437 environ = {}
1468 environ = {}
1438 with lock.inherit() as locker:
1469 with lock.inherit() as locker:
1439 environ[envvar] = locker
1470 environ[envvar] = locker
1440 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1471 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1441
1472
1442 def wlocksub(repo, cmd, *args, **kwargs):
1473 def wlocksub(repo, cmd, *args, **kwargs):
1443 """run cmd as a subprocess that allows inheriting repo's wlock
1474 """run cmd as a subprocess that allows inheriting repo's wlock
1444
1475
1445 This can only be called while the wlock is held. This takes all the
1476 This can only be called while the wlock is held. This takes all the
1446 arguments that ui.system does, and returns the exit code of the
1477 arguments that ui.system does, and returns the exit code of the
1447 subprocess."""
1478 subprocess."""
1448 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1479 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1449 **kwargs)
1480 **kwargs)
1450
1481
1451 class progress(object):
1482 class progress(object):
1452 def __init__(self, ui, updatebar, topic, unit="", total=None):
1483 def __init__(self, ui, updatebar, topic, unit="", total=None):
1453 self.ui = ui
1484 self.ui = ui
1454 self.pos = 0
1485 self.pos = 0
1455 self.topic = topic
1486 self.topic = topic
1456 self.unit = unit
1487 self.unit = unit
1457 self.total = total
1488 self.total = total
1458 self.debug = ui.configbool('progress', 'debug')
1489 self.debug = ui.configbool('progress', 'debug')
1459 self._updatebar = updatebar
1490 self._updatebar = updatebar
1460
1491
1461 def __enter__(self):
1492 def __enter__(self):
1462 return self
1493 return self
1463
1494
1464 def __exit__(self, exc_type, exc_value, exc_tb):
1495 def __exit__(self, exc_type, exc_value, exc_tb):
1465 self.complete()
1496 self.complete()
1466
1497
1467 def update(self, pos, item="", total=None):
1498 def update(self, pos, item="", total=None):
1468 assert pos is not None
1499 assert pos is not None
1469 if total:
1500 if total:
1470 self.total = total
1501 self.total = total
1471 self.pos = pos
1502 self.pos = pos
1472 self._updatebar(self.topic, self.pos, item, self.unit, self.total)
1503 self._updatebar(self.topic, self.pos, item, self.unit, self.total)
1473 if self.debug:
1504 if self.debug:
1474 self._printdebug(item)
1505 self._printdebug(item)
1475
1506
1476 def increment(self, step=1, item="", total=None):
1507 def increment(self, step=1, item="", total=None):
1477 self.update(self.pos + step, item, total)
1508 self.update(self.pos + step, item, total)
1478
1509
1479 def complete(self):
1510 def complete(self):
1480 self.pos = None
1511 self.pos = None
1481 self.unit = ""
1512 self.unit = ""
1482 self.total = None
1513 self.total = None
1483 self._updatebar(self.topic, self.pos, "", self.unit, self.total)
1514 self._updatebar(self.topic, self.pos, "", self.unit, self.total)
1484
1515
1485 def _printdebug(self, item):
1516 def _printdebug(self, item):
1486 if self.unit:
1517 if self.unit:
1487 unit = ' ' + self.unit
1518 unit = ' ' + self.unit
1488 if item:
1519 if item:
1489 item = ' ' + item
1520 item = ' ' + item
1490
1521
1491 if self.total:
1522 if self.total:
1492 pct = 100.0 * self.pos / self.total
1523 pct = 100.0 * self.pos / self.total
1493 self.ui.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1524 self.ui.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1494 % (self.topic, item, self.pos, self.total, unit, pct))
1525 % (self.topic, item, self.pos, self.total, unit, pct))
1495 else:
1526 else:
1496 self.ui.debug('%s:%s %d%s\n' % (self.topic, item, self.pos, unit))
1527 self.ui.debug('%s:%s %d%s\n' % (self.topic, item, self.pos, unit))
1497
1528
1498 def gdinitconfig(ui):
1529 def gdinitconfig(ui):
1499 """helper function to know if a repo should be created as general delta
1530 """helper function to know if a repo should be created as general delta
1500 """
1531 """
1501 # experimental config: format.generaldelta
1532 # experimental config: format.generaldelta
1502 return (ui.configbool('format', 'generaldelta')
1533 return (ui.configbool('format', 'generaldelta')
1503 or ui.configbool('format', 'usegeneraldelta'))
1534 or ui.configbool('format', 'usegeneraldelta'))
1504
1535
1505 def gddeltaconfig(ui):
1536 def gddeltaconfig(ui):
1506 """helper function to know if incoming delta should be optimised
1537 """helper function to know if incoming delta should be optimised
1507 """
1538 """
1508 # experimental config: format.generaldelta
1539 # experimental config: format.generaldelta
1509 return ui.configbool('format', 'generaldelta')
1540 return ui.configbool('format', 'generaldelta')
1510
1541
1511 class simplekeyvaluefile(object):
1542 class simplekeyvaluefile(object):
1512 """A simple file with key=value lines
1543 """A simple file with key=value lines
1513
1544
1514 Keys must be alphanumerics and start with a letter, values must not
1545 Keys must be alphanumerics and start with a letter, values must not
1515 contain '\n' characters"""
1546 contain '\n' characters"""
1516 firstlinekey = '__firstline'
1547 firstlinekey = '__firstline'
1517
1548
1518 def __init__(self, vfs, path, keys=None):
1549 def __init__(self, vfs, path, keys=None):
1519 self.vfs = vfs
1550 self.vfs = vfs
1520 self.path = path
1551 self.path = path
1521
1552
1522 def read(self, firstlinenonkeyval=False):
1553 def read(self, firstlinenonkeyval=False):
1523 """Read the contents of a simple key-value file
1554 """Read the contents of a simple key-value file
1524
1555
1525 'firstlinenonkeyval' indicates whether the first line of file should
1556 'firstlinenonkeyval' indicates whether the first line of file should
1526 be treated as a key-value pair or reuturned fully under the
1557 be treated as a key-value pair or reuturned fully under the
1527 __firstline key."""
1558 __firstline key."""
1528 lines = self.vfs.readlines(self.path)
1559 lines = self.vfs.readlines(self.path)
1529 d = {}
1560 d = {}
1530 if firstlinenonkeyval:
1561 if firstlinenonkeyval:
1531 if not lines:
1562 if not lines:
1532 e = _("empty simplekeyvalue file")
1563 e = _("empty simplekeyvalue file")
1533 raise error.CorruptedState(e)
1564 raise error.CorruptedState(e)
1534 # we don't want to include '\n' in the __firstline
1565 # we don't want to include '\n' in the __firstline
1535 d[self.firstlinekey] = lines[0][:-1]
1566 d[self.firstlinekey] = lines[0][:-1]
1536 del lines[0]
1567 del lines[0]
1537
1568
1538 try:
1569 try:
1539 # the 'if line.strip()' part prevents us from failing on empty
1570 # the 'if line.strip()' part prevents us from failing on empty
1540 # lines which only contain '\n' therefore are not skipped
1571 # lines which only contain '\n' therefore are not skipped
1541 # by 'if line'
1572 # by 'if line'
1542 updatedict = dict(line[:-1].split('=', 1) for line in lines
1573 updatedict = dict(line[:-1].split('=', 1) for line in lines
1543 if line.strip())
1574 if line.strip())
1544 if self.firstlinekey in updatedict:
1575 if self.firstlinekey in updatedict:
1545 e = _("%r can't be used as a key")
1576 e = _("%r can't be used as a key")
1546 raise error.CorruptedState(e % self.firstlinekey)
1577 raise error.CorruptedState(e % self.firstlinekey)
1547 d.update(updatedict)
1578 d.update(updatedict)
1548 except ValueError as e:
1579 except ValueError as e:
1549 raise error.CorruptedState(str(e))
1580 raise error.CorruptedState(str(e))
1550 return d
1581 return d
1551
1582
1552 def write(self, data, firstline=None):
1583 def write(self, data, firstline=None):
1553 """Write key=>value mapping to a file
1584 """Write key=>value mapping to a file
1554 data is a dict. Keys must be alphanumerical and start with a letter.
1585 data is a dict. Keys must be alphanumerical and start with a letter.
1555 Values must not contain newline characters.
1586 Values must not contain newline characters.
1556
1587
1557 If 'firstline' is not None, it is written to file before
1588 If 'firstline' is not None, it is written to file before
1558 everything else, as it is, not in a key=value form"""
1589 everything else, as it is, not in a key=value form"""
1559 lines = []
1590 lines = []
1560 if firstline is not None:
1591 if firstline is not None:
1561 lines.append('%s\n' % firstline)
1592 lines.append('%s\n' % firstline)
1562
1593
1563 for k, v in data.items():
1594 for k, v in data.items():
1564 if k == self.firstlinekey:
1595 if k == self.firstlinekey:
1565 e = "key name '%s' is reserved" % self.firstlinekey
1596 e = "key name '%s' is reserved" % self.firstlinekey
1566 raise error.ProgrammingError(e)
1597 raise error.ProgrammingError(e)
1567 if not k[0:1].isalpha():
1598 if not k[0:1].isalpha():
1568 e = "keys must start with a letter in a key-value file"
1599 e = "keys must start with a letter in a key-value file"
1569 raise error.ProgrammingError(e)
1600 raise error.ProgrammingError(e)
1570 if not k.isalnum():
1601 if not k.isalnum():
1571 e = "invalid key name in a simple key-value file"
1602 e = "invalid key name in a simple key-value file"
1572 raise error.ProgrammingError(e)
1603 raise error.ProgrammingError(e)
1573 if '\n' in v:
1604 if '\n' in v:
1574 e = "invalid value in a simple key-value file"
1605 e = "invalid value in a simple key-value file"
1575 raise error.ProgrammingError(e)
1606 raise error.ProgrammingError(e)
1576 lines.append("%s=%s\n" % (k, v))
1607 lines.append("%s=%s\n" % (k, v))
1577 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1608 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1578 fp.write(''.join(lines))
1609 fp.write(''.join(lines))
1579
1610
1580 _reportobsoletedsource = [
1611 _reportobsoletedsource = [
1581 'debugobsolete',
1612 'debugobsolete',
1582 'pull',
1613 'pull',
1583 'push',
1614 'push',
1584 'serve',
1615 'serve',
1585 'unbundle',
1616 'unbundle',
1586 ]
1617 ]
1587
1618
1588 _reportnewcssource = [
1619 _reportnewcssource = [
1589 'pull',
1620 'pull',
1590 'unbundle',
1621 'unbundle',
1591 ]
1622 ]
1592
1623
1593 def prefetchfiles(repo, revs, match):
1624 def prefetchfiles(repo, revs, match):
1594 """Invokes the registered file prefetch functions, allowing extensions to
1625 """Invokes the registered file prefetch functions, allowing extensions to
1595 ensure the corresponding files are available locally, before the command
1626 ensure the corresponding files are available locally, before the command
1596 uses them."""
1627 uses them."""
1597 if match:
1628 if match:
1598 # The command itself will complain about files that don't exist, so
1629 # The command itself will complain about files that don't exist, so
1599 # don't duplicate the message.
1630 # don't duplicate the message.
1600 match = matchmod.badmatch(match, lambda fn, msg: None)
1631 match = matchmod.badmatch(match, lambda fn, msg: None)
1601 else:
1632 else:
1602 match = matchall(repo)
1633 match = matchall(repo)
1603
1634
1604 fileprefetchhooks(repo, revs, match)
1635 fileprefetchhooks(repo, revs, match)
1605
1636
1606 # a list of (repo, revs, match) prefetch functions
1637 # a list of (repo, revs, match) prefetch functions
1607 fileprefetchhooks = util.hooks()
1638 fileprefetchhooks = util.hooks()
1608
1639
1609 # A marker that tells the evolve extension to suppress its own reporting
1640 # A marker that tells the evolve extension to suppress its own reporting
1610 _reportstroubledchangesets = True
1641 _reportstroubledchangesets = True
1611
1642
1612 def registersummarycallback(repo, otr, txnname=''):
1643 def registersummarycallback(repo, otr, txnname=''):
1613 """register a callback to issue a summary after the transaction is closed
1644 """register a callback to issue a summary after the transaction is closed
1614 """
1645 """
1615 def txmatch(sources):
1646 def txmatch(sources):
1616 return any(txnname.startswith(source) for source in sources)
1647 return any(txnname.startswith(source) for source in sources)
1617
1648
1618 categories = []
1649 categories = []
1619
1650
1620 def reportsummary(func):
1651 def reportsummary(func):
1621 """decorator for report callbacks."""
1652 """decorator for report callbacks."""
1622 # The repoview life cycle is shorter than the one of the actual
1653 # The repoview life cycle is shorter than the one of the actual
1623 # underlying repository. So the filtered object can die before the
1654 # underlying repository. So the filtered object can die before the
1624 # weakref is used leading to troubles. We keep a reference to the
1655 # weakref is used leading to troubles. We keep a reference to the
1625 # unfiltered object and restore the filtering when retrieving the
1656 # unfiltered object and restore the filtering when retrieving the
1626 # repository through the weakref.
1657 # repository through the weakref.
1627 filtername = repo.filtername
1658 filtername = repo.filtername
1628 reporef = weakref.ref(repo.unfiltered())
1659 reporef = weakref.ref(repo.unfiltered())
1629 def wrapped(tr):
1660 def wrapped(tr):
1630 repo = reporef()
1661 repo = reporef()
1631 if filtername:
1662 if filtername:
1632 repo = repo.filtered(filtername)
1663 repo = repo.filtered(filtername)
1633 func(repo, tr)
1664 func(repo, tr)
1634 newcat = '%02i-txnreport' % len(categories)
1665 newcat = '%02i-txnreport' % len(categories)
1635 otr.addpostclose(newcat, wrapped)
1666 otr.addpostclose(newcat, wrapped)
1636 categories.append(newcat)
1667 categories.append(newcat)
1637 return wrapped
1668 return wrapped
1638
1669
1639 if txmatch(_reportobsoletedsource):
1670 if txmatch(_reportobsoletedsource):
1640 @reportsummary
1671 @reportsummary
1641 def reportobsoleted(repo, tr):
1672 def reportobsoleted(repo, tr):
1642 obsoleted = obsutil.getobsoleted(repo, tr)
1673 obsoleted = obsutil.getobsoleted(repo, tr)
1643 if obsoleted:
1674 if obsoleted:
1644 repo.ui.status(_('obsoleted %i changesets\n')
1675 repo.ui.status(_('obsoleted %i changesets\n')
1645 % len(obsoleted))
1676 % len(obsoleted))
1646
1677
1647 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1678 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1648 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1679 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1649 instabilitytypes = [
1680 instabilitytypes = [
1650 ('orphan', 'orphan'),
1681 ('orphan', 'orphan'),
1651 ('phase-divergent', 'phasedivergent'),
1682 ('phase-divergent', 'phasedivergent'),
1652 ('content-divergent', 'contentdivergent'),
1683 ('content-divergent', 'contentdivergent'),
1653 ]
1684 ]
1654
1685
1655 def getinstabilitycounts(repo):
1686 def getinstabilitycounts(repo):
1656 filtered = repo.changelog.filteredrevs
1687 filtered = repo.changelog.filteredrevs
1657 counts = {}
1688 counts = {}
1658 for instability, revset in instabilitytypes:
1689 for instability, revset in instabilitytypes:
1659 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1690 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1660 filtered)
1691 filtered)
1661 return counts
1692 return counts
1662
1693
1663 oldinstabilitycounts = getinstabilitycounts(repo)
1694 oldinstabilitycounts = getinstabilitycounts(repo)
1664 @reportsummary
1695 @reportsummary
1665 def reportnewinstabilities(repo, tr):
1696 def reportnewinstabilities(repo, tr):
1666 newinstabilitycounts = getinstabilitycounts(repo)
1697 newinstabilitycounts = getinstabilitycounts(repo)
1667 for instability, revset in instabilitytypes:
1698 for instability, revset in instabilitytypes:
1668 delta = (newinstabilitycounts[instability] -
1699 delta = (newinstabilitycounts[instability] -
1669 oldinstabilitycounts[instability])
1700 oldinstabilitycounts[instability])
1670 msg = getinstabilitymessage(delta, instability)
1701 msg = getinstabilitymessage(delta, instability)
1671 if msg:
1702 if msg:
1672 repo.ui.warn(msg)
1703 repo.ui.warn(msg)
1673
1704
1674 if txmatch(_reportnewcssource):
1705 if txmatch(_reportnewcssource):
1675 @reportsummary
1706 @reportsummary
1676 def reportnewcs(repo, tr):
1707 def reportnewcs(repo, tr):
1677 """Report the range of new revisions pulled/unbundled."""
1708 """Report the range of new revisions pulled/unbundled."""
1678 origrepolen = tr.changes.get('origrepolen', len(repo))
1709 origrepolen = tr.changes.get('origrepolen', len(repo))
1679 unfi = repo.unfiltered()
1710 unfi = repo.unfiltered()
1680 if origrepolen >= len(unfi):
1711 if origrepolen >= len(unfi):
1681 return
1712 return
1682
1713
1683 # Compute the bounds of new visible revisions' range.
1714 # Compute the bounds of new visible revisions' range.
1684 revs = smartset.spanset(repo, start=origrepolen)
1715 revs = smartset.spanset(repo, start=origrepolen)
1685 if revs:
1716 if revs:
1686 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1717 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1687
1718
1688 if minrev == maxrev:
1719 if minrev == maxrev:
1689 revrange = minrev
1720 revrange = minrev
1690 else:
1721 else:
1691 revrange = '%s:%s' % (minrev, maxrev)
1722 revrange = '%s:%s' % (minrev, maxrev)
1692 draft = len(repo.revs('%ld and draft()', revs))
1723 draft = len(repo.revs('%ld and draft()', revs))
1693 secret = len(repo.revs('%ld and secret()', revs))
1724 secret = len(repo.revs('%ld and secret()', revs))
1694 if not (draft or secret):
1725 if not (draft or secret):
1695 msg = _('new changesets %s\n') % revrange
1726 msg = _('new changesets %s\n') % revrange
1696 elif draft and secret:
1727 elif draft and secret:
1697 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1728 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1698 msg %= (revrange, draft, secret)
1729 msg %= (revrange, draft, secret)
1699 elif draft:
1730 elif draft:
1700 msg = _('new changesets %s (%d drafts)\n')
1731 msg = _('new changesets %s (%d drafts)\n')
1701 msg %= (revrange, draft)
1732 msg %= (revrange, draft)
1702 elif secret:
1733 elif secret:
1703 msg = _('new changesets %s (%d secrets)\n')
1734 msg = _('new changesets %s (%d secrets)\n')
1704 msg %= (revrange, secret)
1735 msg %= (revrange, secret)
1705 else:
1736 else:
1706 errormsg = 'entered unreachable condition'
1737 errormsg = 'entered unreachable condition'
1707 raise error.ProgrammingError(errormsg)
1738 raise error.ProgrammingError(errormsg)
1708 repo.ui.status(msg)
1739 repo.ui.status(msg)
1709
1740
1710 # search new changesets directly pulled as obsolete
1741 # search new changesets directly pulled as obsolete
1711 duplicates = tr.changes.get('revduplicates', ())
1742 duplicates = tr.changes.get('revduplicates', ())
1712 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1743 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1713 origrepolen, duplicates)
1744 origrepolen, duplicates)
1714 cl = repo.changelog
1745 cl = repo.changelog
1715 extinctadded = [r for r in obsadded if r not in cl]
1746 extinctadded = [r for r in obsadded if r not in cl]
1716 if extinctadded:
1747 if extinctadded:
1717 # They are not just obsolete, but obsolete and invisible
1748 # They are not just obsolete, but obsolete and invisible
1718 # we call them "extinct" internally but the terms have not been
1749 # we call them "extinct" internally but the terms have not been
1719 # exposed to users.
1750 # exposed to users.
1720 msg = '(%d other changesets obsolete on arrival)\n'
1751 msg = '(%d other changesets obsolete on arrival)\n'
1721 repo.ui.status(msg % len(extinctadded))
1752 repo.ui.status(msg % len(extinctadded))
1722
1753
1723 @reportsummary
1754 @reportsummary
1724 def reportphasechanges(repo, tr):
1755 def reportphasechanges(repo, tr):
1725 """Report statistics of phase changes for changesets pre-existing
1756 """Report statistics of phase changes for changesets pre-existing
1726 pull/unbundle.
1757 pull/unbundle.
1727 """
1758 """
1728 origrepolen = tr.changes.get('origrepolen', len(repo))
1759 origrepolen = tr.changes.get('origrepolen', len(repo))
1729 phasetracking = tr.changes.get('phases', {})
1760 phasetracking = tr.changes.get('phases', {})
1730 if not phasetracking:
1761 if not phasetracking:
1731 return
1762 return
1732 published = [
1763 published = [
1733 rev for rev, (old, new) in phasetracking.iteritems()
1764 rev for rev, (old, new) in phasetracking.iteritems()
1734 if new == phases.public and rev < origrepolen
1765 if new == phases.public and rev < origrepolen
1735 ]
1766 ]
1736 if not published:
1767 if not published:
1737 return
1768 return
1738 repo.ui.status(_('%d local changesets published\n')
1769 repo.ui.status(_('%d local changesets published\n')
1739 % len(published))
1770 % len(published))
1740
1771
1741 def getinstabilitymessage(delta, instability):
1772 def getinstabilitymessage(delta, instability):
1742 """function to return the message to show warning about new instabilities
1773 """function to return the message to show warning about new instabilities
1743
1774
1744 exists as a separate function so that extension can wrap to show more
1775 exists as a separate function so that extension can wrap to show more
1745 information like how to fix instabilities"""
1776 information like how to fix instabilities"""
1746 if delta > 0:
1777 if delta > 0:
1747 return _('%i new %s changesets\n') % (delta, instability)
1778 return _('%i new %s changesets\n') % (delta, instability)
1748
1779
1749 def nodesummaries(repo, nodes, maxnumnodes=4):
1780 def nodesummaries(repo, nodes, maxnumnodes=4):
1750 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1781 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1751 return ' '.join(short(h) for h in nodes)
1782 return ' '.join(short(h) for h in nodes)
1752 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1783 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1753 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1784 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1754
1785
1755 def enforcesinglehead(repo, tr, desc):
1786 def enforcesinglehead(repo, tr, desc):
1756 """check that no named branch has multiple heads"""
1787 """check that no named branch has multiple heads"""
1757 if desc in ('strip', 'repair'):
1788 if desc in ('strip', 'repair'):
1758 # skip the logic during strip
1789 # skip the logic during strip
1759 return
1790 return
1760 visible = repo.filtered('visible')
1791 visible = repo.filtered('visible')
1761 # possible improvement: we could restrict the check to affected branch
1792 # possible improvement: we could restrict the check to affected branch
1762 for name, heads in visible.branchmap().iteritems():
1793 for name, heads in visible.branchmap().iteritems():
1763 if len(heads) > 1:
1794 if len(heads) > 1:
1764 msg = _('rejecting multiple heads on branch "%s"')
1795 msg = _('rejecting multiple heads on branch "%s"')
1765 msg %= name
1796 msg %= name
1766 hint = _('%d heads: %s')
1797 hint = _('%d heads: %s')
1767 hint %= (len(heads), nodesummaries(repo, heads))
1798 hint %= (len(heads), nodesummaries(repo, heads))
1768 raise error.Abort(msg, hint=hint)
1799 raise error.Abort(msg, hint=hint)
1769
1800
1770 def wrapconvertsink(sink):
1801 def wrapconvertsink(sink):
1771 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1802 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1772 before it is used, whether or not the convert extension was formally loaded.
1803 before it is used, whether or not the convert extension was formally loaded.
1773 """
1804 """
1774 return sink
1805 return sink
1775
1806
1776 def unhidehashlikerevs(repo, specs, hiddentype):
1807 def unhidehashlikerevs(repo, specs, hiddentype):
1777 """parse the user specs and unhide changesets whose hash or revision number
1808 """parse the user specs and unhide changesets whose hash or revision number
1778 is passed.
1809 is passed.
1779
1810
1780 hiddentype can be: 1) 'warn': warn while unhiding changesets
1811 hiddentype can be: 1) 'warn': warn while unhiding changesets
1781 2) 'nowarn': don't warn while unhiding changesets
1812 2) 'nowarn': don't warn while unhiding changesets
1782
1813
1783 returns a repo object with the required changesets unhidden
1814 returns a repo object with the required changesets unhidden
1784 """
1815 """
1785 if not repo.filtername or not repo.ui.configbool('experimental',
1816 if not repo.filtername or not repo.ui.configbool('experimental',
1786 'directaccess'):
1817 'directaccess'):
1787 return repo
1818 return repo
1788
1819
1789 if repo.filtername not in ('visible', 'visible-hidden'):
1820 if repo.filtername not in ('visible', 'visible-hidden'):
1790 return repo
1821 return repo
1791
1822
1792 symbols = set()
1823 symbols = set()
1793 for spec in specs:
1824 for spec in specs:
1794 try:
1825 try:
1795 tree = revsetlang.parse(spec)
1826 tree = revsetlang.parse(spec)
1796 except error.ParseError: # will be reported by scmutil.revrange()
1827 except error.ParseError: # will be reported by scmutil.revrange()
1797 continue
1828 continue
1798
1829
1799 symbols.update(revsetlang.gethashlikesymbols(tree))
1830 symbols.update(revsetlang.gethashlikesymbols(tree))
1800
1831
1801 if not symbols:
1832 if not symbols:
1802 return repo
1833 return repo
1803
1834
1804 revs = _getrevsfromsymbols(repo, symbols)
1835 revs = _getrevsfromsymbols(repo, symbols)
1805
1836
1806 if not revs:
1837 if not revs:
1807 return repo
1838 return repo
1808
1839
1809 if hiddentype == 'warn':
1840 if hiddentype == 'warn':
1810 unfi = repo.unfiltered()
1841 unfi = repo.unfiltered()
1811 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1842 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1812 repo.ui.warn(_("warning: accessing hidden changesets for write "
1843 repo.ui.warn(_("warning: accessing hidden changesets for write "
1813 "operation: %s\n") % revstr)
1844 "operation: %s\n") % revstr)
1814
1845
1815 # we have to use new filtername to separate branch/tags cache until we can
1846 # we have to use new filtername to separate branch/tags cache until we can
1816 # disbale these cache when revisions are dynamically pinned.
1847 # disbale these cache when revisions are dynamically pinned.
1817 return repo.filtered('visible-hidden', revs)
1848 return repo.filtered('visible-hidden', revs)
1818
1849
1819 def _getrevsfromsymbols(repo, symbols):
1850 def _getrevsfromsymbols(repo, symbols):
1820 """parse the list of symbols and returns a set of revision numbers of hidden
1851 """parse the list of symbols and returns a set of revision numbers of hidden
1821 changesets present in symbols"""
1852 changesets present in symbols"""
1822 revs = set()
1853 revs = set()
1823 unfi = repo.unfiltered()
1854 unfi = repo.unfiltered()
1824 unficl = unfi.changelog
1855 unficl = unfi.changelog
1825 cl = repo.changelog
1856 cl = repo.changelog
1826 tiprev = len(unficl)
1857 tiprev = len(unficl)
1827 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1858 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1828 for s in symbols:
1859 for s in symbols:
1829 try:
1860 try:
1830 n = int(s)
1861 n = int(s)
1831 if n <= tiprev:
1862 if n <= tiprev:
1832 if not allowrevnums:
1863 if not allowrevnums:
1833 continue
1864 continue
1834 else:
1865 else:
1835 if n not in cl:
1866 if n not in cl:
1836 revs.add(n)
1867 revs.add(n)
1837 continue
1868 continue
1838 except ValueError:
1869 except ValueError:
1839 pass
1870 pass
1840
1871
1841 try:
1872 try:
1842 s = resolvehexnodeidprefix(unfi, s)
1873 s = resolvehexnodeidprefix(unfi, s)
1843 except (error.LookupError, error.WdirUnsupported):
1874 except (error.LookupError, error.WdirUnsupported):
1844 s = None
1875 s = None
1845
1876
1846 if s is not None:
1877 if s is not None:
1847 rev = unficl.rev(s)
1878 rev = unficl.rev(s)
1848 if rev not in cl:
1879 if rev not in cl:
1849 revs.add(rev)
1880 revs.add(rev)
1850
1881
1851 return revs
1882 return revs
1852
1883
1853 def bookmarkrevs(repo, mark):
1884 def bookmarkrevs(repo, mark):
1854 """
1885 """
1855 Select revisions reachable by a given bookmark
1886 Select revisions reachable by a given bookmark
1856 """
1887 """
1857 return repo.revs("ancestors(bookmark(%s)) - "
1888 return repo.revs("ancestors(bookmark(%s)) - "
1858 "ancestors(head() and not bookmark(%s)) - "
1889 "ancestors(head() and not bookmark(%s)) - "
1859 "ancestors(bookmark() and not bookmark(%s))",
1890 "ancestors(bookmark() and not bookmark(%s))",
1860 mark, mark, mark)
1891 mark, mark, mark)
@@ -1,894 +1,863 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 hex,
12 hex,
13 nullid,
13 nullid,
14 wdirid,
14 wdirid,
15 wdirrev,
15 wdirrev,
16 )
16 )
17
17
18 from . import (
18 from . import (
19 diffutil,
19 diffutil,
20 encoding,
20 encoding,
21 error,
21 error,
22 hbisect,
22 hbisect,
23 i18n,
23 i18n,
24 obsutil,
24 obsutil,
25 patch,
25 patch,
26 pycompat,
26 pycompat,
27 registrar,
27 registrar,
28 scmutil,
28 scmutil,
29 templateutil,
29 templateutil,
30 util,
30 util,
31 )
31 )
32 from .utils import (
32 from .utils import (
33 stringutil,
33 stringutil,
34 )
34 )
35
35
36 _hybrid = templateutil.hybrid
36 _hybrid = templateutil.hybrid
37 hybriddict = templateutil.hybriddict
37 hybriddict = templateutil.hybriddict
38 hybridlist = templateutil.hybridlist
38 hybridlist = templateutil.hybridlist
39 compatdict = templateutil.compatdict
39 compatdict = templateutil.compatdict
40 compatlist = templateutil.compatlist
40 compatlist = templateutil.compatlist
41 _showcompatlist = templateutil._showcompatlist
41 _showcompatlist = templateutil._showcompatlist
42
42
43 def getlatesttags(context, mapping, pattern=None):
43 def getlatesttags(context, mapping, pattern=None):
44 '''return date, distance and name for the latest tag of rev'''
44 '''return date, distance and name for the latest tag of rev'''
45 repo = context.resource(mapping, 'repo')
45 repo = context.resource(mapping, 'repo')
46 ctx = context.resource(mapping, 'ctx')
46 ctx = context.resource(mapping, 'ctx')
47 cache = context.resource(mapping, 'cache')
47 cache = context.resource(mapping, 'cache')
48
48
49 cachename = 'latesttags'
49 cachename = 'latesttags'
50 if pattern is not None:
50 if pattern is not None:
51 cachename += '-' + pattern
51 cachename += '-' + pattern
52 match = stringutil.stringmatcher(pattern)[2]
52 match = stringutil.stringmatcher(pattern)[2]
53 else:
53 else:
54 match = util.always
54 match = util.always
55
55
56 if cachename not in cache:
56 if cachename not in cache:
57 # Cache mapping from rev to a tuple with tag date, tag
57 # Cache mapping from rev to a tuple with tag date, tag
58 # distance and tag name
58 # distance and tag name
59 cache[cachename] = {-1: (0, 0, ['null'])}
59 cache[cachename] = {-1: (0, 0, ['null'])}
60 latesttags = cache[cachename]
60 latesttags = cache[cachename]
61
61
62 rev = ctx.rev()
62 rev = ctx.rev()
63 todo = [rev]
63 todo = [rev]
64 while todo:
64 while todo:
65 rev = todo.pop()
65 rev = todo.pop()
66 if rev in latesttags:
66 if rev in latesttags:
67 continue
67 continue
68 ctx = repo[rev]
68 ctx = repo[rev]
69 tags = [t for t in ctx.tags()
69 tags = [t for t in ctx.tags()
70 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
70 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
71 and match(t))]
71 and match(t))]
72 if tags:
72 if tags:
73 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
73 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
74 continue
74 continue
75 try:
75 try:
76 ptags = [latesttags[p.rev()] for p in ctx.parents()]
76 ptags = [latesttags[p.rev()] for p in ctx.parents()]
77 if len(ptags) > 1:
77 if len(ptags) > 1:
78 if ptags[0][2] == ptags[1][2]:
78 if ptags[0][2] == ptags[1][2]:
79 # The tuples are laid out so the right one can be found by
79 # The tuples are laid out so the right one can be found by
80 # comparison in this case.
80 # comparison in this case.
81 pdate, pdist, ptag = max(ptags)
81 pdate, pdist, ptag = max(ptags)
82 else:
82 else:
83 def key(x):
83 def key(x):
84 tag = x[2][0]
84 tag = x[2][0]
85 if ctx.rev() is None:
85 if ctx.rev() is None:
86 # only() doesn't support wdir
86 # only() doesn't support wdir
87 prevs = [c.rev() for c in ctx.parents()]
87 prevs = [c.rev() for c in ctx.parents()]
88 changes = repo.revs('only(%ld, %s)', prevs, tag)
88 changes = repo.revs('only(%ld, %s)', prevs, tag)
89 changessincetag = len(changes) + 1
89 changessincetag = len(changes) + 1
90 else:
90 else:
91 changes = repo.revs('only(%d, %s)', ctx.rev(), tag)
91 changes = repo.revs('only(%d, %s)', ctx.rev(), tag)
92 changessincetag = len(changes)
92 changessincetag = len(changes)
93 # Smallest number of changes since tag wins. Date is
93 # Smallest number of changes since tag wins. Date is
94 # used as tiebreaker.
94 # used as tiebreaker.
95 return [-changessincetag, x[0]]
95 return [-changessincetag, x[0]]
96 pdate, pdist, ptag = max(ptags, key=key)
96 pdate, pdist, ptag = max(ptags, key=key)
97 else:
97 else:
98 pdate, pdist, ptag = ptags[0]
98 pdate, pdist, ptag = ptags[0]
99 except KeyError:
99 except KeyError:
100 # Cache miss - recurse
100 # Cache miss - recurse
101 todo.append(rev)
101 todo.append(rev)
102 todo.extend(p.rev() for p in ctx.parents())
102 todo.extend(p.rev() for p in ctx.parents())
103 continue
103 continue
104 latesttags[rev] = pdate, pdist + 1, ptag
104 latesttags[rev] = pdate, pdist + 1, ptag
105 return latesttags[rev]
105 return latesttags[rev]
106
106
107 def getrenamedfn(repo, endrev=None):
108 rcache = {}
109 if endrev is None:
110 endrev = len(repo)
111
112 def getrenamed(fn, rev):
113 '''looks up all renames for a file (up to endrev) the first
114 time the file is given. It indexes on the changerev and only
115 parses the manifest if linkrev != changerev.
116 Returns rename info for fn at changerev rev.'''
117 if fn not in rcache:
118 rcache[fn] = {}
119 fl = repo.file(fn)
120 for i in fl:
121 lr = fl.linkrev(i)
122 renamed = fl.renamed(fl.node(i))
123 rcache[fn][lr] = renamed and renamed[0]
124 if lr >= endrev:
125 break
126 if rev in rcache[fn]:
127 return rcache[fn][rev]
128
129 # If linkrev != rev (i.e. rev not found in rcache) fallback to
130 # filectx logic.
131 try:
132 return repo[rev][fn].copysource()
133 except error.LookupError:
134 return None
135
136 return getrenamed
137
138 def getlogcolumns():
107 def getlogcolumns():
139 """Return a dict of log column labels"""
108 """Return a dict of log column labels"""
140 _ = pycompat.identity # temporarily disable gettext
109 _ = pycompat.identity # temporarily disable gettext
141 # i18n: column positioning for "hg log"
110 # i18n: column positioning for "hg log"
142 columns = _('bookmark: %s\n'
111 columns = _('bookmark: %s\n'
143 'branch: %s\n'
112 'branch: %s\n'
144 'changeset: %s\n'
113 'changeset: %s\n'
145 'copies: %s\n'
114 'copies: %s\n'
146 'date: %s\n'
115 'date: %s\n'
147 'extra: %s=%s\n'
116 'extra: %s=%s\n'
148 'files+: %s\n'
117 'files+: %s\n'
149 'files-: %s\n'
118 'files-: %s\n'
150 'files: %s\n'
119 'files: %s\n'
151 'instability: %s\n'
120 'instability: %s\n'
152 'manifest: %s\n'
121 'manifest: %s\n'
153 'obsolete: %s\n'
122 'obsolete: %s\n'
154 'parent: %s\n'
123 'parent: %s\n'
155 'phase: %s\n'
124 'phase: %s\n'
156 'summary: %s\n'
125 'summary: %s\n'
157 'tag: %s\n'
126 'tag: %s\n'
158 'user: %s\n')
127 'user: %s\n')
159 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
128 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
160 i18n._(columns).splitlines(True)))
129 i18n._(columns).splitlines(True)))
161
130
162 # basic internal templates
131 # basic internal templates
163 _changeidtmpl = '{rev}:{node|formatnode}'
132 _changeidtmpl = '{rev}:{node|formatnode}'
164
133
165 # default templates internally used for rendering of lists
134 # default templates internally used for rendering of lists
166 defaulttempl = {
135 defaulttempl = {
167 'parent': _changeidtmpl + ' ',
136 'parent': _changeidtmpl + ' ',
168 'manifest': _changeidtmpl,
137 'manifest': _changeidtmpl,
169 'file_copy': '{name} ({source})',
138 'file_copy': '{name} ({source})',
170 'envvar': '{key}={value}',
139 'envvar': '{key}={value}',
171 'extra': '{key}={value|stringescape}'
140 'extra': '{key}={value|stringescape}'
172 }
141 }
173 # filecopy is preserved for compatibility reasons
142 # filecopy is preserved for compatibility reasons
174 defaulttempl['filecopy'] = defaulttempl['file_copy']
143 defaulttempl['filecopy'] = defaulttempl['file_copy']
175
144
176 # keywords are callables (see registrar.templatekeyword for details)
145 # keywords are callables (see registrar.templatekeyword for details)
177 keywords = {}
146 keywords = {}
178 templatekeyword = registrar.templatekeyword(keywords)
147 templatekeyword = registrar.templatekeyword(keywords)
179
148
180 @templatekeyword('author', requires={'ctx'})
149 @templatekeyword('author', requires={'ctx'})
181 def showauthor(context, mapping):
150 def showauthor(context, mapping):
182 """Alias for ``{user}``"""
151 """Alias for ``{user}``"""
183 return showuser(context, mapping)
152 return showuser(context, mapping)
184
153
185 @templatekeyword('bisect', requires={'repo', 'ctx'})
154 @templatekeyword('bisect', requires={'repo', 'ctx'})
186 def showbisect(context, mapping):
155 def showbisect(context, mapping):
187 """String. The changeset bisection status."""
156 """String. The changeset bisection status."""
188 repo = context.resource(mapping, 'repo')
157 repo = context.resource(mapping, 'repo')
189 ctx = context.resource(mapping, 'ctx')
158 ctx = context.resource(mapping, 'ctx')
190 return hbisect.label(repo, ctx.node())
159 return hbisect.label(repo, ctx.node())
191
160
192 @templatekeyword('branch', requires={'ctx'})
161 @templatekeyword('branch', requires={'ctx'})
193 def showbranch(context, mapping):
162 def showbranch(context, mapping):
194 """String. The name of the branch on which the changeset was
163 """String. The name of the branch on which the changeset was
195 committed.
164 committed.
196 """
165 """
197 ctx = context.resource(mapping, 'ctx')
166 ctx = context.resource(mapping, 'ctx')
198 return ctx.branch()
167 return ctx.branch()
199
168
200 @templatekeyword('branches', requires={'ctx'})
169 @templatekeyword('branches', requires={'ctx'})
201 def showbranches(context, mapping):
170 def showbranches(context, mapping):
202 """List of strings. The name of the branch on which the
171 """List of strings. The name of the branch on which the
203 changeset was committed. Will be empty if the branch name was
172 changeset was committed. Will be empty if the branch name was
204 default. (DEPRECATED)
173 default. (DEPRECATED)
205 """
174 """
206 ctx = context.resource(mapping, 'ctx')
175 ctx = context.resource(mapping, 'ctx')
207 branch = ctx.branch()
176 branch = ctx.branch()
208 if branch != 'default':
177 if branch != 'default':
209 return compatlist(context, mapping, 'branch', [branch],
178 return compatlist(context, mapping, 'branch', [branch],
210 plural='branches')
179 plural='branches')
211 return compatlist(context, mapping, 'branch', [], plural='branches')
180 return compatlist(context, mapping, 'branch', [], plural='branches')
212
181
213 @templatekeyword('bookmarks', requires={'repo', 'ctx'})
182 @templatekeyword('bookmarks', requires={'repo', 'ctx'})
214 def showbookmarks(context, mapping):
183 def showbookmarks(context, mapping):
215 """List of strings. Any bookmarks associated with the
184 """List of strings. Any bookmarks associated with the
216 changeset. Also sets 'active', the name of the active bookmark.
185 changeset. Also sets 'active', the name of the active bookmark.
217 """
186 """
218 repo = context.resource(mapping, 'repo')
187 repo = context.resource(mapping, 'repo')
219 ctx = context.resource(mapping, 'ctx')
188 ctx = context.resource(mapping, 'ctx')
220 bookmarks = ctx.bookmarks()
189 bookmarks = ctx.bookmarks()
221 active = repo._activebookmark
190 active = repo._activebookmark
222 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
191 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
223 f = _showcompatlist(context, mapping, 'bookmark', bookmarks)
192 f = _showcompatlist(context, mapping, 'bookmark', bookmarks)
224 return _hybrid(f, bookmarks, makemap, pycompat.identity)
193 return _hybrid(f, bookmarks, makemap, pycompat.identity)
225
194
226 @templatekeyword('children', requires={'ctx'})
195 @templatekeyword('children', requires={'ctx'})
227 def showchildren(context, mapping):
196 def showchildren(context, mapping):
228 """List of strings. The children of the changeset."""
197 """List of strings. The children of the changeset."""
229 ctx = context.resource(mapping, 'ctx')
198 ctx = context.resource(mapping, 'ctx')
230 childrevs = ['%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
199 childrevs = ['%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
231 return compatlist(context, mapping, 'children', childrevs, element='child')
200 return compatlist(context, mapping, 'children', childrevs, element='child')
232
201
233 # Deprecated, but kept alive for help generation a purpose.
202 # Deprecated, but kept alive for help generation a purpose.
234 @templatekeyword('currentbookmark', requires={'repo', 'ctx'})
203 @templatekeyword('currentbookmark', requires={'repo', 'ctx'})
235 def showcurrentbookmark(context, mapping):
204 def showcurrentbookmark(context, mapping):
236 """String. The active bookmark, if it is associated with the changeset.
205 """String. The active bookmark, if it is associated with the changeset.
237 (DEPRECATED)"""
206 (DEPRECATED)"""
238 return showactivebookmark(context, mapping)
207 return showactivebookmark(context, mapping)
239
208
240 @templatekeyword('activebookmark', requires={'repo', 'ctx'})
209 @templatekeyword('activebookmark', requires={'repo', 'ctx'})
241 def showactivebookmark(context, mapping):
210 def showactivebookmark(context, mapping):
242 """String. The active bookmark, if it is associated with the changeset."""
211 """String. The active bookmark, if it is associated with the changeset."""
243 repo = context.resource(mapping, 'repo')
212 repo = context.resource(mapping, 'repo')
244 ctx = context.resource(mapping, 'ctx')
213 ctx = context.resource(mapping, 'ctx')
245 active = repo._activebookmark
214 active = repo._activebookmark
246 if active and active in ctx.bookmarks():
215 if active and active in ctx.bookmarks():
247 return active
216 return active
248 return ''
217 return ''
249
218
250 @templatekeyword('date', requires={'ctx'})
219 @templatekeyword('date', requires={'ctx'})
251 def showdate(context, mapping):
220 def showdate(context, mapping):
252 """Date information. The date when the changeset was committed."""
221 """Date information. The date when the changeset was committed."""
253 ctx = context.resource(mapping, 'ctx')
222 ctx = context.resource(mapping, 'ctx')
254 # the default string format is '<float(unixtime)><tzoffset>' because
223 # the default string format is '<float(unixtime)><tzoffset>' because
255 # python-hglib splits date at decimal separator.
224 # python-hglib splits date at decimal separator.
256 return templateutil.date(ctx.date(), showfmt='%d.0%d')
225 return templateutil.date(ctx.date(), showfmt='%d.0%d')
257
226
258 @templatekeyword('desc', requires={'ctx'})
227 @templatekeyword('desc', requires={'ctx'})
259 def showdescription(context, mapping):
228 def showdescription(context, mapping):
260 """String. The text of the changeset description."""
229 """String. The text of the changeset description."""
261 ctx = context.resource(mapping, 'ctx')
230 ctx = context.resource(mapping, 'ctx')
262 s = ctx.description()
231 s = ctx.description()
263 if isinstance(s, encoding.localstr):
232 if isinstance(s, encoding.localstr):
264 # try hard to preserve utf-8 bytes
233 # try hard to preserve utf-8 bytes
265 return encoding.tolocal(encoding.fromlocal(s).strip())
234 return encoding.tolocal(encoding.fromlocal(s).strip())
266 elif isinstance(s, encoding.safelocalstr):
235 elif isinstance(s, encoding.safelocalstr):
267 return encoding.safelocalstr(s.strip())
236 return encoding.safelocalstr(s.strip())
268 else:
237 else:
269 return s.strip()
238 return s.strip()
270
239
271 @templatekeyword('diffstat', requires={'ui', 'ctx'})
240 @templatekeyword('diffstat', requires={'ui', 'ctx'})
272 def showdiffstat(context, mapping):
241 def showdiffstat(context, mapping):
273 """String. Statistics of changes with the following format:
242 """String. Statistics of changes with the following format:
274 "modified files: +added/-removed lines"
243 "modified files: +added/-removed lines"
275 """
244 """
276 ui = context.resource(mapping, 'ui')
245 ui = context.resource(mapping, 'ui')
277 ctx = context.resource(mapping, 'ctx')
246 ctx = context.resource(mapping, 'ctx')
278 diffopts = diffutil.diffallopts(ui, {'noprefix': False})
247 diffopts = diffutil.diffallopts(ui, {'noprefix': False})
279 diff = ctx.diff(opts=diffopts)
248 diff = ctx.diff(opts=diffopts)
280 stats = patch.diffstatdata(util.iterlines(diff))
249 stats = patch.diffstatdata(util.iterlines(diff))
281 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
250 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
282 return '%d: +%d/-%d' % (len(stats), adds, removes)
251 return '%d: +%d/-%d' % (len(stats), adds, removes)
283
252
284 @templatekeyword('envvars', requires={'ui'})
253 @templatekeyword('envvars', requires={'ui'})
285 def showenvvars(context, mapping):
254 def showenvvars(context, mapping):
286 """A dictionary of environment variables. (EXPERIMENTAL)"""
255 """A dictionary of environment variables. (EXPERIMENTAL)"""
287 ui = context.resource(mapping, 'ui')
256 ui = context.resource(mapping, 'ui')
288 env = ui.exportableenviron()
257 env = ui.exportableenviron()
289 env = util.sortdict((k, env[k]) for k in sorted(env))
258 env = util.sortdict((k, env[k]) for k in sorted(env))
290 return compatdict(context, mapping, 'envvar', env, plural='envvars')
259 return compatdict(context, mapping, 'envvar', env, plural='envvars')
291
260
292 @templatekeyword('extras', requires={'ctx'})
261 @templatekeyword('extras', requires={'ctx'})
293 def showextras(context, mapping):
262 def showextras(context, mapping):
294 """List of dicts with key, value entries of the 'extras'
263 """List of dicts with key, value entries of the 'extras'
295 field of this changeset."""
264 field of this changeset."""
296 ctx = context.resource(mapping, 'ctx')
265 ctx = context.resource(mapping, 'ctx')
297 extras = ctx.extra()
266 extras = ctx.extra()
298 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
267 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
299 makemap = lambda k: {'key': k, 'value': extras[k]}
268 makemap = lambda k: {'key': k, 'value': extras[k]}
300 c = [makemap(k) for k in extras]
269 c = [makemap(k) for k in extras]
301 f = _showcompatlist(context, mapping, 'extra', c, plural='extras')
270 f = _showcompatlist(context, mapping, 'extra', c, plural='extras')
302 return _hybrid(f, extras, makemap,
271 return _hybrid(f, extras, makemap,
303 lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])))
272 lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])))
304
273
305 def _getfilestatus(context, mapping, listall=False):
274 def _getfilestatus(context, mapping, listall=False):
306 ctx = context.resource(mapping, 'ctx')
275 ctx = context.resource(mapping, 'ctx')
307 revcache = context.resource(mapping, 'revcache')
276 revcache = context.resource(mapping, 'revcache')
308 if 'filestatus' not in revcache or revcache['filestatusall'] < listall:
277 if 'filestatus' not in revcache or revcache['filestatusall'] < listall:
309 stat = ctx.p1().status(ctx, listignored=listall, listclean=listall,
278 stat = ctx.p1().status(ctx, listignored=listall, listclean=listall,
310 listunknown=listall)
279 listunknown=listall)
311 revcache['filestatus'] = stat
280 revcache['filestatus'] = stat
312 revcache['filestatusall'] = listall
281 revcache['filestatusall'] = listall
313 return revcache['filestatus']
282 return revcache['filestatus']
314
283
315 def _getfilestatusmap(context, mapping, listall=False):
284 def _getfilestatusmap(context, mapping, listall=False):
316 revcache = context.resource(mapping, 'revcache')
285 revcache = context.resource(mapping, 'revcache')
317 if 'filestatusmap' not in revcache or revcache['filestatusall'] < listall:
286 if 'filestatusmap' not in revcache or revcache['filestatusall'] < listall:
318 stat = _getfilestatus(context, mapping, listall=listall)
287 stat = _getfilestatus(context, mapping, listall=listall)
319 revcache['filestatusmap'] = statmap = {}
288 revcache['filestatusmap'] = statmap = {}
320 for char, files in zip(pycompat.iterbytestr('MAR!?IC'), stat):
289 for char, files in zip(pycompat.iterbytestr('MAR!?IC'), stat):
321 statmap.update((f, char) for f in files)
290 statmap.update((f, char) for f in files)
322 return revcache['filestatusmap'] # {path: statchar}
291 return revcache['filestatusmap'] # {path: statchar}
323
292
324 def _showfilesbystat(context, mapping, name, index):
293 def _showfilesbystat(context, mapping, name, index):
325 stat = _getfilestatus(context, mapping)
294 stat = _getfilestatus(context, mapping)
326 files = stat[index]
295 files = stat[index]
327 return templateutil.compatfileslist(context, mapping, name, files)
296 return templateutil.compatfileslist(context, mapping, name, files)
328
297
329 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
298 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
330 def showfileadds(context, mapping):
299 def showfileadds(context, mapping):
331 """List of strings. Files added by this changeset."""
300 """List of strings. Files added by this changeset."""
332 return _showfilesbystat(context, mapping, 'file_add', 1)
301 return _showfilesbystat(context, mapping, 'file_add', 1)
333
302
334 @templatekeyword('file_copies',
303 @templatekeyword('file_copies',
335 requires={'repo', 'ctx', 'cache', 'revcache'})
304 requires={'repo', 'ctx', 'cache', 'revcache'})
336 def showfilecopies(context, mapping):
305 def showfilecopies(context, mapping):
337 """List of strings. Files copied in this changeset with
306 """List of strings. Files copied in this changeset with
338 their sources.
307 their sources.
339 """
308 """
340 repo = context.resource(mapping, 'repo')
309 repo = context.resource(mapping, 'repo')
341 ctx = context.resource(mapping, 'ctx')
310 ctx = context.resource(mapping, 'ctx')
342 cache = context.resource(mapping, 'cache')
311 cache = context.resource(mapping, 'cache')
343 copies = context.resource(mapping, 'revcache').get('copies')
312 copies = context.resource(mapping, 'revcache').get('copies')
344 if copies is None:
313 if copies is None:
345 if 'getrenamed' not in cache:
314 if 'getrenamed' not in cache:
346 cache['getrenamed'] = getrenamedfn(repo)
315 cache['getrenamed'] = scmutil.getrenamedfn(repo)
347 copies = []
316 copies = []
348 getrenamed = cache['getrenamed']
317 getrenamed = cache['getrenamed']
349 for fn in ctx.files():
318 for fn in ctx.files():
350 rename = getrenamed(fn, ctx.rev())
319 rename = getrenamed(fn, ctx.rev())
351 if rename:
320 if rename:
352 copies.append((fn, rename))
321 copies.append((fn, rename))
353 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
322 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
354 copies)
323 copies)
355
324
356 # showfilecopiesswitch() displays file copies only if copy records are
325 # showfilecopiesswitch() displays file copies only if copy records are
357 # provided before calling the templater, usually with a --copies
326 # provided before calling the templater, usually with a --copies
358 # command line switch.
327 # command line switch.
359 @templatekeyword('file_copies_switch', requires={'revcache'})
328 @templatekeyword('file_copies_switch', requires={'revcache'})
360 def showfilecopiesswitch(context, mapping):
329 def showfilecopiesswitch(context, mapping):
361 """List of strings. Like "file_copies" but displayed
330 """List of strings. Like "file_copies" but displayed
362 only if the --copied switch is set.
331 only if the --copied switch is set.
363 """
332 """
364 copies = context.resource(mapping, 'revcache').get('copies') or []
333 copies = context.resource(mapping, 'revcache').get('copies') or []
365 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
334 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
366 copies)
335 copies)
367
336
368 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
337 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
369 def showfiledels(context, mapping):
338 def showfiledels(context, mapping):
370 """List of strings. Files removed by this changeset."""
339 """List of strings. Files removed by this changeset."""
371 return _showfilesbystat(context, mapping, 'file_del', 2)
340 return _showfilesbystat(context, mapping, 'file_del', 2)
372
341
373 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
342 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
374 def showfilemods(context, mapping):
343 def showfilemods(context, mapping):
375 """List of strings. Files modified by this changeset."""
344 """List of strings. Files modified by this changeset."""
376 return _showfilesbystat(context, mapping, 'file_mod', 0)
345 return _showfilesbystat(context, mapping, 'file_mod', 0)
377
346
378 @templatekeyword('files', requires={'ctx'})
347 @templatekeyword('files', requires={'ctx'})
379 def showfiles(context, mapping):
348 def showfiles(context, mapping):
380 """List of strings. All files modified, added, or removed by this
349 """List of strings. All files modified, added, or removed by this
381 changeset.
350 changeset.
382 """
351 """
383 ctx = context.resource(mapping, 'ctx')
352 ctx = context.resource(mapping, 'ctx')
384 return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
353 return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
385
354
386 @templatekeyword('graphnode', requires={'repo', 'ctx'})
355 @templatekeyword('graphnode', requires={'repo', 'ctx'})
387 def showgraphnode(context, mapping):
356 def showgraphnode(context, mapping):
388 """String. The character representing the changeset node in an ASCII
357 """String. The character representing the changeset node in an ASCII
389 revision graph."""
358 revision graph."""
390 repo = context.resource(mapping, 'repo')
359 repo = context.resource(mapping, 'repo')
391 ctx = context.resource(mapping, 'ctx')
360 ctx = context.resource(mapping, 'ctx')
392 return getgraphnode(repo, ctx)
361 return getgraphnode(repo, ctx)
393
362
394 def getgraphnode(repo, ctx):
363 def getgraphnode(repo, ctx):
395 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
364 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
396
365
397 def getgraphnodecurrent(repo, ctx):
366 def getgraphnodecurrent(repo, ctx):
398 wpnodes = repo.dirstate.parents()
367 wpnodes = repo.dirstate.parents()
399 if wpnodes[1] == nullid:
368 if wpnodes[1] == nullid:
400 wpnodes = wpnodes[:1]
369 wpnodes = wpnodes[:1]
401 if ctx.node() in wpnodes:
370 if ctx.node() in wpnodes:
402 return '@'
371 return '@'
403 else:
372 else:
404 return ''
373 return ''
405
374
406 def getgraphnodesymbol(ctx):
375 def getgraphnodesymbol(ctx):
407 if ctx.obsolete():
376 if ctx.obsolete():
408 return 'x'
377 return 'x'
409 elif ctx.isunstable():
378 elif ctx.isunstable():
410 return '*'
379 return '*'
411 elif ctx.closesbranch():
380 elif ctx.closesbranch():
412 return '_'
381 return '_'
413 else:
382 else:
414 return 'o'
383 return 'o'
415
384
416 @templatekeyword('graphwidth', requires=())
385 @templatekeyword('graphwidth', requires=())
417 def showgraphwidth(context, mapping):
386 def showgraphwidth(context, mapping):
418 """Integer. The width of the graph drawn by 'log --graph' or zero."""
387 """Integer. The width of the graph drawn by 'log --graph' or zero."""
419 # just hosts documentation; should be overridden by template mapping
388 # just hosts documentation; should be overridden by template mapping
420 return 0
389 return 0
421
390
422 @templatekeyword('index', requires=())
391 @templatekeyword('index', requires=())
423 def showindex(context, mapping):
392 def showindex(context, mapping):
424 """Integer. The current iteration of the loop. (0 indexed)"""
393 """Integer. The current iteration of the loop. (0 indexed)"""
425 # just hosts documentation; should be overridden by template mapping
394 # just hosts documentation; should be overridden by template mapping
426 raise error.Abort(_("can't use index in this context"))
395 raise error.Abort(_("can't use index in this context"))
427
396
428 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
397 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
429 def showlatesttag(context, mapping):
398 def showlatesttag(context, mapping):
430 """List of strings. The global tags on the most recent globally
399 """List of strings. The global tags on the most recent globally
431 tagged ancestor of this changeset. If no such tags exist, the list
400 tagged ancestor of this changeset. If no such tags exist, the list
432 consists of the single string "null".
401 consists of the single string "null".
433 """
402 """
434 return showlatesttags(context, mapping, None)
403 return showlatesttags(context, mapping, None)
435
404
436 def showlatesttags(context, mapping, pattern):
405 def showlatesttags(context, mapping, pattern):
437 """helper method for the latesttag keyword and function"""
406 """helper method for the latesttag keyword and function"""
438 latesttags = getlatesttags(context, mapping, pattern)
407 latesttags = getlatesttags(context, mapping, pattern)
439
408
440 # latesttag[0] is an implementation detail for sorting csets on different
409 # latesttag[0] is an implementation detail for sorting csets on different
441 # branches in a stable manner- it is the date the tagged cset was created,
410 # branches in a stable manner- it is the date the tagged cset was created,
442 # not the date the tag was created. Therefore it isn't made visible here.
411 # not the date the tag was created. Therefore it isn't made visible here.
443 makemap = lambda v: {
412 makemap = lambda v: {
444 'changes': _showchangessincetag,
413 'changes': _showchangessincetag,
445 'distance': latesttags[1],
414 'distance': latesttags[1],
446 'latesttag': v, # BC with {latesttag % '{latesttag}'}
415 'latesttag': v, # BC with {latesttag % '{latesttag}'}
447 'tag': v
416 'tag': v
448 }
417 }
449
418
450 tags = latesttags[2]
419 tags = latesttags[2]
451 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
420 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
452 return _hybrid(f, tags, makemap, pycompat.identity)
421 return _hybrid(f, tags, makemap, pycompat.identity)
453
422
454 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
423 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
455 def showlatesttagdistance(context, mapping):
424 def showlatesttagdistance(context, mapping):
456 """Integer. Longest path to the latest tag."""
425 """Integer. Longest path to the latest tag."""
457 return getlatesttags(context, mapping)[1]
426 return getlatesttags(context, mapping)[1]
458
427
459 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
428 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
460 def showchangessincelatesttag(context, mapping):
429 def showchangessincelatesttag(context, mapping):
461 """Integer. All ancestors not in the latest tag."""
430 """Integer. All ancestors not in the latest tag."""
462 tag = getlatesttags(context, mapping)[2][0]
431 tag = getlatesttags(context, mapping)[2][0]
463 mapping = context.overlaymap(mapping, {'tag': tag})
432 mapping = context.overlaymap(mapping, {'tag': tag})
464 return _showchangessincetag(context, mapping)
433 return _showchangessincetag(context, mapping)
465
434
466 def _showchangessincetag(context, mapping):
435 def _showchangessincetag(context, mapping):
467 repo = context.resource(mapping, 'repo')
436 repo = context.resource(mapping, 'repo')
468 ctx = context.resource(mapping, 'ctx')
437 ctx = context.resource(mapping, 'ctx')
469 offset = 0
438 offset = 0
470 revs = [ctx.rev()]
439 revs = [ctx.rev()]
471 tag = context.symbol(mapping, 'tag')
440 tag = context.symbol(mapping, 'tag')
472
441
473 # The only() revset doesn't currently support wdir()
442 # The only() revset doesn't currently support wdir()
474 if ctx.rev() is None:
443 if ctx.rev() is None:
475 offset = 1
444 offset = 1
476 revs = [p.rev() for p in ctx.parents()]
445 revs = [p.rev() for p in ctx.parents()]
477
446
478 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
447 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
479
448
480 # teach templater latesttags.changes is switched to (context, mapping) API
449 # teach templater latesttags.changes is switched to (context, mapping) API
481 _showchangessincetag._requires = {'repo', 'ctx'}
450 _showchangessincetag._requires = {'repo', 'ctx'}
482
451
483 @templatekeyword('manifest', requires={'repo', 'ctx'})
452 @templatekeyword('manifest', requires={'repo', 'ctx'})
484 def showmanifest(context, mapping):
453 def showmanifest(context, mapping):
485 repo = context.resource(mapping, 'repo')
454 repo = context.resource(mapping, 'repo')
486 ctx = context.resource(mapping, 'ctx')
455 ctx = context.resource(mapping, 'ctx')
487 mnode = ctx.manifestnode()
456 mnode = ctx.manifestnode()
488 if mnode is None:
457 if mnode is None:
489 mnode = wdirid
458 mnode = wdirid
490 mrev = wdirrev
459 mrev = wdirrev
491 else:
460 else:
492 mrev = repo.manifestlog.rev(mnode)
461 mrev = repo.manifestlog.rev(mnode)
493 mhex = hex(mnode)
462 mhex = hex(mnode)
494 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
463 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
495 f = context.process('manifest', mapping)
464 f = context.process('manifest', mapping)
496 return templateutil.hybriditem(f, None, f,
465 return templateutil.hybriditem(f, None, f,
497 lambda x: {'rev': mrev, 'node': mhex})
466 lambda x: {'rev': mrev, 'node': mhex})
498
467
499 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
468 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
500 def showobsfate(context, mapping):
469 def showobsfate(context, mapping):
501 # this function returns a list containing pre-formatted obsfate strings.
470 # this function returns a list containing pre-formatted obsfate strings.
502 #
471 #
503 # This function will be replaced by templates fragments when we will have
472 # This function will be replaced by templates fragments when we will have
504 # the verbosity templatekw available.
473 # the verbosity templatekw available.
505 succsandmarkers = showsuccsandmarkers(context, mapping)
474 succsandmarkers = showsuccsandmarkers(context, mapping)
506
475
507 ui = context.resource(mapping, 'ui')
476 ui = context.resource(mapping, 'ui')
508 repo = context.resource(mapping, 'repo')
477 repo = context.resource(mapping, 'repo')
509 values = []
478 values = []
510
479
511 for x in succsandmarkers.tovalue(context, mapping):
480 for x in succsandmarkers.tovalue(context, mapping):
512 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
481 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
513 scmutil.formatchangeid)
482 scmutil.formatchangeid)
514 values.append(v)
483 values.append(v)
515
484
516 return compatlist(context, mapping, "fate", values)
485 return compatlist(context, mapping, "fate", values)
517
486
518 def shownames(context, mapping, namespace):
487 def shownames(context, mapping, namespace):
519 """helper method to generate a template keyword for a namespace"""
488 """helper method to generate a template keyword for a namespace"""
520 repo = context.resource(mapping, 'repo')
489 repo = context.resource(mapping, 'repo')
521 ctx = context.resource(mapping, 'ctx')
490 ctx = context.resource(mapping, 'ctx')
522 ns = repo.names[namespace]
491 ns = repo.names[namespace]
523 names = ns.names(repo, ctx.node())
492 names = ns.names(repo, ctx.node())
524 return compatlist(context, mapping, ns.templatename, names,
493 return compatlist(context, mapping, ns.templatename, names,
525 plural=namespace)
494 plural=namespace)
526
495
527 @templatekeyword('namespaces', requires={'repo', 'ctx'})
496 @templatekeyword('namespaces', requires={'repo', 'ctx'})
528 def shownamespaces(context, mapping):
497 def shownamespaces(context, mapping):
529 """Dict of lists. Names attached to this changeset per
498 """Dict of lists. Names attached to this changeset per
530 namespace."""
499 namespace."""
531 repo = context.resource(mapping, 'repo')
500 repo = context.resource(mapping, 'repo')
532 ctx = context.resource(mapping, 'ctx')
501 ctx = context.resource(mapping, 'ctx')
533
502
534 namespaces = util.sortdict()
503 namespaces = util.sortdict()
535 def makensmapfn(ns):
504 def makensmapfn(ns):
536 # 'name' for iterating over namespaces, templatename for local reference
505 # 'name' for iterating over namespaces, templatename for local reference
537 return lambda v: {'name': v, ns.templatename: v}
506 return lambda v: {'name': v, ns.templatename: v}
538
507
539 for k, ns in repo.names.iteritems():
508 for k, ns in repo.names.iteritems():
540 names = ns.names(repo, ctx.node())
509 names = ns.names(repo, ctx.node())
541 f = _showcompatlist(context, mapping, 'name', names)
510 f = _showcompatlist(context, mapping, 'name', names)
542 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
511 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
543
512
544 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
513 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
545
514
546 def makemap(ns):
515 def makemap(ns):
547 return {
516 return {
548 'namespace': ns,
517 'namespace': ns,
549 'names': namespaces[ns],
518 'names': namespaces[ns],
550 'builtin': repo.names[ns].builtin,
519 'builtin': repo.names[ns].builtin,
551 'colorname': repo.names[ns].colorname,
520 'colorname': repo.names[ns].colorname,
552 }
521 }
553
522
554 return _hybrid(f, namespaces, makemap, pycompat.identity)
523 return _hybrid(f, namespaces, makemap, pycompat.identity)
555
524
556 @templatekeyword('negrev', requires={'repo', 'ctx'})
525 @templatekeyword('negrev', requires={'repo', 'ctx'})
557 def shownegrev(context, mapping):
526 def shownegrev(context, mapping):
558 """Integer. The repository-local changeset negative revision number,
527 """Integer. The repository-local changeset negative revision number,
559 which counts in the opposite direction."""
528 which counts in the opposite direction."""
560 ctx = context.resource(mapping, 'ctx')
529 ctx = context.resource(mapping, 'ctx')
561 rev = ctx.rev()
530 rev = ctx.rev()
562 if rev is None or rev < 0: # wdir() or nullrev?
531 if rev is None or rev < 0: # wdir() or nullrev?
563 return None
532 return None
564 repo = context.resource(mapping, 'repo')
533 repo = context.resource(mapping, 'repo')
565 return rev - len(repo)
534 return rev - len(repo)
566
535
567 @templatekeyword('node', requires={'ctx'})
536 @templatekeyword('node', requires={'ctx'})
568 def shownode(context, mapping):
537 def shownode(context, mapping):
569 """String. The changeset identification hash, as a 40 hexadecimal
538 """String. The changeset identification hash, as a 40 hexadecimal
570 digit string.
539 digit string.
571 """
540 """
572 ctx = context.resource(mapping, 'ctx')
541 ctx = context.resource(mapping, 'ctx')
573 return ctx.hex()
542 return ctx.hex()
574
543
575 @templatekeyword('obsolete', requires={'ctx'})
544 @templatekeyword('obsolete', requires={'ctx'})
576 def showobsolete(context, mapping):
545 def showobsolete(context, mapping):
577 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
546 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
578 ctx = context.resource(mapping, 'ctx')
547 ctx = context.resource(mapping, 'ctx')
579 if ctx.obsolete():
548 if ctx.obsolete():
580 return 'obsolete'
549 return 'obsolete'
581 return ''
550 return ''
582
551
583 @templatekeyword('path', requires={'fctx'})
552 @templatekeyword('path', requires={'fctx'})
584 def showpath(context, mapping):
553 def showpath(context, mapping):
585 """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
554 """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
586 fctx = context.resource(mapping, 'fctx')
555 fctx = context.resource(mapping, 'fctx')
587 return fctx.path()
556 return fctx.path()
588
557
589 @templatekeyword('peerurls', requires={'repo'})
558 @templatekeyword('peerurls', requires={'repo'})
590 def showpeerurls(context, mapping):
559 def showpeerurls(context, mapping):
591 """A dictionary of repository locations defined in the [paths] section
560 """A dictionary of repository locations defined in the [paths] section
592 of your configuration file."""
561 of your configuration file."""
593 repo = context.resource(mapping, 'repo')
562 repo = context.resource(mapping, 'repo')
594 # see commands.paths() for naming of dictionary keys
563 # see commands.paths() for naming of dictionary keys
595 paths = repo.ui.paths
564 paths = repo.ui.paths
596 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
565 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
597 def makemap(k):
566 def makemap(k):
598 p = paths[k]
567 p = paths[k]
599 d = {'name': k, 'url': p.rawloc}
568 d = {'name': k, 'url': p.rawloc}
600 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
569 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
601 return d
570 return d
602 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
571 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
603
572
604 @templatekeyword("predecessors", requires={'repo', 'ctx'})
573 @templatekeyword("predecessors", requires={'repo', 'ctx'})
605 def showpredecessors(context, mapping):
574 def showpredecessors(context, mapping):
606 """Returns the list of the closest visible successors. (EXPERIMENTAL)"""
575 """Returns the list of the closest visible successors. (EXPERIMENTAL)"""
607 repo = context.resource(mapping, 'repo')
576 repo = context.resource(mapping, 'repo')
608 ctx = context.resource(mapping, 'ctx')
577 ctx = context.resource(mapping, 'ctx')
609 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
578 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
610 predecessors = pycompat.maplist(hex, predecessors)
579 predecessors = pycompat.maplist(hex, predecessors)
611
580
612 return _hybrid(None, predecessors,
581 return _hybrid(None, predecessors,
613 lambda x: {'ctx': repo[x]},
582 lambda x: {'ctx': repo[x]},
614 lambda x: scmutil.formatchangeid(repo[x]))
583 lambda x: scmutil.formatchangeid(repo[x]))
615
584
616 @templatekeyword('reporoot', requires={'repo'})
585 @templatekeyword('reporoot', requires={'repo'})
617 def showreporoot(context, mapping):
586 def showreporoot(context, mapping):
618 """String. The root directory of the current repository."""
587 """String. The root directory of the current repository."""
619 repo = context.resource(mapping, 'repo')
588 repo = context.resource(mapping, 'repo')
620 return repo.root
589 return repo.root
621
590
622 @templatekeyword('size', requires={'fctx'})
591 @templatekeyword('size', requires={'fctx'})
623 def showsize(context, mapping):
592 def showsize(context, mapping):
624 """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
593 """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
625 fctx = context.resource(mapping, 'fctx')
594 fctx = context.resource(mapping, 'fctx')
626 return fctx.size()
595 return fctx.size()
627
596
628 # requires 'fctx' to denote {status} depends on (ctx, path) pair
597 # requires 'fctx' to denote {status} depends on (ctx, path) pair
629 @templatekeyword('status', requires={'ctx', 'fctx', 'revcache'})
598 @templatekeyword('status', requires={'ctx', 'fctx', 'revcache'})
630 def showstatus(context, mapping):
599 def showstatus(context, mapping):
631 """String. Status code of the current file. (EXPERIMENTAL)"""
600 """String. Status code of the current file. (EXPERIMENTAL)"""
632 path = templateutil.runsymbol(context, mapping, 'path')
601 path = templateutil.runsymbol(context, mapping, 'path')
633 path = templateutil.stringify(context, mapping, path)
602 path = templateutil.stringify(context, mapping, path)
634 if not path:
603 if not path:
635 return
604 return
636 statmap = _getfilestatusmap(context, mapping)
605 statmap = _getfilestatusmap(context, mapping)
637 if path not in statmap:
606 if path not in statmap:
638 statmap = _getfilestatusmap(context, mapping, listall=True)
607 statmap = _getfilestatusmap(context, mapping, listall=True)
639 return statmap.get(path)
608 return statmap.get(path)
640
609
641 @templatekeyword("successorssets", requires={'repo', 'ctx'})
610 @templatekeyword("successorssets", requires={'repo', 'ctx'})
642 def showsuccessorssets(context, mapping):
611 def showsuccessorssets(context, mapping):
643 """Returns a string of sets of successors for a changectx. Format used
612 """Returns a string of sets of successors for a changectx. Format used
644 is: [ctx1, ctx2], [ctx3] if ctx has been split into ctx1 and ctx2
613 is: [ctx1, ctx2], [ctx3] if ctx has been split into ctx1 and ctx2
645 while also diverged into ctx3. (EXPERIMENTAL)"""
614 while also diverged into ctx3. (EXPERIMENTAL)"""
646 repo = context.resource(mapping, 'repo')
615 repo = context.resource(mapping, 'repo')
647 ctx = context.resource(mapping, 'ctx')
616 ctx = context.resource(mapping, 'ctx')
648 if not ctx.obsolete():
617 if not ctx.obsolete():
649 return ''
618 return ''
650
619
651 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
620 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
652 ssets = [[hex(n) for n in ss] for ss in ssets]
621 ssets = [[hex(n) for n in ss] for ss in ssets]
653
622
654 data = []
623 data = []
655 for ss in ssets:
624 for ss in ssets:
656 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
625 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
657 lambda x: scmutil.formatchangeid(repo[x]))
626 lambda x: scmutil.formatchangeid(repo[x]))
658 data.append(h)
627 data.append(h)
659
628
660 # Format the successorssets
629 # Format the successorssets
661 def render(d):
630 def render(d):
662 return templateutil.stringify(context, mapping, d)
631 return templateutil.stringify(context, mapping, d)
663
632
664 def gen(data):
633 def gen(data):
665 yield "; ".join(render(d) for d in data)
634 yield "; ".join(render(d) for d in data)
666
635
667 return _hybrid(gen(data), data, lambda x: {'successorset': x},
636 return _hybrid(gen(data), data, lambda x: {'successorset': x},
668 pycompat.identity)
637 pycompat.identity)
669
638
670 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
639 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
671 def showsuccsandmarkers(context, mapping):
640 def showsuccsandmarkers(context, mapping):
672 """Returns a list of dict for each final successor of ctx. The dict
641 """Returns a list of dict for each final successor of ctx. The dict
673 contains successors node id in "successors" keys and the list of
642 contains successors node id in "successors" keys and the list of
674 obs-markers from ctx to the set of successors in "markers".
643 obs-markers from ctx to the set of successors in "markers".
675 (EXPERIMENTAL)
644 (EXPERIMENTAL)
676 """
645 """
677 repo = context.resource(mapping, 'repo')
646 repo = context.resource(mapping, 'repo')
678 ctx = context.resource(mapping, 'ctx')
647 ctx = context.resource(mapping, 'ctx')
679
648
680 values = obsutil.successorsandmarkers(repo, ctx)
649 values = obsutil.successorsandmarkers(repo, ctx)
681
650
682 if values is None:
651 if values is None:
683 values = []
652 values = []
684
653
685 # Format successors and markers to avoid exposing binary to templates
654 # Format successors and markers to avoid exposing binary to templates
686 data = []
655 data = []
687 for i in values:
656 for i in values:
688 # Format successors
657 # Format successors
689 successors = i['successors']
658 successors = i['successors']
690
659
691 successors = [hex(n) for n in successors]
660 successors = [hex(n) for n in successors]
692 successors = _hybrid(None, successors,
661 successors = _hybrid(None, successors,
693 lambda x: {'ctx': repo[x]},
662 lambda x: {'ctx': repo[x]},
694 lambda x: scmutil.formatchangeid(repo[x]))
663 lambda x: scmutil.formatchangeid(repo[x]))
695
664
696 # Format markers
665 # Format markers
697 finalmarkers = []
666 finalmarkers = []
698 for m in i['markers']:
667 for m in i['markers']:
699 hexprec = hex(m[0])
668 hexprec = hex(m[0])
700 hexsucs = tuple(hex(n) for n in m[1])
669 hexsucs = tuple(hex(n) for n in m[1])
701 hexparents = None
670 hexparents = None
702 if m[5] is not None:
671 if m[5] is not None:
703 hexparents = tuple(hex(n) for n in m[5])
672 hexparents = tuple(hex(n) for n in m[5])
704 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
673 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
705 finalmarkers.append(newmarker)
674 finalmarkers.append(newmarker)
706
675
707 data.append({'successors': successors, 'markers': finalmarkers})
676 data.append({'successors': successors, 'markers': finalmarkers})
708
677
709 return templateutil.mappinglist(data)
678 return templateutil.mappinglist(data)
710
679
711 @templatekeyword('p1', requires={'ctx'})
680 @templatekeyword('p1', requires={'ctx'})
712 def showp1(context, mapping):
681 def showp1(context, mapping):
713 """Changeset. The changeset's first parent. ``{p1.rev}`` for the revision
682 """Changeset. The changeset's first parent. ``{p1.rev}`` for the revision
714 number, and ``{p1.node}`` for the identification hash."""
683 number, and ``{p1.node}`` for the identification hash."""
715 ctx = context.resource(mapping, 'ctx')
684 ctx = context.resource(mapping, 'ctx')
716 return templateutil.mappingdict({'ctx': ctx.p1()}, tmpl=_changeidtmpl)
685 return templateutil.mappingdict({'ctx': ctx.p1()}, tmpl=_changeidtmpl)
717
686
718 @templatekeyword('p2', requires={'ctx'})
687 @templatekeyword('p2', requires={'ctx'})
719 def showp2(context, mapping):
688 def showp2(context, mapping):
720 """Changeset. The changeset's second parent. ``{p2.rev}`` for the revision
689 """Changeset. The changeset's second parent. ``{p2.rev}`` for the revision
721 number, and ``{p2.node}`` for the identification hash."""
690 number, and ``{p2.node}`` for the identification hash."""
722 ctx = context.resource(mapping, 'ctx')
691 ctx = context.resource(mapping, 'ctx')
723 return templateutil.mappingdict({'ctx': ctx.p2()}, tmpl=_changeidtmpl)
692 return templateutil.mappingdict({'ctx': ctx.p2()}, tmpl=_changeidtmpl)
724
693
725 @templatekeyword('p1rev', requires={'ctx'})
694 @templatekeyword('p1rev', requires={'ctx'})
726 def showp1rev(context, mapping):
695 def showp1rev(context, mapping):
727 """Integer. The repository-local revision number of the changeset's
696 """Integer. The repository-local revision number of the changeset's
728 first parent, or -1 if the changeset has no parents. (DEPRECATED)"""
697 first parent, or -1 if the changeset has no parents. (DEPRECATED)"""
729 ctx = context.resource(mapping, 'ctx')
698 ctx = context.resource(mapping, 'ctx')
730 return ctx.p1().rev()
699 return ctx.p1().rev()
731
700
732 @templatekeyword('p2rev', requires={'ctx'})
701 @templatekeyword('p2rev', requires={'ctx'})
733 def showp2rev(context, mapping):
702 def showp2rev(context, mapping):
734 """Integer. The repository-local revision number of the changeset's
703 """Integer. The repository-local revision number of the changeset's
735 second parent, or -1 if the changeset has no second parent. (DEPRECATED)"""
704 second parent, or -1 if the changeset has no second parent. (DEPRECATED)"""
736 ctx = context.resource(mapping, 'ctx')
705 ctx = context.resource(mapping, 'ctx')
737 return ctx.p2().rev()
706 return ctx.p2().rev()
738
707
739 @templatekeyword('p1node', requires={'ctx'})
708 @templatekeyword('p1node', requires={'ctx'})
740 def showp1node(context, mapping):
709 def showp1node(context, mapping):
741 """String. The identification hash of the changeset's first parent,
710 """String. The identification hash of the changeset's first parent,
742 as a 40 digit hexadecimal string. If the changeset has no parents, all
711 as a 40 digit hexadecimal string. If the changeset has no parents, all
743 digits are 0. (DEPRECATED)"""
712 digits are 0. (DEPRECATED)"""
744 ctx = context.resource(mapping, 'ctx')
713 ctx = context.resource(mapping, 'ctx')
745 return ctx.p1().hex()
714 return ctx.p1().hex()
746
715
747 @templatekeyword('p2node', requires={'ctx'})
716 @templatekeyword('p2node', requires={'ctx'})
748 def showp2node(context, mapping):
717 def showp2node(context, mapping):
749 """String. The identification hash of the changeset's second
718 """String. The identification hash of the changeset's second
750 parent, as a 40 digit hexadecimal string. If the changeset has no second
719 parent, as a 40 digit hexadecimal string. If the changeset has no second
751 parent, all digits are 0. (DEPRECATED)"""
720 parent, all digits are 0. (DEPRECATED)"""
752 ctx = context.resource(mapping, 'ctx')
721 ctx = context.resource(mapping, 'ctx')
753 return ctx.p2().hex()
722 return ctx.p2().hex()
754
723
755 @templatekeyword('parents', requires={'repo', 'ctx'})
724 @templatekeyword('parents', requires={'repo', 'ctx'})
756 def showparents(context, mapping):
725 def showparents(context, mapping):
757 """List of strings. The parents of the changeset in "rev:node"
726 """List of strings. The parents of the changeset in "rev:node"
758 format. If the changeset has only one "natural" parent (the predecessor
727 format. If the changeset has only one "natural" parent (the predecessor
759 revision) nothing is shown."""
728 revision) nothing is shown."""
760 repo = context.resource(mapping, 'repo')
729 repo = context.resource(mapping, 'repo')
761 ctx = context.resource(mapping, 'ctx')
730 ctx = context.resource(mapping, 'ctx')
762 pctxs = scmutil.meaningfulparents(repo, ctx)
731 pctxs = scmutil.meaningfulparents(repo, ctx)
763 prevs = [p.rev() for p in pctxs]
732 prevs = [p.rev() for p in pctxs]
764 parents = [[('rev', p.rev()),
733 parents = [[('rev', p.rev()),
765 ('node', p.hex()),
734 ('node', p.hex()),
766 ('phase', p.phasestr())]
735 ('phase', p.phasestr())]
767 for p in pctxs]
736 for p in pctxs]
768 f = _showcompatlist(context, mapping, 'parent', parents)
737 f = _showcompatlist(context, mapping, 'parent', parents)
769 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
738 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
770 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
739 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
771
740
772 @templatekeyword('phase', requires={'ctx'})
741 @templatekeyword('phase', requires={'ctx'})
773 def showphase(context, mapping):
742 def showphase(context, mapping):
774 """String. The changeset phase name."""
743 """String. The changeset phase name."""
775 ctx = context.resource(mapping, 'ctx')
744 ctx = context.resource(mapping, 'ctx')
776 return ctx.phasestr()
745 return ctx.phasestr()
777
746
778 @templatekeyword('phaseidx', requires={'ctx'})
747 @templatekeyword('phaseidx', requires={'ctx'})
779 def showphaseidx(context, mapping):
748 def showphaseidx(context, mapping):
780 """Integer. The changeset phase index. (ADVANCED)"""
749 """Integer. The changeset phase index. (ADVANCED)"""
781 ctx = context.resource(mapping, 'ctx')
750 ctx = context.resource(mapping, 'ctx')
782 return ctx.phase()
751 return ctx.phase()
783
752
784 @templatekeyword('rev', requires={'ctx'})
753 @templatekeyword('rev', requires={'ctx'})
785 def showrev(context, mapping):
754 def showrev(context, mapping):
786 """Integer. The repository-local changeset revision number."""
755 """Integer. The repository-local changeset revision number."""
787 ctx = context.resource(mapping, 'ctx')
756 ctx = context.resource(mapping, 'ctx')
788 return scmutil.intrev(ctx)
757 return scmutil.intrev(ctx)
789
758
790 def showrevslist(context, mapping, name, revs):
759 def showrevslist(context, mapping, name, revs):
791 """helper to generate a list of revisions in which a mapped template will
760 """helper to generate a list of revisions in which a mapped template will
792 be evaluated"""
761 be evaluated"""
793 repo = context.resource(mapping, 'repo')
762 repo = context.resource(mapping, 'repo')
794 # revs may be a smartset; don't compute it until f() has to be evaluated
763 # revs may be a smartset; don't compute it until f() has to be evaluated
795 def f():
764 def f():
796 srevs = ['%d' % r for r in revs]
765 srevs = ['%d' % r for r in revs]
797 return _showcompatlist(context, mapping, name, srevs)
766 return _showcompatlist(context, mapping, name, srevs)
798 return _hybrid(f, revs,
767 return _hybrid(f, revs,
799 lambda x: {name: x, 'ctx': repo[x]},
768 lambda x: {name: x, 'ctx': repo[x]},
800 pycompat.identity, keytype=int)
769 pycompat.identity, keytype=int)
801
770
802 @templatekeyword('subrepos', requires={'ctx'})
771 @templatekeyword('subrepos', requires={'ctx'})
803 def showsubrepos(context, mapping):
772 def showsubrepos(context, mapping):
804 """List of strings. Updated subrepositories in the changeset."""
773 """List of strings. Updated subrepositories in the changeset."""
805 ctx = context.resource(mapping, 'ctx')
774 ctx = context.resource(mapping, 'ctx')
806 substate = ctx.substate
775 substate = ctx.substate
807 if not substate:
776 if not substate:
808 return compatlist(context, mapping, 'subrepo', [])
777 return compatlist(context, mapping, 'subrepo', [])
809 psubstate = ctx.p1().substate or {}
778 psubstate = ctx.p1().substate or {}
810 subrepos = []
779 subrepos = []
811 for sub in substate:
780 for sub in substate:
812 if sub not in psubstate or substate[sub] != psubstate[sub]:
781 if sub not in psubstate or substate[sub] != psubstate[sub]:
813 subrepos.append(sub) # modified or newly added in ctx
782 subrepos.append(sub) # modified or newly added in ctx
814 for sub in psubstate:
783 for sub in psubstate:
815 if sub not in substate:
784 if sub not in substate:
816 subrepos.append(sub) # removed in ctx
785 subrepos.append(sub) # removed in ctx
817 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
786 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
818
787
819 # don't remove "showtags" definition, even though namespaces will put
788 # don't remove "showtags" definition, even though namespaces will put
820 # a helper function for "tags" keyword into "keywords" map automatically,
789 # a helper function for "tags" keyword into "keywords" map automatically,
821 # because online help text is built without namespaces initialization
790 # because online help text is built without namespaces initialization
822 @templatekeyword('tags', requires={'repo', 'ctx'})
791 @templatekeyword('tags', requires={'repo', 'ctx'})
823 def showtags(context, mapping):
792 def showtags(context, mapping):
824 """List of strings. Any tags associated with the changeset."""
793 """List of strings. Any tags associated with the changeset."""
825 return shownames(context, mapping, 'tags')
794 return shownames(context, mapping, 'tags')
826
795
827 @templatekeyword('termwidth', requires={'ui'})
796 @templatekeyword('termwidth', requires={'ui'})
828 def showtermwidth(context, mapping):
797 def showtermwidth(context, mapping):
829 """Integer. The width of the current terminal."""
798 """Integer. The width of the current terminal."""
830 ui = context.resource(mapping, 'ui')
799 ui = context.resource(mapping, 'ui')
831 return ui.termwidth()
800 return ui.termwidth()
832
801
833 @templatekeyword('user', requires={'ctx'})
802 @templatekeyword('user', requires={'ctx'})
834 def showuser(context, mapping):
803 def showuser(context, mapping):
835 """String. The unmodified author of the changeset."""
804 """String. The unmodified author of the changeset."""
836 ctx = context.resource(mapping, 'ctx')
805 ctx = context.resource(mapping, 'ctx')
837 return ctx.user()
806 return ctx.user()
838
807
839 @templatekeyword('instabilities', requires={'ctx'})
808 @templatekeyword('instabilities', requires={'ctx'})
840 def showinstabilities(context, mapping):
809 def showinstabilities(context, mapping):
841 """List of strings. Evolution instabilities affecting the changeset.
810 """List of strings. Evolution instabilities affecting the changeset.
842 (EXPERIMENTAL)
811 (EXPERIMENTAL)
843 """
812 """
844 ctx = context.resource(mapping, 'ctx')
813 ctx = context.resource(mapping, 'ctx')
845 return compatlist(context, mapping, 'instability', ctx.instabilities(),
814 return compatlist(context, mapping, 'instability', ctx.instabilities(),
846 plural='instabilities')
815 plural='instabilities')
847
816
848 @templatekeyword('verbosity', requires={'ui'})
817 @templatekeyword('verbosity', requires={'ui'})
849 def showverbosity(context, mapping):
818 def showverbosity(context, mapping):
850 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
819 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
851 or ''."""
820 or ''."""
852 ui = context.resource(mapping, 'ui')
821 ui = context.resource(mapping, 'ui')
853 # see logcmdutil.changesettemplater for priority of these flags
822 # see logcmdutil.changesettemplater for priority of these flags
854 if ui.debugflag:
823 if ui.debugflag:
855 return 'debug'
824 return 'debug'
856 elif ui.quiet:
825 elif ui.quiet:
857 return 'quiet'
826 return 'quiet'
858 elif ui.verbose:
827 elif ui.verbose:
859 return 'verbose'
828 return 'verbose'
860 return ''
829 return ''
861
830
862 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
831 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
863 def showwhyunstable(context, mapping):
832 def showwhyunstable(context, mapping):
864 """List of dicts explaining all instabilities of a changeset.
833 """List of dicts explaining all instabilities of a changeset.
865 (EXPERIMENTAL)
834 (EXPERIMENTAL)
866 """
835 """
867 repo = context.resource(mapping, 'repo')
836 repo = context.resource(mapping, 'repo')
868 ctx = context.resource(mapping, 'ctx')
837 ctx = context.resource(mapping, 'ctx')
869
838
870 def formatnode(ctx):
839 def formatnode(ctx):
871 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
840 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
872
841
873 entries = obsutil.whyunstable(repo, ctx)
842 entries = obsutil.whyunstable(repo, ctx)
874
843
875 for entry in entries:
844 for entry in entries:
876 if entry.get('divergentnodes'):
845 if entry.get('divergentnodes'):
877 dnodes = entry['divergentnodes']
846 dnodes = entry['divergentnodes']
878 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
847 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
879 lambda x: {'ctx': repo[x]},
848 lambda x: {'ctx': repo[x]},
880 lambda x: formatnode(repo[x]))
849 lambda x: formatnode(repo[x]))
881 entry['divergentnodes'] = dnhybrid
850 entry['divergentnodes'] = dnhybrid
882
851
883 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
852 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
884 '{reason} {node|short}')
853 '{reason} {node|short}')
885 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
854 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
886
855
887 def loadkeyword(ui, extname, registrarobj):
856 def loadkeyword(ui, extname, registrarobj):
888 """Load template keyword from specified registrarobj
857 """Load template keyword from specified registrarobj
889 """
858 """
890 for name, func in registrarobj._table.iteritems():
859 for name, func in registrarobj._table.iteritems():
891 keywords[name] = func
860 keywords[name] = func
892
861
893 # tell hggettext to extract docstrings from these functions:
862 # tell hggettext to extract docstrings from these functions:
894 i18nfunctions = keywords.values()
863 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now