##// END OF EJS Templates
bundlespec: merge the contentopts and params dictionnary...
marmoute -
r50220:bf66f7a1 default
parent child Browse files
Show More
@@ -1,443 +1,462 b''
1 1 # bundlecaches.py - utility to deal with pre-computed bundle for servers
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 import collections
7
6 8 from .i18n import _
7 9
8 10 from .thirdparty import attr
9 11
10 12 from . import (
11 13 error,
12 14 requirements as requirementsmod,
13 15 sslutil,
14 16 util,
15 17 )
16 18 from .utils import stringutil
17 19
18 20 urlreq = util.urlreq
19 21
20 22 CB_MANIFEST_FILE = b'clonebundles.manifest'
21 23
22 24
23 25 @attr.s
24 26 class bundlespec:
25 27 compression = attr.ib()
26 28 wirecompression = attr.ib()
27 29 version = attr.ib()
28 30 wireversion = attr.ib()
29 params = attr.ib()
30 contentopts = attr.ib()
31 # parameters explicitly overwritten by the config or the specification
32 _explicit_params = attr.ib()
33 # default parameter for the version
34 #
35 # Keeping it separated is useful to check what was actually overwritten.
36 _default_opts = attr.ib()
37
38 @property
39 def params(self):
40 return collections.ChainMap(self._explicit_params, self._default_opts)
41
42 @property
43 def contentopts(self):
44 # kept for Backward Compatibility concerns.
45 return self.params
46
47 def set_param(self, key, value):
48 """overwrite a parameter value"""
49 self._explicit_params[key] = value
31 50
32 51
33 52 # Maps bundle version human names to changegroup versions.
34 53 _bundlespeccgversions = {
35 54 b'v1': b'01',
36 55 b'v2': b'02',
37 56 b'packed1': b's1',
38 57 b'bundle2': b'02', # legacy
39 58 }
40 59
41 60 # Maps bundle version with content opts to choose which part to bundle
42 61 _bundlespeccontentopts = {
43 62 b'v1': {
44 63 b'changegroup': True,
45 64 b'cg.version': b'01',
46 65 b'obsolescence': False,
47 66 b'phases': False,
48 67 b'tagsfnodescache': False,
49 68 b'revbranchcache': False,
50 69 },
51 70 b'v2': {
52 71 b'changegroup': True,
53 72 b'cg.version': b'02',
54 73 b'obsolescence': False,
55 74 b'phases': False,
56 75 b'tagsfnodescache': True,
57 76 b'revbranchcache': True,
58 77 },
59 78 b'streamv2': {
60 79 b'changegroup': False,
61 80 b'cg.version': b'02',
62 81 b'obsolescence': False,
63 82 b'phases': False,
64 83 b"streamv2": True,
65 84 b'tagsfnodescache': False,
66 85 b'revbranchcache': False,
67 86 },
68 87 b'packed1': {
69 88 b'cg.version': b's1',
70 89 },
71 90 b'bundle2': { # legacy
72 91 b'cg.version': b'02',
73 92 },
74 93 }
75 94 _bundlespeccontentopts[b'bundle2'] = _bundlespeccontentopts[b'v2']
76 95
77 96 _bundlespecvariants = {b"streamv2": {}}
78 97
79 98 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
80 99 _bundlespecv1compengines = {b'gzip', b'bzip2', b'none'}
81 100
82 101
83 102 def parsebundlespec(repo, spec, strict=True):
84 103 """Parse a bundle string specification into parts.
85 104
86 105 Bundle specifications denote a well-defined bundle/exchange format.
87 106 The content of a given specification should not change over time in
88 107 order to ensure that bundles produced by a newer version of Mercurial are
89 108 readable from an older version.
90 109
91 110 The string currently has the form:
92 111
93 112 <compression>-<type>[;<parameter0>[;<parameter1>]]
94 113
95 114 Where <compression> is one of the supported compression formats
96 115 and <type> is (currently) a version string. A ";" can follow the type and
97 116 all text afterwards is interpreted as URI encoded, ";" delimited key=value
98 117 pairs.
99 118
100 119 If ``strict`` is True (the default) <compression> is required. Otherwise,
101 120 it is optional.
102 121
103 122 Returns a bundlespec object of (compression, version, parameters).
104 123 Compression will be ``None`` if not in strict mode and a compression isn't
105 124 defined.
106 125
107 126 An ``InvalidBundleSpecification`` is raised when the specification is
108 127 not syntactically well formed.
109 128
110 129 An ``UnsupportedBundleSpecification`` is raised when the compression or
111 130 bundle type/version is not recognized.
112 131
113 132 Note: this function will likely eventually return a more complex data
114 133 structure, including bundle2 part information.
115 134 """
116 135
117 136 def parseparams(s):
118 137 if b';' not in s:
119 138 return s, {}
120 139
121 140 params = {}
122 141 version, paramstr = s.split(b';', 1)
123 142
124 143 for p in paramstr.split(b';'):
125 144 if b'=' not in p:
126 145 raise error.InvalidBundleSpecification(
127 146 _(
128 147 b'invalid bundle specification: '
129 148 b'missing "=" in parameter: %s'
130 149 )
131 150 % p
132 151 )
133 152
134 153 key, value = p.split(b'=', 1)
135 154 key = urlreq.unquote(key)
136 155 value = urlreq.unquote(value)
137 156 params[key] = value
138 157
139 158 return version, params
140 159
141 160 if strict and b'-' not in spec:
142 161 raise error.InvalidBundleSpecification(
143 162 _(
144 163 b'invalid bundle specification; '
145 164 b'must be prefixed with compression: %s'
146 165 )
147 166 % spec
148 167 )
149 168
150 169 if b'-' in spec:
151 170 compression, version = spec.split(b'-', 1)
152 171
153 172 if compression not in util.compengines.supportedbundlenames:
154 173 raise error.UnsupportedBundleSpecification(
155 174 _(b'%s compression is not supported') % compression
156 175 )
157 176
158 177 version, params = parseparams(version)
159 178
160 179 if version not in _bundlespeccontentopts:
161 180 raise error.UnsupportedBundleSpecification(
162 181 _(b'%s is not a recognized bundle version') % version
163 182 )
164 183 else:
165 184 # Value could be just the compression or just the version, in which
166 185 # case some defaults are assumed (but only when not in strict mode).
167 186 assert not strict
168 187
169 188 spec, params = parseparams(spec)
170 189
171 190 if spec in util.compengines.supportedbundlenames:
172 191 compression = spec
173 192 version = b'v1'
174 193 # Generaldelta repos require v2.
175 194 if requirementsmod.GENERALDELTA_REQUIREMENT in repo.requirements:
176 195 version = b'v2'
177 196 elif requirementsmod.REVLOGV2_REQUIREMENT in repo.requirements:
178 197 version = b'v2'
179 198 # Modern compression engines require v2.
180 199 if compression not in _bundlespecv1compengines:
181 200 version = b'v2'
182 201 elif spec in _bundlespeccontentopts:
183 202 if spec == b'packed1':
184 203 compression = b'none'
185 204 else:
186 205 compression = b'bzip2'
187 206 version = spec
188 207 else:
189 208 raise error.UnsupportedBundleSpecification(
190 209 _(b'%s is not a recognized bundle specification') % spec
191 210 )
192 211
193 212 # Bundle version 1 only supports a known set of compression engines.
194 213 if version == b'v1' and compression not in _bundlespecv1compengines:
195 214 raise error.UnsupportedBundleSpecification(
196 215 _(b'compression engine %s is not supported on v1 bundles')
197 216 % compression
198 217 )
199 218
200 219 # The specification for packed1 can optionally declare the data formats
201 220 # required to apply it. If we see this metadata, compare against what the
202 221 # repo supports and error if the bundle isn't compatible.
203 222 if version == b'packed1' and b'requirements' in params:
204 223 requirements = set(params[b'requirements'].split(b','))
205 224 missingreqs = requirements - requirementsmod.STREAM_FIXED_REQUIREMENTS
206 225 if missingreqs:
207 226 raise error.UnsupportedBundleSpecification(
208 227 _(b'missing support for repository features: %s')
209 228 % b', '.join(sorted(missingreqs))
210 229 )
211 230
212 231 # Compute contentopts based on the version
213 232 if b"stream" in params and params[b"stream"] == b"v2":
214 233 # That case is fishy as this mostly derails the version selection
215 234 # mechanism. `stream` bundles are quite specific and used differently
216 235 # as "normal" bundles.
217 236 #
218 237 # So we are pinning this to "v2", as this will likely be
219 238 # compatible forever. (see the next conditional).
220 239 #
221 240 # (we should probably define a cleaner way to do this and raise a
222 241 # warning when the old way is encounter)
223 242 version = b"streamv2"
224 243 contentopts = _bundlespeccontentopts.get(version, {}).copy()
225 244 if version == b"streamv2":
226 245 # streamv2 have been reported as "v2" for a while.
227 246 version = b"v2"
228 247
229 248 engine = util.compengines.forbundlename(compression)
230 249 compression, wirecompression = engine.bundletype()
231 250 wireversion = _bundlespeccontentopts[version][b'cg.version']
232 251
233 252 return bundlespec(
234 253 compression, wirecompression, version, wireversion, params, contentopts
235 254 )
236 255
237 256
238 257 def parseclonebundlesmanifest(repo, s):
239 258 """Parses the raw text of a clone bundles manifest.
240 259
241 260 Returns a list of dicts. The dicts have a ``URL`` key corresponding
242 261 to the URL and other keys are the attributes for the entry.
243 262 """
244 263 m = []
245 264 for line in s.splitlines():
246 265 fields = line.split()
247 266 if not fields:
248 267 continue
249 268 attrs = {b'URL': fields[0]}
250 269 for rawattr in fields[1:]:
251 270 key, value = rawattr.split(b'=', 1)
252 271 key = util.urlreq.unquote(key)
253 272 value = util.urlreq.unquote(value)
254 273 attrs[key] = value
255 274
256 275 # Parse BUNDLESPEC into components. This makes client-side
257 276 # preferences easier to specify since you can prefer a single
258 277 # component of the BUNDLESPEC.
259 278 if key == b'BUNDLESPEC':
260 279 try:
261 280 bundlespec = parsebundlespec(repo, value)
262 281 attrs[b'COMPRESSION'] = bundlespec.compression
263 282 attrs[b'VERSION'] = bundlespec.version
264 283 except error.InvalidBundleSpecification:
265 284 pass
266 285 except error.UnsupportedBundleSpecification:
267 286 pass
268 287
269 288 m.append(attrs)
270 289
271 290 return m
272 291
273 292
274 293 def isstreamclonespec(bundlespec):
275 294 # Stream clone v1
276 295 if bundlespec.wirecompression == b'UN' and bundlespec.wireversion == b's1':
277 296 return True
278 297
279 298 # Stream clone v2
280 299 if (
281 300 bundlespec.wirecompression == b'UN'
282 301 and bundlespec.wireversion == b'02'
283 302 and bundlespec.contentopts.get(b'streamv2')
284 303 ):
285 304 return True
286 305
287 306 return False
288 307
289 308
290 309 def filterclonebundleentries(repo, entries, streamclonerequested=False):
291 310 """Remove incompatible clone bundle manifest entries.
292 311
293 312 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
294 313 and returns a new list consisting of only the entries that this client
295 314 should be able to apply.
296 315
297 316 There is no guarantee we'll be able to apply all returned entries because
298 317 the metadata we use to filter on may be missing or wrong.
299 318 """
300 319 newentries = []
301 320 for entry in entries:
302 321 spec = entry.get(b'BUNDLESPEC')
303 322 if spec:
304 323 try:
305 324 bundlespec = parsebundlespec(repo, spec, strict=True)
306 325
307 326 # If a stream clone was requested, filter out non-streamclone
308 327 # entries.
309 328 if streamclonerequested and not isstreamclonespec(bundlespec):
310 329 repo.ui.debug(
311 330 b'filtering %s because not a stream clone\n'
312 331 % entry[b'URL']
313 332 )
314 333 continue
315 334
316 335 except error.InvalidBundleSpecification as e:
317 336 repo.ui.debug(stringutil.forcebytestr(e) + b'\n')
318 337 continue
319 338 except error.UnsupportedBundleSpecification as e:
320 339 repo.ui.debug(
321 340 b'filtering %s because unsupported bundle '
322 341 b'spec: %s\n' % (entry[b'URL'], stringutil.forcebytestr(e))
323 342 )
324 343 continue
325 344 # If we don't have a spec and requested a stream clone, we don't know
326 345 # what the entry is so don't attempt to apply it.
327 346 elif streamclonerequested:
328 347 repo.ui.debug(
329 348 b'filtering %s because cannot determine if a stream '
330 349 b'clone bundle\n' % entry[b'URL']
331 350 )
332 351 continue
333 352
334 353 if b'REQUIRESNI' in entry and not sslutil.hassni:
335 354 repo.ui.debug(
336 355 b'filtering %s because SNI not supported\n' % entry[b'URL']
337 356 )
338 357 continue
339 358
340 359 if b'REQUIREDRAM' in entry:
341 360 try:
342 361 requiredram = util.sizetoint(entry[b'REQUIREDRAM'])
343 362 except error.ParseError:
344 363 repo.ui.debug(
345 364 b'filtering %s due to a bad REQUIREDRAM attribute\n'
346 365 % entry[b'URL']
347 366 )
348 367 continue
349 368 actualram = repo.ui.estimatememory()
350 369 if actualram is not None and actualram * 0.66 < requiredram:
351 370 repo.ui.debug(
352 371 b'filtering %s as it needs more than 2/3 of system memory\n'
353 372 % entry[b'URL']
354 373 )
355 374 continue
356 375
357 376 newentries.append(entry)
358 377
359 378 return newentries
360 379
361 380
362 381 class clonebundleentry:
363 382 """Represents an item in a clone bundles manifest.
364 383
365 384 This rich class is needed to support sorting since sorted() in Python 3
366 385 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
367 386 won't work.
368 387 """
369 388
370 389 def __init__(self, value, prefers):
371 390 self.value = value
372 391 self.prefers = prefers
373 392
374 393 def _cmp(self, other):
375 394 for prefkey, prefvalue in self.prefers:
376 395 avalue = self.value.get(prefkey)
377 396 bvalue = other.value.get(prefkey)
378 397
379 398 # Special case for b missing attribute and a matches exactly.
380 399 if avalue is not None and bvalue is None and avalue == prefvalue:
381 400 return -1
382 401
383 402 # Special case for a missing attribute and b matches exactly.
384 403 if bvalue is not None and avalue is None and bvalue == prefvalue:
385 404 return 1
386 405
387 406 # We can't compare unless attribute present on both.
388 407 if avalue is None or bvalue is None:
389 408 continue
390 409
391 410 # Same values should fall back to next attribute.
392 411 if avalue == bvalue:
393 412 continue
394 413
395 414 # Exact matches come first.
396 415 if avalue == prefvalue:
397 416 return -1
398 417 if bvalue == prefvalue:
399 418 return 1
400 419
401 420 # Fall back to next attribute.
402 421 continue
403 422
404 423 # If we got here we couldn't sort by attributes and prefers. Fall
405 424 # back to index order.
406 425 return 0
407 426
408 427 def __lt__(self, other):
409 428 return self._cmp(other) < 0
410 429
411 430 def __gt__(self, other):
412 431 return self._cmp(other) > 0
413 432
414 433 def __eq__(self, other):
415 434 return self._cmp(other) == 0
416 435
417 436 def __le__(self, other):
418 437 return self._cmp(other) <= 0
419 438
420 439 def __ge__(self, other):
421 440 return self._cmp(other) >= 0
422 441
423 442 def __ne__(self, other):
424 443 return self._cmp(other) != 0
425 444
426 445
427 446 def sortclonebundleentries(ui, entries):
428 447 prefers = ui.configlist(b'ui', b'clonebundleprefers')
429 448 if not prefers:
430 449 return list(entries)
431 450
432 451 def _split(p):
433 452 if b'=' not in p:
434 453 hint = _(b"each comma separated item should be key=value pairs")
435 454 raise error.Abort(
436 455 _(b"invalid ui.clonebundleprefers item: %s") % p, hint=hint
437 456 )
438 457 return p.split(b'=', 1)
439 458
440 459 prefers = [_split(p) for p in prefers]
441 460
442 461 items = sorted(clonebundleentry(v, prefers) for v in entries)
443 462 return [i.value for i in items]
@@ -1,7948 +1,7946 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import os
10 10 import re
11 11 import sys
12 12
13 13 from .i18n import _
14 14 from .node import (
15 15 hex,
16 16 nullrev,
17 17 short,
18 18 wdirrev,
19 19 )
20 20 from .pycompat import open
21 21 from . import (
22 22 archival,
23 23 bookmarks,
24 24 bundle2,
25 25 bundlecaches,
26 26 changegroup,
27 27 cmdutil,
28 28 copies,
29 29 debugcommands as debugcommandsmod,
30 30 destutil,
31 31 dirstateguard,
32 32 discovery,
33 33 encoding,
34 34 error,
35 35 exchange,
36 36 extensions,
37 37 filemerge,
38 38 formatter,
39 39 graphmod,
40 40 grep as grepmod,
41 41 hbisect,
42 42 help,
43 43 hg,
44 44 logcmdutil,
45 45 merge as mergemod,
46 46 mergestate as mergestatemod,
47 47 narrowspec,
48 48 obsolete,
49 49 obsutil,
50 50 patch,
51 51 phases,
52 52 pycompat,
53 53 rcutil,
54 54 registrar,
55 55 requirements,
56 56 revsetlang,
57 57 rewriteutil,
58 58 scmutil,
59 59 server,
60 60 shelve as shelvemod,
61 61 state as statemod,
62 62 streamclone,
63 63 tags as tagsmod,
64 64 ui as uimod,
65 65 util,
66 66 verify as verifymod,
67 67 vfs as vfsmod,
68 68 wireprotoserver,
69 69 )
70 70 from .utils import (
71 71 dateutil,
72 72 stringutil,
73 73 urlutil,
74 74 )
75 75
76 76 table = {}
77 77 table.update(debugcommandsmod.command._table)
78 78
79 79 command = registrar.command(table)
80 80 INTENT_READONLY = registrar.INTENT_READONLY
81 81
82 82 # common command options
83 83
84 84 globalopts = [
85 85 (
86 86 b'R',
87 87 b'repository',
88 88 b'',
89 89 _(b'repository root directory or name of overlay bundle file'),
90 90 _(b'REPO'),
91 91 ),
92 92 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
93 93 (
94 94 b'y',
95 95 b'noninteractive',
96 96 None,
97 97 _(
98 98 b'do not prompt, automatically pick the first choice for all prompts'
99 99 ),
100 100 ),
101 101 (b'q', b'quiet', None, _(b'suppress output')),
102 102 (b'v', b'verbose', None, _(b'enable additional output')),
103 103 (
104 104 b'',
105 105 b'color',
106 106 b'',
107 107 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
108 108 # and should not be translated
109 109 _(b"when to colorize (boolean, always, auto, never, or debug)"),
110 110 _(b'TYPE'),
111 111 ),
112 112 (
113 113 b'',
114 114 b'config',
115 115 [],
116 116 _(b'set/override config option (use \'section.name=value\')'),
117 117 _(b'CONFIG'),
118 118 ),
119 119 (b'', b'debug', None, _(b'enable debugging output')),
120 120 (b'', b'debugger', None, _(b'start debugger')),
121 121 (
122 122 b'',
123 123 b'encoding',
124 124 encoding.encoding,
125 125 _(b'set the charset encoding'),
126 126 _(b'ENCODE'),
127 127 ),
128 128 (
129 129 b'',
130 130 b'encodingmode',
131 131 encoding.encodingmode,
132 132 _(b'set the charset encoding mode'),
133 133 _(b'MODE'),
134 134 ),
135 135 (b'', b'traceback', None, _(b'always print a traceback on exception')),
136 136 (b'', b'time', None, _(b'time how long the command takes')),
137 137 (b'', b'profile', None, _(b'print command execution profile')),
138 138 (b'', b'version', None, _(b'output version information and exit')),
139 139 (b'h', b'help', None, _(b'display help and exit')),
140 140 (b'', b'hidden', False, _(b'consider hidden changesets')),
141 141 (
142 142 b'',
143 143 b'pager',
144 144 b'auto',
145 145 _(b"when to paginate (boolean, always, auto, or never)"),
146 146 _(b'TYPE'),
147 147 ),
148 148 ]
149 149
150 150 dryrunopts = cmdutil.dryrunopts
151 151 remoteopts = cmdutil.remoteopts
152 152 walkopts = cmdutil.walkopts
153 153 commitopts = cmdutil.commitopts
154 154 commitopts2 = cmdutil.commitopts2
155 155 commitopts3 = cmdutil.commitopts3
156 156 formatteropts = cmdutil.formatteropts
157 157 templateopts = cmdutil.templateopts
158 158 logopts = cmdutil.logopts
159 159 diffopts = cmdutil.diffopts
160 160 diffwsopts = cmdutil.diffwsopts
161 161 diffopts2 = cmdutil.diffopts2
162 162 mergetoolopts = cmdutil.mergetoolopts
163 163 similarityopts = cmdutil.similarityopts
164 164 subrepoopts = cmdutil.subrepoopts
165 165 debugrevlogopts = cmdutil.debugrevlogopts
166 166
167 167 # Commands start here, listed alphabetically
168 168
169 169
170 170 @command(
171 171 b'abort',
172 172 dryrunopts,
173 173 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
174 174 helpbasic=True,
175 175 )
176 176 def abort(ui, repo, **opts):
177 177 """abort an unfinished operation (EXPERIMENTAL)
178 178
179 179 Aborts a multistep operation like graft, histedit, rebase, merge,
180 180 and unshelve if they are in an unfinished state.
181 181
182 182 use --dry-run/-n to dry run the command.
183 183 """
184 184 dryrun = opts.get('dry_run')
185 185 abortstate = cmdutil.getunfinishedstate(repo)
186 186 if not abortstate:
187 187 raise error.StateError(_(b'no operation in progress'))
188 188 if not abortstate.abortfunc:
189 189 raise error.InputError(
190 190 (
191 191 _(b"%s in progress but does not support 'hg abort'")
192 192 % (abortstate._opname)
193 193 ),
194 194 hint=abortstate.hint(),
195 195 )
196 196 if dryrun:
197 197 ui.status(
198 198 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
199 199 )
200 200 return
201 201 return abortstate.abortfunc(ui, repo)
202 202
203 203
204 204 @command(
205 205 b'add',
206 206 walkopts + subrepoopts + dryrunopts,
207 207 _(b'[OPTION]... [FILE]...'),
208 208 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
209 209 helpbasic=True,
210 210 inferrepo=True,
211 211 )
212 212 def add(ui, repo, *pats, **opts):
213 213 """add the specified files on the next commit
214 214
215 215 Schedule files to be version controlled and added to the
216 216 repository.
217 217
218 218 The files will be added to the repository at the next commit. To
219 219 undo an add before that, see :hg:`forget`.
220 220
221 221 If no names are given, add all files to the repository (except
222 222 files matching ``.hgignore``).
223 223
224 224 .. container:: verbose
225 225
226 226 Examples:
227 227
228 228 - New (unknown) files are added
229 229 automatically by :hg:`add`::
230 230
231 231 $ ls
232 232 foo.c
233 233 $ hg status
234 234 ? foo.c
235 235 $ hg add
236 236 adding foo.c
237 237 $ hg status
238 238 A foo.c
239 239
240 240 - Specific files to be added can be specified::
241 241
242 242 $ ls
243 243 bar.c foo.c
244 244 $ hg status
245 245 ? bar.c
246 246 ? foo.c
247 247 $ hg add bar.c
248 248 $ hg status
249 249 A bar.c
250 250 ? foo.c
251 251
252 252 Returns 0 if all files are successfully added.
253 253 """
254 254
255 255 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
256 256 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
257 257 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
258 258 return rejected and 1 or 0
259 259
260 260
261 261 @command(
262 262 b'addremove',
263 263 similarityopts + subrepoopts + walkopts + dryrunopts,
264 264 _(b'[OPTION]... [FILE]...'),
265 265 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
266 266 inferrepo=True,
267 267 )
268 268 def addremove(ui, repo, *pats, **opts):
269 269 """add all new files, delete all missing files
270 270
271 271 Add all new files and remove all missing files from the
272 272 repository.
273 273
274 274 Unless names are given, new files are ignored if they match any of
275 275 the patterns in ``.hgignore``. As with add, these changes take
276 276 effect at the next commit.
277 277
278 278 Use the -s/--similarity option to detect renamed files. This
279 279 option takes a percentage between 0 (disabled) and 100 (files must
280 280 be identical) as its parameter. With a parameter greater than 0,
281 281 this compares every removed file with every added file and records
282 282 those similar enough as renames. Detecting renamed files this way
283 283 can be expensive. After using this option, :hg:`status -C` can be
284 284 used to check which files were identified as moved or renamed. If
285 285 not specified, -s/--similarity defaults to 100 and only renames of
286 286 identical files are detected.
287 287
288 288 .. container:: verbose
289 289
290 290 Examples:
291 291
292 292 - A number of files (bar.c and foo.c) are new,
293 293 while foobar.c has been removed (without using :hg:`remove`)
294 294 from the repository::
295 295
296 296 $ ls
297 297 bar.c foo.c
298 298 $ hg status
299 299 ! foobar.c
300 300 ? bar.c
301 301 ? foo.c
302 302 $ hg addremove
303 303 adding bar.c
304 304 adding foo.c
305 305 removing foobar.c
306 306 $ hg status
307 307 A bar.c
308 308 A foo.c
309 309 R foobar.c
310 310
311 311 - A file foobar.c was moved to foo.c without using :hg:`rename`.
312 312 Afterwards, it was edited slightly::
313 313
314 314 $ ls
315 315 foo.c
316 316 $ hg status
317 317 ! foobar.c
318 318 ? foo.c
319 319 $ hg addremove --similarity 90
320 320 removing foobar.c
321 321 adding foo.c
322 322 recording removal of foobar.c as rename to foo.c (94% similar)
323 323 $ hg status -C
324 324 A foo.c
325 325 foobar.c
326 326 R foobar.c
327 327
328 328 Returns 0 if all files are successfully added.
329 329 """
330 330 opts = pycompat.byteskwargs(opts)
331 331 if not opts.get(b'similarity'):
332 332 opts[b'similarity'] = b'100'
333 333 matcher = scmutil.match(repo[None], pats, opts)
334 334 relative = scmutil.anypats(pats, opts)
335 335 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
336 336 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
337 337
338 338
339 339 @command(
340 340 b'annotate|blame',
341 341 [
342 342 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
343 343 (
344 344 b'',
345 345 b'follow',
346 346 None,
347 347 _(b'follow copies/renames and list the filename (DEPRECATED)'),
348 348 ),
349 349 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
350 350 (b'a', b'text', None, _(b'treat all files as text')),
351 351 (b'u', b'user', None, _(b'list the author (long with -v)')),
352 352 (b'f', b'file', None, _(b'list the filename')),
353 353 (b'd', b'date', None, _(b'list the date (short with -q)')),
354 354 (b'n', b'number', None, _(b'list the revision number (default)')),
355 355 (b'c', b'changeset', None, _(b'list the changeset')),
356 356 (
357 357 b'l',
358 358 b'line-number',
359 359 None,
360 360 _(b'show line number at the first appearance'),
361 361 ),
362 362 (
363 363 b'',
364 364 b'skip',
365 365 [],
366 366 _(b'revset to not display (EXPERIMENTAL)'),
367 367 _(b'REV'),
368 368 ),
369 369 ]
370 370 + diffwsopts
371 371 + walkopts
372 372 + formatteropts,
373 373 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
374 374 helpcategory=command.CATEGORY_FILE_CONTENTS,
375 375 helpbasic=True,
376 376 inferrepo=True,
377 377 )
378 378 def annotate(ui, repo, *pats, **opts):
379 379 """show changeset information by line for each file
380 380
381 381 List changes in files, showing the revision id responsible for
382 382 each line.
383 383
384 384 This command is useful for discovering when a change was made and
385 385 by whom.
386 386
387 387 If you include --file, --user, or --date, the revision number is
388 388 suppressed unless you also include --number.
389 389
390 390 Without the -a/--text option, annotate will avoid processing files
391 391 it detects as binary. With -a, annotate will annotate the file
392 392 anyway, although the results will probably be neither useful
393 393 nor desirable.
394 394
395 395 .. container:: verbose
396 396
397 397 Template:
398 398
399 399 The following keywords are supported in addition to the common template
400 400 keywords and functions. See also :hg:`help templates`.
401 401
402 402 :lines: List of lines with annotation data.
403 403 :path: String. Repository-absolute path of the specified file.
404 404
405 405 And each entry of ``{lines}`` provides the following sub-keywords in
406 406 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
407 407
408 408 :line: String. Line content.
409 409 :lineno: Integer. Line number at that revision.
410 410 :path: String. Repository-absolute path of the file at that revision.
411 411
412 412 See :hg:`help templates.operators` for the list expansion syntax.
413 413
414 414 Returns 0 on success.
415 415 """
416 416 opts = pycompat.byteskwargs(opts)
417 417 if not pats:
418 418 raise error.InputError(
419 419 _(b'at least one filename or pattern is required')
420 420 )
421 421
422 422 if opts.get(b'follow'):
423 423 # --follow is deprecated and now just an alias for -f/--file
424 424 # to mimic the behavior of Mercurial before version 1.5
425 425 opts[b'file'] = True
426 426
427 427 if (
428 428 not opts.get(b'user')
429 429 and not opts.get(b'changeset')
430 430 and not opts.get(b'date')
431 431 and not opts.get(b'file')
432 432 ):
433 433 opts[b'number'] = True
434 434
435 435 linenumber = opts.get(b'line_number') is not None
436 436 if (
437 437 linenumber
438 438 and (not opts.get(b'changeset'))
439 439 and (not opts.get(b'number'))
440 440 ):
441 441 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
442 442
443 443 rev = opts.get(b'rev')
444 444 if rev:
445 445 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
446 446 ctx = logcmdutil.revsingle(repo, rev)
447 447
448 448 ui.pager(b'annotate')
449 449 rootfm = ui.formatter(b'annotate', opts)
450 450 if ui.debugflag:
451 451 shorthex = pycompat.identity
452 452 else:
453 453
454 454 def shorthex(h):
455 455 return h[:12]
456 456
457 457 if ui.quiet:
458 458 datefunc = dateutil.shortdate
459 459 else:
460 460 datefunc = dateutil.datestr
461 461 if ctx.rev() is None:
462 462 if opts.get(b'changeset'):
463 463 # omit "+" suffix which is appended to node hex
464 464 def formatrev(rev):
465 465 if rev == wdirrev:
466 466 return b'%d' % ctx.p1().rev()
467 467 else:
468 468 return b'%d' % rev
469 469
470 470 else:
471 471
472 472 def formatrev(rev):
473 473 if rev == wdirrev:
474 474 return b'%d+' % ctx.p1().rev()
475 475 else:
476 476 return b'%d ' % rev
477 477
478 478 def formathex(h):
479 479 if h == repo.nodeconstants.wdirhex:
480 480 return b'%s+' % shorthex(hex(ctx.p1().node()))
481 481 else:
482 482 return b'%s ' % shorthex(h)
483 483
484 484 else:
485 485 formatrev = b'%d'.__mod__
486 486 formathex = shorthex
487 487
488 488 opmap = [
489 489 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
490 490 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
491 491 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
492 492 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
493 493 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
494 494 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
495 495 ]
496 496 opnamemap = {
497 497 b'rev': b'number',
498 498 b'node': b'changeset',
499 499 b'path': b'file',
500 500 b'lineno': b'line_number',
501 501 }
502 502
503 503 if rootfm.isplain():
504 504
505 505 def makefunc(get, fmt):
506 506 return lambda x: fmt(get(x))
507 507
508 508 else:
509 509
510 510 def makefunc(get, fmt):
511 511 return get
512 512
513 513 datahint = rootfm.datahint()
514 514 funcmap = [
515 515 (makefunc(get, fmt), sep)
516 516 for fn, sep, get, fmt in opmap
517 517 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
518 518 ]
519 519 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
520 520 fields = b' '.join(
521 521 fn
522 522 for fn, sep, get, fmt in opmap
523 523 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
524 524 )
525 525
526 526 def bad(x, y):
527 527 raise error.InputError(b"%s: %s" % (x, y))
528 528
529 529 m = scmutil.match(ctx, pats, opts, badfn=bad)
530 530
531 531 follow = not opts.get(b'no_follow')
532 532 diffopts = patch.difffeatureopts(
533 533 ui, opts, section=b'annotate', whitespace=True
534 534 )
535 535 skiprevs = opts.get(b'skip')
536 536 if skiprevs:
537 537 skiprevs = logcmdutil.revrange(repo, skiprevs)
538 538
539 539 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
540 540 for abs in ctx.walk(m):
541 541 fctx = ctx[abs]
542 542 rootfm.startitem()
543 543 rootfm.data(path=abs)
544 544 if not opts.get(b'text') and fctx.isbinary():
545 545 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
546 546 continue
547 547
548 548 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
549 549 lines = fctx.annotate(
550 550 follow=follow, skiprevs=skiprevs, diffopts=diffopts
551 551 )
552 552 if not lines:
553 553 fm.end()
554 554 continue
555 555 formats = []
556 556 pieces = []
557 557
558 558 for f, sep in funcmap:
559 559 l = [f(n) for n in lines]
560 560 if fm.isplain():
561 561 sizes = [encoding.colwidth(x) for x in l]
562 562 ml = max(sizes)
563 563 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
564 564 else:
565 565 formats.append([b'%s'] * len(l))
566 566 pieces.append(l)
567 567
568 568 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
569 569 fm.startitem()
570 570 fm.context(fctx=n.fctx)
571 571 fm.write(fields, b"".join(f), *p)
572 572 if n.skip:
573 573 fmt = b"* %s"
574 574 else:
575 575 fmt = b": %s"
576 576 fm.write(b'line', fmt, n.text)
577 577
578 578 if not lines[-1].text.endswith(b'\n'):
579 579 fm.plain(b'\n')
580 580 fm.end()
581 581
582 582 rootfm.end()
583 583
584 584
585 585 @command(
586 586 b'archive',
587 587 [
588 588 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
589 589 (
590 590 b'p',
591 591 b'prefix',
592 592 b'',
593 593 _(b'directory prefix for files in archive'),
594 594 _(b'PREFIX'),
595 595 ),
596 596 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
597 597 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
598 598 ]
599 599 + subrepoopts
600 600 + walkopts,
601 601 _(b'[OPTION]... DEST'),
602 602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
603 603 )
604 604 def archive(ui, repo, dest, **opts):
605 605 """create an unversioned archive of a repository revision
606 606
607 607 By default, the revision used is the parent of the working
608 608 directory; use -r/--rev to specify a different revision.
609 609
610 610 The archive type is automatically detected based on file
611 611 extension (to override, use -t/--type).
612 612
613 613 .. container:: verbose
614 614
615 615 Examples:
616 616
617 617 - create a zip file containing the 1.0 release::
618 618
619 619 hg archive -r 1.0 project-1.0.zip
620 620
621 621 - create a tarball excluding .hg files::
622 622
623 623 hg archive project.tar.gz -X ".hg*"
624 624
625 625 Valid types are:
626 626
627 627 :``files``: a directory full of files (default)
628 628 :``tar``: tar archive, uncompressed
629 629 :``tbz2``: tar archive, compressed using bzip2
630 630 :``tgz``: tar archive, compressed using gzip
631 631 :``txz``: tar archive, compressed using lzma (only in Python 3)
632 632 :``uzip``: zip archive, uncompressed
633 633 :``zip``: zip archive, compressed using deflate
634 634
635 635 The exact name of the destination archive or directory is given
636 636 using a format string; see :hg:`help export` for details.
637 637
638 638 Each member added to an archive file has a directory prefix
639 639 prepended. Use -p/--prefix to specify a format string for the
640 640 prefix. The default is the basename of the archive, with suffixes
641 641 removed.
642 642
643 643 Returns 0 on success.
644 644 """
645 645
646 646 opts = pycompat.byteskwargs(opts)
647 647 rev = opts.get(b'rev')
648 648 if rev:
649 649 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
650 650 ctx = logcmdutil.revsingle(repo, rev)
651 651 if not ctx:
652 652 raise error.InputError(
653 653 _(b'no working directory: please specify a revision')
654 654 )
655 655 node = ctx.node()
656 656 dest = cmdutil.makefilename(ctx, dest)
657 657 if os.path.realpath(dest) == repo.root:
658 658 raise error.InputError(_(b'repository root cannot be destination'))
659 659
660 660 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
661 661 prefix = opts.get(b'prefix')
662 662
663 663 if dest == b'-':
664 664 if kind == b'files':
665 665 raise error.InputError(_(b'cannot archive plain files to stdout'))
666 666 dest = cmdutil.makefileobj(ctx, dest)
667 667 if not prefix:
668 668 prefix = os.path.basename(repo.root) + b'-%h'
669 669
670 670 prefix = cmdutil.makefilename(ctx, prefix)
671 671 match = scmutil.match(ctx, [], opts)
672 672 archival.archive(
673 673 repo,
674 674 dest,
675 675 node,
676 676 kind,
677 677 not opts.get(b'no_decode'),
678 678 match,
679 679 prefix,
680 680 subrepos=opts.get(b'subrepos'),
681 681 )
682 682
683 683
684 684 @command(
685 685 b'backout',
686 686 [
687 687 (
688 688 b'',
689 689 b'merge',
690 690 None,
691 691 _(b'merge with old dirstate parent after backout'),
692 692 ),
693 693 (
694 694 b'',
695 695 b'commit',
696 696 None,
697 697 _(b'commit if no conflicts were encountered (DEPRECATED)'),
698 698 ),
699 699 (b'', b'no-commit', None, _(b'do not commit')),
700 700 (
701 701 b'',
702 702 b'parent',
703 703 b'',
704 704 _(b'parent to choose when backing out merge (DEPRECATED)'),
705 705 _(b'REV'),
706 706 ),
707 707 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
708 708 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
709 709 ]
710 710 + mergetoolopts
711 711 + walkopts
712 712 + commitopts
713 713 + commitopts2,
714 714 _(b'[OPTION]... [-r] REV'),
715 715 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
716 716 )
717 717 def backout(ui, repo, node=None, rev=None, **opts):
718 718 """reverse effect of earlier changeset
719 719
720 720 Prepare a new changeset with the effect of REV undone in the
721 721 current working directory. If no conflicts were encountered,
722 722 it will be committed immediately.
723 723
724 724 If REV is the parent of the working directory, then this new changeset
725 725 is committed automatically (unless --no-commit is specified).
726 726
727 727 .. note::
728 728
729 729 :hg:`backout` cannot be used to fix either an unwanted or
730 730 incorrect merge.
731 731
732 732 .. container:: verbose
733 733
734 734 Examples:
735 735
736 736 - Reverse the effect of the parent of the working directory.
737 737 This backout will be committed immediately::
738 738
739 739 hg backout -r .
740 740
741 741 - Reverse the effect of previous bad revision 23::
742 742
743 743 hg backout -r 23
744 744
745 745 - Reverse the effect of previous bad revision 23 and
746 746 leave changes uncommitted::
747 747
748 748 hg backout -r 23 --no-commit
749 749 hg commit -m "Backout revision 23"
750 750
751 751 By default, the pending changeset will have one parent,
752 752 maintaining a linear history. With --merge, the pending
753 753 changeset will instead have two parents: the old parent of the
754 754 working directory and a new child of REV that simply undoes REV.
755 755
756 756 Before version 1.7, the behavior without --merge was equivalent
757 757 to specifying --merge followed by :hg:`update --clean .` to
758 758 cancel the merge and leave the child of REV as a head to be
759 759 merged separately.
760 760
761 761 See :hg:`help dates` for a list of formats valid for -d/--date.
762 762
763 763 See :hg:`help revert` for a way to restore files to the state
764 764 of another revision.
765 765
766 766 Returns 0 on success, 1 if nothing to backout or there are unresolved
767 767 files.
768 768 """
769 769 with repo.wlock(), repo.lock():
770 770 return _dobackout(ui, repo, node, rev, **opts)
771 771
772 772
773 773 def _dobackout(ui, repo, node=None, rev=None, **opts):
774 774 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
775 775 opts = pycompat.byteskwargs(opts)
776 776
777 777 if rev and node:
778 778 raise error.InputError(_(b"please specify just one revision"))
779 779
780 780 if not rev:
781 781 rev = node
782 782
783 783 if not rev:
784 784 raise error.InputError(_(b"please specify a revision to backout"))
785 785
786 786 date = opts.get(b'date')
787 787 if date:
788 788 opts[b'date'] = dateutil.parsedate(date)
789 789
790 790 cmdutil.checkunfinished(repo)
791 791 cmdutil.bailifchanged(repo)
792 792 ctx = logcmdutil.revsingle(repo, rev)
793 793 node = ctx.node()
794 794
795 795 op1, op2 = repo.dirstate.parents()
796 796 if not repo.changelog.isancestor(node, op1):
797 797 raise error.InputError(
798 798 _(b'cannot backout change that is not an ancestor')
799 799 )
800 800
801 801 p1, p2 = repo.changelog.parents(node)
802 802 if p1 == repo.nullid:
803 803 raise error.InputError(_(b'cannot backout a change with no parents'))
804 804 if p2 != repo.nullid:
805 805 if not opts.get(b'parent'):
806 806 raise error.InputError(_(b'cannot backout a merge changeset'))
807 807 p = repo.lookup(opts[b'parent'])
808 808 if p not in (p1, p2):
809 809 raise error.InputError(
810 810 _(b'%s is not a parent of %s') % (short(p), short(node))
811 811 )
812 812 parent = p
813 813 else:
814 814 if opts.get(b'parent'):
815 815 raise error.InputError(
816 816 _(b'cannot use --parent on non-merge changeset')
817 817 )
818 818 parent = p1
819 819
820 820 # the backout should appear on the same branch
821 821 branch = repo.dirstate.branch()
822 822 bheads = repo.branchheads(branch)
823 823 rctx = scmutil.revsingle(repo, hex(parent))
824 824 if not opts.get(b'merge') and op1 != node:
825 825 with dirstateguard.dirstateguard(repo, b'backout'):
826 826 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
827 827 with ui.configoverride(overrides, b'backout'):
828 828 stats = mergemod.back_out(ctx, parent=repo[parent])
829 829 repo.setparents(op1, op2)
830 830 hg._showstats(repo, stats)
831 831 if stats.unresolvedcount:
832 832 repo.ui.status(
833 833 _(b"use 'hg resolve' to retry unresolved file merges\n")
834 834 )
835 835 return 1
836 836 else:
837 837 hg.clean(repo, node, show_stats=False)
838 838 repo.dirstate.setbranch(branch)
839 839 cmdutil.revert(ui, repo, rctx)
840 840
841 841 if opts.get(b'no_commit'):
842 842 msg = _(b"changeset %s backed out, don't forget to commit.\n")
843 843 ui.status(msg % short(node))
844 844 return 0
845 845
846 846 def commitfunc(ui, repo, message, match, opts):
847 847 editform = b'backout'
848 848 e = cmdutil.getcommiteditor(
849 849 editform=editform, **pycompat.strkwargs(opts)
850 850 )
851 851 if not message:
852 852 # we don't translate commit messages
853 853 message = b"Backed out changeset %s" % short(node)
854 854 e = cmdutil.getcommiteditor(edit=True, editform=editform)
855 855 return repo.commit(
856 856 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
857 857 )
858 858
859 859 # save to detect changes
860 860 tip = repo.changelog.tip()
861 861
862 862 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
863 863 if not newnode:
864 864 ui.status(_(b"nothing changed\n"))
865 865 return 1
866 866 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
867 867
868 868 def nice(node):
869 869 return b'%d:%s' % (repo.changelog.rev(node), short(node))
870 870
871 871 ui.status(
872 872 _(b'changeset %s backs out changeset %s\n')
873 873 % (nice(newnode), nice(node))
874 874 )
875 875 if opts.get(b'merge') and op1 != node:
876 876 hg.clean(repo, op1, show_stats=False)
877 877 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
878 878 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
879 879 with ui.configoverride(overrides, b'backout'):
880 880 return hg.merge(repo[b'tip'])
881 881 return 0
882 882
883 883
884 884 @command(
885 885 b'bisect',
886 886 [
887 887 (b'r', b'reset', False, _(b'reset bisect state')),
888 888 (b'g', b'good', False, _(b'mark changeset good')),
889 889 (b'b', b'bad', False, _(b'mark changeset bad')),
890 890 (b's', b'skip', False, _(b'skip testing changeset')),
891 891 (b'e', b'extend', False, _(b'extend the bisect range')),
892 892 (
893 893 b'c',
894 894 b'command',
895 895 b'',
896 896 _(b'use command to check changeset state'),
897 897 _(b'CMD'),
898 898 ),
899 899 (b'U', b'noupdate', False, _(b'do not update to target')),
900 900 ],
901 901 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
902 902 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
903 903 )
904 904 def bisect(
905 905 ui,
906 906 repo,
907 907 positional_1=None,
908 908 positional_2=None,
909 909 command=None,
910 910 reset=None,
911 911 good=None,
912 912 bad=None,
913 913 skip=None,
914 914 extend=None,
915 915 noupdate=None,
916 916 ):
917 917 """subdivision search of changesets
918 918
919 919 This command helps to find changesets which introduce problems. To
920 920 use, mark the earliest changeset you know exhibits the problem as
921 921 bad, then mark the latest changeset which is free from the problem
922 922 as good. Bisect will update your working directory to a revision
923 923 for testing (unless the -U/--noupdate option is specified). Once
924 924 you have performed tests, mark the working directory as good or
925 925 bad, and bisect will either update to another candidate changeset
926 926 or announce that it has found the bad revision.
927 927
928 928 As a shortcut, you can also use the revision argument to mark a
929 929 revision as good or bad without checking it out first.
930 930
931 931 If you supply a command, it will be used for automatic bisection.
932 932 The environment variable HG_NODE will contain the ID of the
933 933 changeset being tested. The exit status of the command will be
934 934 used to mark revisions as good or bad: status 0 means good, 125
935 935 means to skip the revision, 127 (command not found) will abort the
936 936 bisection, and any other non-zero exit status means the revision
937 937 is bad.
938 938
939 939 .. container:: verbose
940 940
941 941 Some examples:
942 942
943 943 - start a bisection with known bad revision 34, and good revision 12::
944 944
945 945 hg bisect --bad 34
946 946 hg bisect --good 12
947 947
948 948 - advance the current bisection by marking current revision as good or
949 949 bad::
950 950
951 951 hg bisect --good
952 952 hg bisect --bad
953 953
954 954 - mark the current revision, or a known revision, to be skipped (e.g. if
955 955 that revision is not usable because of another issue)::
956 956
957 957 hg bisect --skip
958 958 hg bisect --skip 23
959 959
960 960 - skip all revisions that do not touch directories ``foo`` or ``bar``::
961 961
962 962 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
963 963
964 964 - forget the current bisection::
965 965
966 966 hg bisect --reset
967 967
968 968 - use 'make && make tests' to automatically find the first broken
969 969 revision::
970 970
971 971 hg bisect --reset
972 972 hg bisect --bad 34
973 973 hg bisect --good 12
974 974 hg bisect --command "make && make tests"
975 975
976 976 - see all changesets whose states are already known in the current
977 977 bisection::
978 978
979 979 hg log -r "bisect(pruned)"
980 980
981 981 - see the changeset currently being bisected (especially useful
982 982 if running with -U/--noupdate)::
983 983
984 984 hg log -r "bisect(current)"
985 985
986 986 - see all changesets that took part in the current bisection::
987 987
988 988 hg log -r "bisect(range)"
989 989
990 990 - you can even get a nice graph::
991 991
992 992 hg log --graph -r "bisect(range)"
993 993
994 994 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
995 995
996 996 Returns 0 on success.
997 997 """
998 998 rev = []
999 999 # backward compatibility
1000 1000 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1001 1001 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1002 1002 cmd = positional_1
1003 1003 rev.append(positional_2)
1004 1004 if cmd == b"good":
1005 1005 good = True
1006 1006 elif cmd == b"bad":
1007 1007 bad = True
1008 1008 else:
1009 1009 reset = True
1010 1010 elif positional_2:
1011 1011 raise error.InputError(_(b'incompatible arguments'))
1012 1012 elif positional_1 is not None:
1013 1013 rev.append(positional_1)
1014 1014
1015 1015 incompatibles = {
1016 1016 b'--bad': bad,
1017 1017 b'--command': bool(command),
1018 1018 b'--extend': extend,
1019 1019 b'--good': good,
1020 1020 b'--reset': reset,
1021 1021 b'--skip': skip,
1022 1022 }
1023 1023
1024 1024 enabled = [x for x in incompatibles if incompatibles[x]]
1025 1025
1026 1026 if len(enabled) > 1:
1027 1027 raise error.InputError(
1028 1028 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1029 1029 )
1030 1030
1031 1031 if reset:
1032 1032 hbisect.resetstate(repo)
1033 1033 return
1034 1034
1035 1035 state = hbisect.load_state(repo)
1036 1036
1037 1037 if rev:
1038 1038 nodes = [repo[i].node() for i in logcmdutil.revrange(repo, rev)]
1039 1039 else:
1040 1040 nodes = [repo.lookup(b'.')]
1041 1041
1042 1042 # update state
1043 1043 if good or bad or skip:
1044 1044 if good:
1045 1045 state[b'good'] += nodes
1046 1046 elif bad:
1047 1047 state[b'bad'] += nodes
1048 1048 elif skip:
1049 1049 state[b'skip'] += nodes
1050 1050 hbisect.save_state(repo, state)
1051 1051 if not (state[b'good'] and state[b'bad']):
1052 1052 return
1053 1053
1054 1054 def mayupdate(repo, node, show_stats=True):
1055 1055 """common used update sequence"""
1056 1056 if noupdate:
1057 1057 return
1058 1058 cmdutil.checkunfinished(repo)
1059 1059 cmdutil.bailifchanged(repo)
1060 1060 return hg.clean(repo, node, show_stats=show_stats)
1061 1061
1062 1062 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1063 1063
1064 1064 if command:
1065 1065 changesets = 1
1066 1066 if noupdate:
1067 1067 try:
1068 1068 node = state[b'current'][0]
1069 1069 except LookupError:
1070 1070 raise error.StateError(
1071 1071 _(
1072 1072 b'current bisect revision is unknown - '
1073 1073 b'start a new bisect to fix'
1074 1074 )
1075 1075 )
1076 1076 else:
1077 1077 node, p2 = repo.dirstate.parents()
1078 1078 if p2 != repo.nullid:
1079 1079 raise error.StateError(_(b'current bisect revision is a merge'))
1080 1080 if rev:
1081 1081 if not nodes:
1082 1082 raise error.InputError(_(b'empty revision set'))
1083 1083 node = repo[nodes[-1]].node()
1084 1084 with hbisect.restore_state(repo, state, node):
1085 1085 while changesets:
1086 1086 # update state
1087 1087 state[b'current'] = [node]
1088 1088 hbisect.save_state(repo, state)
1089 1089 status = ui.system(
1090 1090 command,
1091 1091 environ={b'HG_NODE': hex(node)},
1092 1092 blockedtag=b'bisect_check',
1093 1093 )
1094 1094 if status == 125:
1095 1095 transition = b"skip"
1096 1096 elif status == 0:
1097 1097 transition = b"good"
1098 1098 # status < 0 means process was killed
1099 1099 elif status == 127:
1100 1100 raise error.Abort(_(b"failed to execute %s") % command)
1101 1101 elif status < 0:
1102 1102 raise error.Abort(_(b"%s killed") % command)
1103 1103 else:
1104 1104 transition = b"bad"
1105 1105 state[transition].append(node)
1106 1106 ctx = repo[node]
1107 1107 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1108 1108 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1109 1109 hbisect.checkstate(state)
1110 1110 # bisect
1111 1111 nodes, changesets, bgood = hbisect.bisect(repo, state)
1112 1112 # update to next check
1113 1113 node = nodes[0]
1114 1114 mayupdate(repo, node, show_stats=False)
1115 1115 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1116 1116 return
1117 1117
1118 1118 hbisect.checkstate(state)
1119 1119
1120 1120 # actually bisect
1121 1121 nodes, changesets, good = hbisect.bisect(repo, state)
1122 1122 if extend:
1123 1123 if not changesets:
1124 1124 extendctx = hbisect.extendrange(repo, state, nodes, good)
1125 1125 if extendctx is not None:
1126 1126 ui.write(
1127 1127 _(b"Extending search to changeset %s\n")
1128 1128 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1129 1129 )
1130 1130 state[b'current'] = [extendctx.node()]
1131 1131 hbisect.save_state(repo, state)
1132 1132 return mayupdate(repo, extendctx.node())
1133 1133 raise error.StateError(_(b"nothing to extend"))
1134 1134
1135 1135 if changesets == 0:
1136 1136 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1137 1137 else:
1138 1138 assert len(nodes) == 1 # only a single node can be tested next
1139 1139 node = nodes[0]
1140 1140 # compute the approximate number of remaining tests
1141 1141 tests, size = 0, 2
1142 1142 while size <= changesets:
1143 1143 tests, size = tests + 1, size * 2
1144 1144 rev = repo.changelog.rev(node)
1145 1145 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1146 1146 ui.write(
1147 1147 _(
1148 1148 b"Testing changeset %s "
1149 1149 b"(%d changesets remaining, ~%d tests)\n"
1150 1150 )
1151 1151 % (summary, changesets, tests)
1152 1152 )
1153 1153 state[b'current'] = [node]
1154 1154 hbisect.save_state(repo, state)
1155 1155 return mayupdate(repo, node)
1156 1156
1157 1157
1158 1158 @command(
1159 1159 b'bookmarks|bookmark',
1160 1160 [
1161 1161 (b'f', b'force', False, _(b'force')),
1162 1162 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1163 1163 (b'd', b'delete', False, _(b'delete a given bookmark')),
1164 1164 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1165 1165 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1166 1166 (b'l', b'list', False, _(b'list existing bookmarks')),
1167 1167 ]
1168 1168 + formatteropts,
1169 1169 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1170 1170 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1171 1171 )
1172 1172 def bookmark(ui, repo, *names, **opts):
1173 1173 """create a new bookmark or list existing bookmarks
1174 1174
1175 1175 Bookmarks are labels on changesets to help track lines of development.
1176 1176 Bookmarks are unversioned and can be moved, renamed and deleted.
1177 1177 Deleting or moving a bookmark has no effect on the associated changesets.
1178 1178
1179 1179 Creating or updating to a bookmark causes it to be marked as 'active'.
1180 1180 The active bookmark is indicated with a '*'.
1181 1181 When a commit is made, the active bookmark will advance to the new commit.
1182 1182 A plain :hg:`update` will also advance an active bookmark, if possible.
1183 1183 Updating away from a bookmark will cause it to be deactivated.
1184 1184
1185 1185 Bookmarks can be pushed and pulled between repositories (see
1186 1186 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1187 1187 diverged, a new 'divergent bookmark' of the form 'name@path' will
1188 1188 be created. Using :hg:`merge` will resolve the divergence.
1189 1189
1190 1190 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1191 1191 the active bookmark's name.
1192 1192
1193 1193 A bookmark named '@' has the special property that :hg:`clone` will
1194 1194 check it out by default if it exists.
1195 1195
1196 1196 .. container:: verbose
1197 1197
1198 1198 Template:
1199 1199
1200 1200 The following keywords are supported in addition to the common template
1201 1201 keywords and functions such as ``{bookmark}``. See also
1202 1202 :hg:`help templates`.
1203 1203
1204 1204 :active: Boolean. True if the bookmark is active.
1205 1205
1206 1206 Examples:
1207 1207
1208 1208 - create an active bookmark for a new line of development::
1209 1209
1210 1210 hg book new-feature
1211 1211
1212 1212 - create an inactive bookmark as a place marker::
1213 1213
1214 1214 hg book -i reviewed
1215 1215
1216 1216 - create an inactive bookmark on another changeset::
1217 1217
1218 1218 hg book -r .^ tested
1219 1219
1220 1220 - rename bookmark turkey to dinner::
1221 1221
1222 1222 hg book -m turkey dinner
1223 1223
1224 1224 - move the '@' bookmark from another branch::
1225 1225
1226 1226 hg book -f @
1227 1227
1228 1228 - print only the active bookmark name::
1229 1229
1230 1230 hg book -ql .
1231 1231 """
1232 1232 opts = pycompat.byteskwargs(opts)
1233 1233 force = opts.get(b'force')
1234 1234 rev = opts.get(b'rev')
1235 1235 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1236 1236
1237 1237 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1238 1238 if action:
1239 1239 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1240 1240 elif names or rev:
1241 1241 action = b'add'
1242 1242 elif inactive:
1243 1243 action = b'inactive' # meaning deactivate
1244 1244 else:
1245 1245 action = b'list'
1246 1246
1247 1247 cmdutil.check_incompatible_arguments(
1248 1248 opts, b'inactive', [b'delete', b'list']
1249 1249 )
1250 1250 if not names and action in {b'add', b'delete'}:
1251 1251 raise error.InputError(_(b"bookmark name required"))
1252 1252
1253 1253 if action in {b'add', b'delete', b'rename', b'inactive'}:
1254 1254 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1255 1255 if action == b'delete':
1256 1256 names = pycompat.maplist(repo._bookmarks.expandname, names)
1257 1257 bookmarks.delete(repo, tr, names)
1258 1258 elif action == b'rename':
1259 1259 if not names:
1260 1260 raise error.InputError(_(b"new bookmark name required"))
1261 1261 elif len(names) > 1:
1262 1262 raise error.InputError(
1263 1263 _(b"only one new bookmark name allowed")
1264 1264 )
1265 1265 oldname = repo._bookmarks.expandname(opts[b'rename'])
1266 1266 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1267 1267 elif action == b'add':
1268 1268 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1269 1269 elif action == b'inactive':
1270 1270 if len(repo._bookmarks) == 0:
1271 1271 ui.status(_(b"no bookmarks set\n"))
1272 1272 elif not repo._activebookmark:
1273 1273 ui.status(_(b"no active bookmark\n"))
1274 1274 else:
1275 1275 bookmarks.deactivate(repo)
1276 1276 elif action == b'list':
1277 1277 names = pycompat.maplist(repo._bookmarks.expandname, names)
1278 1278 with ui.formatter(b'bookmarks', opts) as fm:
1279 1279 bookmarks.printbookmarks(ui, repo, fm, names)
1280 1280 else:
1281 1281 raise error.ProgrammingError(b'invalid action: %s' % action)
1282 1282
1283 1283
1284 1284 @command(
1285 1285 b'branch',
1286 1286 [
1287 1287 (
1288 1288 b'f',
1289 1289 b'force',
1290 1290 None,
1291 1291 _(b'set branch name even if it shadows an existing branch'),
1292 1292 ),
1293 1293 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1294 1294 (
1295 1295 b'r',
1296 1296 b'rev',
1297 1297 [],
1298 1298 _(b'change branches of the given revs (EXPERIMENTAL)'),
1299 1299 ),
1300 1300 ],
1301 1301 _(b'[-fC] [NAME]'),
1302 1302 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1303 1303 )
1304 1304 def branch(ui, repo, label=None, **opts):
1305 1305 """set or show the current branch name
1306 1306
1307 1307 .. note::
1308 1308
1309 1309 Branch names are permanent and global. Use :hg:`bookmark` to create a
1310 1310 light-weight bookmark instead. See :hg:`help glossary` for more
1311 1311 information about named branches and bookmarks.
1312 1312
1313 1313 With no argument, show the current branch name. With one argument,
1314 1314 set the working directory branch name (the branch will not exist
1315 1315 in the repository until the next commit). Standard practice
1316 1316 recommends that primary development take place on the 'default'
1317 1317 branch.
1318 1318
1319 1319 Unless -f/--force is specified, branch will not let you set a
1320 1320 branch name that already exists.
1321 1321
1322 1322 Use -C/--clean to reset the working directory branch to that of
1323 1323 the parent of the working directory, negating a previous branch
1324 1324 change.
1325 1325
1326 1326 Use the command :hg:`update` to switch to an existing branch. Use
1327 1327 :hg:`commit --close-branch` to mark this branch head as closed.
1328 1328 When all heads of a branch are closed, the branch will be
1329 1329 considered closed.
1330 1330
1331 1331 Returns 0 on success.
1332 1332 """
1333 1333 opts = pycompat.byteskwargs(opts)
1334 1334 revs = opts.get(b'rev')
1335 1335 if label:
1336 1336 label = label.strip()
1337 1337
1338 1338 if not opts.get(b'clean') and not label:
1339 1339 if revs:
1340 1340 raise error.InputError(
1341 1341 _(b"no branch name specified for the revisions")
1342 1342 )
1343 1343 ui.write(b"%s\n" % repo.dirstate.branch())
1344 1344 return
1345 1345
1346 1346 with repo.wlock():
1347 1347 if opts.get(b'clean'):
1348 1348 label = repo[b'.'].branch()
1349 1349 repo.dirstate.setbranch(label)
1350 1350 ui.status(_(b'reset working directory to branch %s\n') % label)
1351 1351 elif label:
1352 1352
1353 1353 scmutil.checknewlabel(repo, label, b'branch')
1354 1354 if revs:
1355 1355 return cmdutil.changebranch(ui, repo, revs, label, opts)
1356 1356
1357 1357 if not opts.get(b'force') and label in repo.branchmap():
1358 1358 if label not in [p.branch() for p in repo[None].parents()]:
1359 1359 raise error.InputError(
1360 1360 _(b'a branch of the same name already exists'),
1361 1361 # i18n: "it" refers to an existing branch
1362 1362 hint=_(b"use 'hg update' to switch to it"),
1363 1363 )
1364 1364
1365 1365 repo.dirstate.setbranch(label)
1366 1366 ui.status(_(b'marked working directory as branch %s\n') % label)
1367 1367
1368 1368 # find any open named branches aside from default
1369 1369 for n, h, t, c in repo.branchmap().iterbranches():
1370 1370 if n != b"default" and not c:
1371 1371 return 0
1372 1372 ui.status(
1373 1373 _(
1374 1374 b'(branches are permanent and global, '
1375 1375 b'did you want a bookmark?)\n'
1376 1376 )
1377 1377 )
1378 1378
1379 1379
1380 1380 @command(
1381 1381 b'branches',
1382 1382 [
1383 1383 (
1384 1384 b'a',
1385 1385 b'active',
1386 1386 False,
1387 1387 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1388 1388 ),
1389 1389 (b'c', b'closed', False, _(b'show normal and closed branches')),
1390 1390 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1391 1391 ]
1392 1392 + formatteropts,
1393 1393 _(b'[-c]'),
1394 1394 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1395 1395 intents={INTENT_READONLY},
1396 1396 )
1397 1397 def branches(ui, repo, active=False, closed=False, **opts):
1398 1398 """list repository named branches
1399 1399
1400 1400 List the repository's named branches, indicating which ones are
1401 1401 inactive. If -c/--closed is specified, also list branches which have
1402 1402 been marked closed (see :hg:`commit --close-branch`).
1403 1403
1404 1404 Use the command :hg:`update` to switch to an existing branch.
1405 1405
1406 1406 .. container:: verbose
1407 1407
1408 1408 Template:
1409 1409
1410 1410 The following keywords are supported in addition to the common template
1411 1411 keywords and functions such as ``{branch}``. See also
1412 1412 :hg:`help templates`.
1413 1413
1414 1414 :active: Boolean. True if the branch is active.
1415 1415 :closed: Boolean. True if the branch is closed.
1416 1416 :current: Boolean. True if it is the current branch.
1417 1417
1418 1418 Returns 0.
1419 1419 """
1420 1420
1421 1421 opts = pycompat.byteskwargs(opts)
1422 1422 revs = opts.get(b'rev')
1423 1423 selectedbranches = None
1424 1424 if revs:
1425 1425 revs = logcmdutil.revrange(repo, revs)
1426 1426 getbi = repo.revbranchcache().branchinfo
1427 1427 selectedbranches = {getbi(r)[0] for r in revs}
1428 1428
1429 1429 ui.pager(b'branches')
1430 1430 fm = ui.formatter(b'branches', opts)
1431 1431 hexfunc = fm.hexfunc
1432 1432
1433 1433 allheads = set(repo.heads())
1434 1434 branches = []
1435 1435 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1436 1436 if selectedbranches is not None and tag not in selectedbranches:
1437 1437 continue
1438 1438 isactive = False
1439 1439 if not isclosed:
1440 1440 openheads = set(repo.branchmap().iteropen(heads))
1441 1441 isactive = bool(openheads & allheads)
1442 1442 branches.append((tag, repo[tip], isactive, not isclosed))
1443 1443 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1444 1444
1445 1445 for tag, ctx, isactive, isopen in branches:
1446 1446 if active and not isactive:
1447 1447 continue
1448 1448 if isactive:
1449 1449 label = b'branches.active'
1450 1450 notice = b''
1451 1451 elif not isopen:
1452 1452 if not closed:
1453 1453 continue
1454 1454 label = b'branches.closed'
1455 1455 notice = _(b' (closed)')
1456 1456 else:
1457 1457 label = b'branches.inactive'
1458 1458 notice = _(b' (inactive)')
1459 1459 current = tag == repo.dirstate.branch()
1460 1460 if current:
1461 1461 label = b'branches.current'
1462 1462
1463 1463 fm.startitem()
1464 1464 fm.write(b'branch', b'%s', tag, label=label)
1465 1465 rev = ctx.rev()
1466 1466 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1467 1467 fmt = b' ' * padsize + b' %d:%s'
1468 1468 fm.condwrite(
1469 1469 not ui.quiet,
1470 1470 b'rev node',
1471 1471 fmt,
1472 1472 rev,
1473 1473 hexfunc(ctx.node()),
1474 1474 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1475 1475 )
1476 1476 fm.context(ctx=ctx)
1477 1477 fm.data(active=isactive, closed=not isopen, current=current)
1478 1478 if not ui.quiet:
1479 1479 fm.plain(notice)
1480 1480 fm.plain(b'\n')
1481 1481 fm.end()
1482 1482
1483 1483
1484 1484 @command(
1485 1485 b'bundle',
1486 1486 [
1487 1487 (
1488 1488 b'f',
1489 1489 b'force',
1490 1490 None,
1491 1491 _(b'run even when the destination is unrelated'),
1492 1492 ),
1493 1493 (
1494 1494 b'r',
1495 1495 b'rev',
1496 1496 [],
1497 1497 _(b'a changeset intended to be added to the destination'),
1498 1498 _(b'REV'),
1499 1499 ),
1500 1500 (
1501 1501 b'b',
1502 1502 b'branch',
1503 1503 [],
1504 1504 _(b'a specific branch you would like to bundle'),
1505 1505 _(b'BRANCH'),
1506 1506 ),
1507 1507 (
1508 1508 b'',
1509 1509 b'base',
1510 1510 [],
1511 1511 _(b'a base changeset assumed to be available at the destination'),
1512 1512 _(b'REV'),
1513 1513 ),
1514 1514 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1515 1515 (
1516 1516 b't',
1517 1517 b'type',
1518 1518 b'bzip2',
1519 1519 _(b'bundle compression type to use'),
1520 1520 _(b'TYPE'),
1521 1521 ),
1522 1522 ]
1523 1523 + remoteopts,
1524 1524 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1525 1525 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1526 1526 )
1527 1527 def bundle(ui, repo, fname, *dests, **opts):
1528 1528 """create a bundle file
1529 1529
1530 1530 Generate a bundle file containing data to be transferred to another
1531 1531 repository.
1532 1532
1533 1533 To create a bundle containing all changesets, use -a/--all
1534 1534 (or --base null). Otherwise, hg assumes the destination will have
1535 1535 all the nodes you specify with --base parameters. Otherwise, hg
1536 1536 will assume the repository has all the nodes in destination, or
1537 1537 default-push/default if no destination is specified, where destination
1538 1538 is the repositories you provide through DEST option.
1539 1539
1540 1540 You can change bundle format with the -t/--type option. See
1541 1541 :hg:`help bundlespec` for documentation on this format. By default,
1542 1542 the most appropriate format is used and compression defaults to
1543 1543 bzip2.
1544 1544
1545 1545 The bundle file can then be transferred using conventional means
1546 1546 and applied to another repository with the unbundle or pull
1547 1547 command. This is useful when direct push and pull are not
1548 1548 available or when exporting an entire repository is undesirable.
1549 1549
1550 1550 Applying bundles preserves all changeset contents including
1551 1551 permissions, copy/rename information, and revision history.
1552 1552
1553 1553 Returns 0 on success, 1 if no changes found.
1554 1554 """
1555 1555 opts = pycompat.byteskwargs(opts)
1556 1556 revs = None
1557 1557 if b'rev' in opts:
1558 1558 revstrings = opts[b'rev']
1559 1559 revs = logcmdutil.revrange(repo, revstrings)
1560 1560 if revstrings and not revs:
1561 1561 raise error.InputError(_(b'no commits to bundle'))
1562 1562
1563 1563 bundletype = opts.get(b'type', b'bzip2').lower()
1564 1564 try:
1565 1565 bundlespec = bundlecaches.parsebundlespec(
1566 1566 repo, bundletype, strict=False
1567 1567 )
1568 1568 except error.UnsupportedBundleSpecification as e:
1569 1569 raise error.InputError(
1570 1570 pycompat.bytestr(e),
1571 1571 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1572 1572 )
1573 cgversion = bundlespec.contentopts[b"cg.version"]
1573 cgversion = bundlespec.params[b"cg.version"]
1574 1574
1575 1575 # Packed bundles are a pseudo bundle format for now.
1576 1576 if cgversion == b's1':
1577 1577 raise error.InputError(
1578 1578 _(b'packed bundles cannot be produced by "hg bundle"'),
1579 1579 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1580 1580 )
1581 1581
1582 1582 if opts.get(b'all'):
1583 1583 if dests:
1584 1584 raise error.InputError(
1585 1585 _(b"--all is incompatible with specifying destinations")
1586 1586 )
1587 1587 if opts.get(b'base'):
1588 1588 ui.warn(_(b"ignoring --base because --all was specified\n"))
1589 1589 base = [nullrev]
1590 1590 else:
1591 1591 base = logcmdutil.revrange(repo, opts.get(b'base'))
1592 1592 if cgversion not in changegroup.supportedoutgoingversions(repo):
1593 1593 raise error.Abort(
1594 1594 _(b"repository does not support bundle version %s") % cgversion
1595 1595 )
1596 1596
1597 1597 if base:
1598 1598 if dests:
1599 1599 raise error.InputError(
1600 1600 _(b"--base is incompatible with specifying destinations")
1601 1601 )
1602 1602 cl = repo.changelog
1603 1603 common = [cl.node(rev) for rev in base]
1604 1604 heads = [cl.node(r) for r in revs] if revs else None
1605 1605 outgoing = discovery.outgoing(repo, common, heads)
1606 1606 missing = outgoing.missing
1607 1607 excluded = outgoing.excluded
1608 1608 else:
1609 1609 missing = set()
1610 1610 excluded = set()
1611 1611 for path in urlutil.get_push_paths(repo, ui, dests):
1612 1612 other = hg.peer(repo, opts, path.rawloc)
1613 1613 if revs is not None:
1614 1614 hex_revs = [repo[r].hex() for r in revs]
1615 1615 else:
1616 1616 hex_revs = None
1617 1617 branches = (path.branch, [])
1618 1618 head_revs, checkout = hg.addbranchrevs(
1619 1619 repo, repo, branches, hex_revs
1620 1620 )
1621 1621 heads = (
1622 1622 head_revs
1623 1623 and pycompat.maplist(repo.lookup, head_revs)
1624 1624 or head_revs
1625 1625 )
1626 1626 outgoing = discovery.findcommonoutgoing(
1627 1627 repo,
1628 1628 other,
1629 1629 onlyheads=heads,
1630 1630 force=opts.get(b'force'),
1631 1631 portable=True,
1632 1632 )
1633 1633 missing.update(outgoing.missing)
1634 1634 excluded.update(outgoing.excluded)
1635 1635
1636 1636 if not missing:
1637 1637 scmutil.nochangesfound(ui, repo, not base and excluded)
1638 1638 return 1
1639 1639
1640 1640 if heads:
1641 1641 outgoing = discovery.outgoing(
1642 1642 repo, missingroots=missing, ancestorsof=heads
1643 1643 )
1644 1644 else:
1645 1645 outgoing = discovery.outgoing(repo, missingroots=missing)
1646 1646 outgoing.excluded = sorted(excluded)
1647 1647
1648 1648 if cgversion == b'01': # bundle1
1649 1649 bversion = b'HG10' + bundlespec.wirecompression
1650 1650 bcompression = None
1651 1651 elif cgversion in (b'02', b'03'):
1652 1652 bversion = b'HG20'
1653 1653 bcompression = bundlespec.wirecompression
1654 1654 else:
1655 1655 raise error.ProgrammingError(
1656 1656 b'bundle: unexpected changegroup version %s' % cgversion
1657 1657 )
1658 1658
1659 1659 # TODO compression options should be derived from bundlespec parsing.
1660 1660 # This is a temporary hack to allow adjusting bundle compression
1661 1661 # level without a) formalizing the bundlespec changes to declare it
1662 1662 # b) introducing a command flag.
1663 1663 compopts = {}
1664 1664 complevel = ui.configint(
1665 1665 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1666 1666 )
1667 1667 if complevel is None:
1668 1668 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1669 1669 if complevel is not None:
1670 1670 compopts[b'level'] = complevel
1671 1671
1672 1672 compthreads = ui.configint(
1673 1673 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1674 1674 )
1675 1675 if compthreads is None:
1676 1676 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1677 1677 if compthreads is not None:
1678 1678 compopts[b'threads'] = compthreads
1679 1679
1680 1680 # Bundling of obsmarker and phases is optional as not all clients
1681 1681 # support the necessary features.
1682 1682 cfg = ui.configbool
1683 contentopts = {
1684 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1685 b'obsolescence-mandatory': cfg(
1686 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1687 ),
1688 b'phases': cfg(b'experimental', b'bundle-phases'),
1689 }
1690 bundlespec.contentopts.update(contentopts)
1683 obsolescence_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker')
1684 bundlespec.set_param(b'obsolescence', obsolescence_cfg)
1685 obs_mand_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker:mandatory')
1686 bundlespec.set_param(b'obsolescence-mandatory', obs_mand_cfg)
1687 phases_cfg = cfg(b'experimental', b'bundle-phases')
1688 bundlespec.set_param(b'phases', phases_cfg)
1691 1689
1692 1690 bundle2.writenewbundle(
1693 1691 ui,
1694 1692 repo,
1695 1693 b'bundle',
1696 1694 fname,
1697 1695 bversion,
1698 1696 outgoing,
1699 bundlespec.contentopts,
1697 bundlespec.params,
1700 1698 compression=bcompression,
1701 1699 compopts=compopts,
1702 1700 )
1703 1701
1704 1702
1705 1703 @command(
1706 1704 b'cat',
1707 1705 [
1708 1706 (
1709 1707 b'o',
1710 1708 b'output',
1711 1709 b'',
1712 1710 _(b'print output to file with formatted name'),
1713 1711 _(b'FORMAT'),
1714 1712 ),
1715 1713 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1716 1714 (b'', b'decode', None, _(b'apply any matching decode filter')),
1717 1715 ]
1718 1716 + walkopts
1719 1717 + formatteropts,
1720 1718 _(b'[OPTION]... FILE...'),
1721 1719 helpcategory=command.CATEGORY_FILE_CONTENTS,
1722 1720 inferrepo=True,
1723 1721 intents={INTENT_READONLY},
1724 1722 )
1725 1723 def cat(ui, repo, file1, *pats, **opts):
1726 1724 """output the current or given revision of files
1727 1725
1728 1726 Print the specified files as they were at the given revision. If
1729 1727 no revision is given, the parent of the working directory is used.
1730 1728
1731 1729 Output may be to a file, in which case the name of the file is
1732 1730 given using a template string. See :hg:`help templates`. In addition
1733 1731 to the common template keywords, the following formatting rules are
1734 1732 supported:
1735 1733
1736 1734 :``%%``: literal "%" character
1737 1735 :``%s``: basename of file being printed
1738 1736 :``%d``: dirname of file being printed, or '.' if in repository root
1739 1737 :``%p``: root-relative path name of file being printed
1740 1738 :``%H``: changeset hash (40 hexadecimal digits)
1741 1739 :``%R``: changeset revision number
1742 1740 :``%h``: short-form changeset hash (12 hexadecimal digits)
1743 1741 :``%r``: zero-padded changeset revision number
1744 1742 :``%b``: basename of the exporting repository
1745 1743 :``\\``: literal "\\" character
1746 1744
1747 1745 .. container:: verbose
1748 1746
1749 1747 Template:
1750 1748
1751 1749 The following keywords are supported in addition to the common template
1752 1750 keywords and functions. See also :hg:`help templates`.
1753 1751
1754 1752 :data: String. File content.
1755 1753 :path: String. Repository-absolute path of the file.
1756 1754
1757 1755 Returns 0 on success.
1758 1756 """
1759 1757 opts = pycompat.byteskwargs(opts)
1760 1758 rev = opts.get(b'rev')
1761 1759 if rev:
1762 1760 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1763 1761 ctx = logcmdutil.revsingle(repo, rev)
1764 1762 m = scmutil.match(ctx, (file1,) + pats, opts)
1765 1763 fntemplate = opts.pop(b'output', b'')
1766 1764 if cmdutil.isstdiofilename(fntemplate):
1767 1765 fntemplate = b''
1768 1766
1769 1767 if fntemplate:
1770 1768 fm = formatter.nullformatter(ui, b'cat', opts)
1771 1769 else:
1772 1770 ui.pager(b'cat')
1773 1771 fm = ui.formatter(b'cat', opts)
1774 1772 with fm:
1775 1773 return cmdutil.cat(
1776 1774 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1777 1775 )
1778 1776
1779 1777
1780 1778 @command(
1781 1779 b'clone',
1782 1780 [
1783 1781 (
1784 1782 b'U',
1785 1783 b'noupdate',
1786 1784 None,
1787 1785 _(
1788 1786 b'the clone will include an empty working '
1789 1787 b'directory (only a repository)'
1790 1788 ),
1791 1789 ),
1792 1790 (
1793 1791 b'u',
1794 1792 b'updaterev',
1795 1793 b'',
1796 1794 _(b'revision, tag, or branch to check out'),
1797 1795 _(b'REV'),
1798 1796 ),
1799 1797 (
1800 1798 b'r',
1801 1799 b'rev',
1802 1800 [],
1803 1801 _(
1804 1802 b'do not clone everything, but include this changeset'
1805 1803 b' and its ancestors'
1806 1804 ),
1807 1805 _(b'REV'),
1808 1806 ),
1809 1807 (
1810 1808 b'b',
1811 1809 b'branch',
1812 1810 [],
1813 1811 _(
1814 1812 b'do not clone everything, but include this branch\'s'
1815 1813 b' changesets and their ancestors'
1816 1814 ),
1817 1815 _(b'BRANCH'),
1818 1816 ),
1819 1817 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1820 1818 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1821 1819 (b'', b'stream', None, _(b'clone with minimal data processing')),
1822 1820 ]
1823 1821 + remoteopts,
1824 1822 _(b'[OPTION]... SOURCE [DEST]'),
1825 1823 helpcategory=command.CATEGORY_REPO_CREATION,
1826 1824 helpbasic=True,
1827 1825 norepo=True,
1828 1826 )
1829 1827 def clone(ui, source, dest=None, **opts):
1830 1828 """make a copy of an existing repository
1831 1829
1832 1830 Create a copy of an existing repository in a new directory.
1833 1831
1834 1832 If no destination directory name is specified, it defaults to the
1835 1833 basename of the source.
1836 1834
1837 1835 The location of the source is added to the new repository's
1838 1836 ``.hg/hgrc`` file, as the default to be used for future pulls.
1839 1837
1840 1838 Only local paths and ``ssh://`` URLs are supported as
1841 1839 destinations. For ``ssh://`` destinations, no working directory or
1842 1840 ``.hg/hgrc`` will be created on the remote side.
1843 1841
1844 1842 If the source repository has a bookmark called '@' set, that
1845 1843 revision will be checked out in the new repository by default.
1846 1844
1847 1845 To check out a particular version, use -u/--update, or
1848 1846 -U/--noupdate to create a clone with no working directory.
1849 1847
1850 1848 To pull only a subset of changesets, specify one or more revisions
1851 1849 identifiers with -r/--rev or branches with -b/--branch. The
1852 1850 resulting clone will contain only the specified changesets and
1853 1851 their ancestors. These options (or 'clone src#rev dest') imply
1854 1852 --pull, even for local source repositories.
1855 1853
1856 1854 In normal clone mode, the remote normalizes repository data into a common
1857 1855 exchange format and the receiving end translates this data into its local
1858 1856 storage format. --stream activates a different clone mode that essentially
1859 1857 copies repository files from the remote with minimal data processing. This
1860 1858 significantly reduces the CPU cost of a clone both remotely and locally.
1861 1859 However, it often increases the transferred data size by 30-40%. This can
1862 1860 result in substantially faster clones where I/O throughput is plentiful,
1863 1861 especially for larger repositories. A side-effect of --stream clones is
1864 1862 that storage settings and requirements on the remote are applied locally:
1865 1863 a modern client may inherit legacy or inefficient storage used by the
1866 1864 remote or a legacy Mercurial client may not be able to clone from a
1867 1865 modern Mercurial remote.
1868 1866
1869 1867 .. note::
1870 1868
1871 1869 Specifying a tag will include the tagged changeset but not the
1872 1870 changeset containing the tag.
1873 1871
1874 1872 .. container:: verbose
1875 1873
1876 1874 For efficiency, hardlinks are used for cloning whenever the
1877 1875 source and destination are on the same filesystem (note this
1878 1876 applies only to the repository data, not to the working
1879 1877 directory). Some filesystems, such as AFS, implement hardlinking
1880 1878 incorrectly, but do not report errors. In these cases, use the
1881 1879 --pull option to avoid hardlinking.
1882 1880
1883 1881 Mercurial will update the working directory to the first applicable
1884 1882 revision from this list:
1885 1883
1886 1884 a) null if -U or the source repository has no changesets
1887 1885 b) if -u . and the source repository is local, the first parent of
1888 1886 the source repository's working directory
1889 1887 c) the changeset specified with -u (if a branch name, this means the
1890 1888 latest head of that branch)
1891 1889 d) the changeset specified with -r
1892 1890 e) the tipmost head specified with -b
1893 1891 f) the tipmost head specified with the url#branch source syntax
1894 1892 g) the revision marked with the '@' bookmark, if present
1895 1893 h) the tipmost head of the default branch
1896 1894 i) tip
1897 1895
1898 1896 When cloning from servers that support it, Mercurial may fetch
1899 1897 pre-generated data from a server-advertised URL or inline from the
1900 1898 same stream. When this is done, hooks operating on incoming changesets
1901 1899 and changegroups may fire more than once, once for each pre-generated
1902 1900 bundle and as well as for any additional remaining data. In addition,
1903 1901 if an error occurs, the repository may be rolled back to a partial
1904 1902 clone. This behavior may change in future releases.
1905 1903 See :hg:`help -e clonebundles` for more.
1906 1904
1907 1905 Examples:
1908 1906
1909 1907 - clone a remote repository to a new directory named hg/::
1910 1908
1911 1909 hg clone https://www.mercurial-scm.org/repo/hg/
1912 1910
1913 1911 - create a lightweight local clone::
1914 1912
1915 1913 hg clone project/ project-feature/
1916 1914
1917 1915 - clone from an absolute path on an ssh server (note double-slash)::
1918 1916
1919 1917 hg clone ssh://user@server//home/projects/alpha/
1920 1918
1921 1919 - do a streaming clone while checking out a specified version::
1922 1920
1923 1921 hg clone --stream http://server/repo -u 1.5
1924 1922
1925 1923 - create a repository without changesets after a particular revision::
1926 1924
1927 1925 hg clone -r 04e544 experimental/ good/
1928 1926
1929 1927 - clone (and track) a particular named branch::
1930 1928
1931 1929 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1932 1930
1933 1931 See :hg:`help urls` for details on specifying URLs.
1934 1932
1935 1933 Returns 0 on success.
1936 1934 """
1937 1935 opts = pycompat.byteskwargs(opts)
1938 1936 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1939 1937
1940 1938 # --include/--exclude can come from narrow or sparse.
1941 1939 includepats, excludepats = None, None
1942 1940
1943 1941 # hg.clone() differentiates between None and an empty set. So make sure
1944 1942 # patterns are sets if narrow is requested without patterns.
1945 1943 if opts.get(b'narrow'):
1946 1944 includepats = set()
1947 1945 excludepats = set()
1948 1946
1949 1947 if opts.get(b'include'):
1950 1948 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1951 1949 if opts.get(b'exclude'):
1952 1950 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1953 1951
1954 1952 r = hg.clone(
1955 1953 ui,
1956 1954 opts,
1957 1955 source,
1958 1956 dest,
1959 1957 pull=opts.get(b'pull'),
1960 1958 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1961 1959 revs=opts.get(b'rev'),
1962 1960 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1963 1961 branch=opts.get(b'branch'),
1964 1962 shareopts=opts.get(b'shareopts'),
1965 1963 storeincludepats=includepats,
1966 1964 storeexcludepats=excludepats,
1967 1965 depth=opts.get(b'depth') or None,
1968 1966 )
1969 1967
1970 1968 return r is None
1971 1969
1972 1970
1973 1971 @command(
1974 1972 b'commit|ci',
1975 1973 [
1976 1974 (
1977 1975 b'A',
1978 1976 b'addremove',
1979 1977 None,
1980 1978 _(b'mark new/missing files as added/removed before committing'),
1981 1979 ),
1982 1980 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1983 1981 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1984 1982 (b's', b'secret', None, _(b'use the secret phase for committing')),
1985 1983 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1986 1984 (
1987 1985 b'',
1988 1986 b'force-close-branch',
1989 1987 None,
1990 1988 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1991 1989 ),
1992 1990 (b'i', b'interactive', None, _(b'use interactive mode')),
1993 1991 ]
1994 1992 + walkopts
1995 1993 + commitopts
1996 1994 + commitopts2
1997 1995 + subrepoopts,
1998 1996 _(b'[OPTION]... [FILE]...'),
1999 1997 helpcategory=command.CATEGORY_COMMITTING,
2000 1998 helpbasic=True,
2001 1999 inferrepo=True,
2002 2000 )
2003 2001 def commit(ui, repo, *pats, **opts):
2004 2002 """commit the specified files or all outstanding changes
2005 2003
2006 2004 Commit changes to the given files into the repository. Unlike a
2007 2005 centralized SCM, this operation is a local operation. See
2008 2006 :hg:`push` for a way to actively distribute your changes.
2009 2007
2010 2008 If a list of files is omitted, all changes reported by :hg:`status`
2011 2009 will be committed.
2012 2010
2013 2011 If you are committing the result of a merge, do not provide any
2014 2012 filenames or -I/-X filters.
2015 2013
2016 2014 If no commit message is specified, Mercurial starts your
2017 2015 configured editor where you can enter a message. In case your
2018 2016 commit fails, you will find a backup of your message in
2019 2017 ``.hg/last-message.txt``.
2020 2018
2021 2019 The --close-branch flag can be used to mark the current branch
2022 2020 head closed. When all heads of a branch are closed, the branch
2023 2021 will be considered closed and no longer listed.
2024 2022
2025 2023 The --amend flag can be used to amend the parent of the
2026 2024 working directory with a new commit that contains the changes
2027 2025 in the parent in addition to those currently reported by :hg:`status`,
2028 2026 if there are any. The old commit is stored in a backup bundle in
2029 2027 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2030 2028 on how to restore it).
2031 2029
2032 2030 Message, user and date are taken from the amended commit unless
2033 2031 specified. When a message isn't specified on the command line,
2034 2032 the editor will open with the message of the amended commit.
2035 2033
2036 2034 It is not possible to amend public changesets (see :hg:`help phases`)
2037 2035 or changesets that have children.
2038 2036
2039 2037 See :hg:`help dates` for a list of formats valid for -d/--date.
2040 2038
2041 2039 Returns 0 on success, 1 if nothing changed.
2042 2040
2043 2041 .. container:: verbose
2044 2042
2045 2043 Examples:
2046 2044
2047 2045 - commit all files ending in .py::
2048 2046
2049 2047 hg commit --include "set:**.py"
2050 2048
2051 2049 - commit all non-binary files::
2052 2050
2053 2051 hg commit --exclude "set:binary()"
2054 2052
2055 2053 - amend the current commit and set the date to now::
2056 2054
2057 2055 hg commit --amend --date now
2058 2056 """
2059 2057 with repo.wlock(), repo.lock():
2060 2058 return _docommit(ui, repo, *pats, **opts)
2061 2059
2062 2060
2063 2061 def _docommit(ui, repo, *pats, **opts):
2064 2062 if opts.get('interactive'):
2065 2063 opts.pop('interactive')
2066 2064 ret = cmdutil.dorecord(
2067 2065 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2068 2066 )
2069 2067 # ret can be 0 (no changes to record) or the value returned by
2070 2068 # commit(), 1 if nothing changed or None on success.
2071 2069 return 1 if ret == 0 else ret
2072 2070
2073 2071 if opts.get('subrepos'):
2074 2072 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2075 2073 # Let --subrepos on the command line override config setting.
2076 2074 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2077 2075
2078 2076 cmdutil.checkunfinished(repo, commit=True)
2079 2077
2080 2078 branch = repo[None].branch()
2081 2079 bheads = repo.branchheads(branch)
2082 2080 tip = repo.changelog.tip()
2083 2081
2084 2082 extra = {}
2085 2083 if opts.get('close_branch') or opts.get('force_close_branch'):
2086 2084 extra[b'close'] = b'1'
2087 2085
2088 2086 if repo[b'.'].closesbranch():
2089 2087 raise error.InputError(
2090 2088 _(b'current revision is already a branch closing head')
2091 2089 )
2092 2090 elif not bheads:
2093 2091 raise error.InputError(
2094 2092 _(b'branch "%s" has no heads to close') % branch
2095 2093 )
2096 2094 elif (
2097 2095 branch == repo[b'.'].branch()
2098 2096 and repo[b'.'].node() not in bheads
2099 2097 and not opts.get('force_close_branch')
2100 2098 ):
2101 2099 hint = _(
2102 2100 b'use --force-close-branch to close branch from a non-head'
2103 2101 b' changeset'
2104 2102 )
2105 2103 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2106 2104 elif opts.get('amend'):
2107 2105 if (
2108 2106 repo[b'.'].p1().branch() != branch
2109 2107 and repo[b'.'].p2().branch() != branch
2110 2108 ):
2111 2109 raise error.InputError(_(b'can only close branch heads'))
2112 2110
2113 2111 if opts.get('amend'):
2114 2112 if ui.configbool(b'ui', b'commitsubrepos'):
2115 2113 raise error.InputError(
2116 2114 _(b'cannot amend with ui.commitsubrepos enabled')
2117 2115 )
2118 2116
2119 2117 old = repo[b'.']
2120 2118 rewriteutil.precheck(repo, [old.rev()], b'amend')
2121 2119
2122 2120 # Currently histedit gets confused if an amend happens while histedit
2123 2121 # is in progress. Since we have a checkunfinished command, we are
2124 2122 # temporarily honoring it.
2125 2123 #
2126 2124 # Note: eventually this guard will be removed. Please do not expect
2127 2125 # this behavior to remain.
2128 2126 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2129 2127 cmdutil.checkunfinished(repo)
2130 2128
2131 2129 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2132 2130 opts = pycompat.byteskwargs(opts)
2133 2131 if node == old.node():
2134 2132 ui.status(_(b"nothing changed\n"))
2135 2133 return 1
2136 2134 else:
2137 2135
2138 2136 def commitfunc(ui, repo, message, match, opts):
2139 2137 overrides = {}
2140 2138 if opts.get(b'secret'):
2141 2139 overrides[(b'phases', b'new-commit')] = b'secret'
2142 2140
2143 2141 baseui = repo.baseui
2144 2142 with baseui.configoverride(overrides, b'commit'):
2145 2143 with ui.configoverride(overrides, b'commit'):
2146 2144 editform = cmdutil.mergeeditform(
2147 2145 repo[None], b'commit.normal'
2148 2146 )
2149 2147 editor = cmdutil.getcommiteditor(
2150 2148 editform=editform, **pycompat.strkwargs(opts)
2151 2149 )
2152 2150 return repo.commit(
2153 2151 message,
2154 2152 opts.get(b'user'),
2155 2153 opts.get(b'date'),
2156 2154 match,
2157 2155 editor=editor,
2158 2156 extra=extra,
2159 2157 )
2160 2158
2161 2159 opts = pycompat.byteskwargs(opts)
2162 2160 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2163 2161
2164 2162 if not node:
2165 2163 stat = cmdutil.postcommitstatus(repo, pats, opts)
2166 2164 if stat.deleted:
2167 2165 ui.status(
2168 2166 _(
2169 2167 b"nothing changed (%d missing files, see "
2170 2168 b"'hg status')\n"
2171 2169 )
2172 2170 % len(stat.deleted)
2173 2171 )
2174 2172 else:
2175 2173 ui.status(_(b"nothing changed\n"))
2176 2174 return 1
2177 2175
2178 2176 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2179 2177
2180 2178 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2181 2179 status(
2182 2180 ui,
2183 2181 repo,
2184 2182 modified=True,
2185 2183 added=True,
2186 2184 removed=True,
2187 2185 deleted=True,
2188 2186 unknown=True,
2189 2187 subrepos=opts.get(b'subrepos'),
2190 2188 )
2191 2189
2192 2190
2193 2191 @command(
2194 2192 b'config|showconfig|debugconfig',
2195 2193 [
2196 2194 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2197 2195 # This is experimental because we need
2198 2196 # * reasonable behavior around aliases,
2199 2197 # * decide if we display [debug] [experimental] and [devel] section par
2200 2198 # default
2201 2199 # * some way to display "generic" config entry (the one matching
2202 2200 # regexp,
2203 2201 # * proper display of the different value type
2204 2202 # * a better way to handle <DYNAMIC> values (and variable types),
2205 2203 # * maybe some type information ?
2206 2204 (
2207 2205 b'',
2208 2206 b'exp-all-known',
2209 2207 None,
2210 2208 _(b'show all known config option (EXPERIMENTAL)'),
2211 2209 ),
2212 2210 (b'e', b'edit', None, _(b'edit user config')),
2213 2211 (b'l', b'local', None, _(b'edit repository config')),
2214 2212 (b'', b'source', None, _(b'show source of configuration value')),
2215 2213 (
2216 2214 b'',
2217 2215 b'shared',
2218 2216 None,
2219 2217 _(b'edit shared source repository config (EXPERIMENTAL)'),
2220 2218 ),
2221 2219 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2222 2220 (b'g', b'global', None, _(b'edit global config')),
2223 2221 ]
2224 2222 + formatteropts,
2225 2223 _(b'[-u] [NAME]...'),
2226 2224 helpcategory=command.CATEGORY_HELP,
2227 2225 optionalrepo=True,
2228 2226 intents={INTENT_READONLY},
2229 2227 )
2230 2228 def config(ui, repo, *values, **opts):
2231 2229 """show combined config settings from all hgrc files
2232 2230
2233 2231 With no arguments, print names and values of all config items.
2234 2232
2235 2233 With one argument of the form section.name, print just the value
2236 2234 of that config item.
2237 2235
2238 2236 With multiple arguments, print names and values of all config
2239 2237 items with matching section names or section.names.
2240 2238
2241 2239 With --edit, start an editor on the user-level config file. With
2242 2240 --global, edit the system-wide config file. With --local, edit the
2243 2241 repository-level config file.
2244 2242
2245 2243 With --source, the source (filename and line number) is printed
2246 2244 for each config item.
2247 2245
2248 2246 See :hg:`help config` for more information about config files.
2249 2247
2250 2248 .. container:: verbose
2251 2249
2252 2250 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2253 2251 This file is not shared across shares when in share-safe mode.
2254 2252
2255 2253 Template:
2256 2254
2257 2255 The following keywords are supported. See also :hg:`help templates`.
2258 2256
2259 2257 :name: String. Config name.
2260 2258 :source: String. Filename and line number where the item is defined.
2261 2259 :value: String. Config value.
2262 2260
2263 2261 The --shared flag can be used to edit the config file of shared source
2264 2262 repository. It only works when you have shared using the experimental
2265 2263 share safe feature.
2266 2264
2267 2265 Returns 0 on success, 1 if NAME does not exist.
2268 2266
2269 2267 """
2270 2268
2271 2269 opts = pycompat.byteskwargs(opts)
2272 2270 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2273 2271 if any(opts.get(o) for o in editopts):
2274 2272 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2275 2273 if opts.get(b'local'):
2276 2274 if not repo:
2277 2275 raise error.InputError(
2278 2276 _(b"can't use --local outside a repository")
2279 2277 )
2280 2278 paths = [repo.vfs.join(b'hgrc')]
2281 2279 elif opts.get(b'global'):
2282 2280 paths = rcutil.systemrcpath()
2283 2281 elif opts.get(b'shared'):
2284 2282 if not repo.shared():
2285 2283 raise error.InputError(
2286 2284 _(b"repository is not shared; can't use --shared")
2287 2285 )
2288 2286 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2289 2287 raise error.InputError(
2290 2288 _(
2291 2289 b"share safe feature not enabled; "
2292 2290 b"unable to edit shared source repository config"
2293 2291 )
2294 2292 )
2295 2293 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2296 2294 elif opts.get(b'non_shared'):
2297 2295 paths = [repo.vfs.join(b'hgrc-not-shared')]
2298 2296 else:
2299 2297 paths = rcutil.userrcpath()
2300 2298
2301 2299 for f in paths:
2302 2300 if os.path.exists(f):
2303 2301 break
2304 2302 else:
2305 2303 if opts.get(b'global'):
2306 2304 samplehgrc = uimod.samplehgrcs[b'global']
2307 2305 elif opts.get(b'local'):
2308 2306 samplehgrc = uimod.samplehgrcs[b'local']
2309 2307 else:
2310 2308 samplehgrc = uimod.samplehgrcs[b'user']
2311 2309
2312 2310 f = paths[0]
2313 2311 fp = open(f, b"wb")
2314 2312 fp.write(util.tonativeeol(samplehgrc))
2315 2313 fp.close()
2316 2314
2317 2315 editor = ui.geteditor()
2318 2316 ui.system(
2319 2317 b"%s \"%s\"" % (editor, f),
2320 2318 onerr=error.InputError,
2321 2319 errprefix=_(b"edit failed"),
2322 2320 blockedtag=b'config_edit',
2323 2321 )
2324 2322 return
2325 2323 ui.pager(b'config')
2326 2324 fm = ui.formatter(b'config', opts)
2327 2325 for t, f in rcutil.rccomponents():
2328 2326 if t == b'path':
2329 2327 ui.debug(b'read config from: %s\n' % f)
2330 2328 elif t == b'resource':
2331 2329 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2332 2330 elif t == b'items':
2333 2331 # Don't print anything for 'items'.
2334 2332 pass
2335 2333 else:
2336 2334 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2337 2335 untrusted = bool(opts.get(b'untrusted'))
2338 2336
2339 2337 selsections = selentries = []
2340 2338 if values:
2341 2339 selsections = [v for v in values if b'.' not in v]
2342 2340 selentries = [v for v in values if b'.' in v]
2343 2341 uniquesel = len(selentries) == 1 and not selsections
2344 2342 selsections = set(selsections)
2345 2343 selentries = set(selentries)
2346 2344
2347 2345 matched = False
2348 2346 all_known = opts[b'exp_all_known']
2349 2347 show_source = ui.debugflag or opts.get(b'source')
2350 2348 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2351 2349 for section, name, value in entries:
2352 2350 source = ui.configsource(section, name, untrusted)
2353 2351 value = pycompat.bytestr(value)
2354 2352 defaultvalue = ui.configdefault(section, name)
2355 2353 if fm.isplain():
2356 2354 source = source or b'none'
2357 2355 value = value.replace(b'\n', b'\\n')
2358 2356 entryname = section + b'.' + name
2359 2357 if values and not (section in selsections or entryname in selentries):
2360 2358 continue
2361 2359 fm.startitem()
2362 2360 fm.condwrite(show_source, b'source', b'%s: ', source)
2363 2361 if uniquesel:
2364 2362 fm.data(name=entryname)
2365 2363 fm.write(b'value', b'%s\n', value)
2366 2364 else:
2367 2365 fm.write(b'name value', b'%s=%s\n', entryname, value)
2368 2366 if formatter.isprintable(defaultvalue):
2369 2367 fm.data(defaultvalue=defaultvalue)
2370 2368 elif isinstance(defaultvalue, list) and all(
2371 2369 formatter.isprintable(e) for e in defaultvalue
2372 2370 ):
2373 2371 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2374 2372 # TODO: no idea how to process unsupported defaultvalue types
2375 2373 matched = True
2376 2374 fm.end()
2377 2375 if matched:
2378 2376 return 0
2379 2377 return 1
2380 2378
2381 2379
2382 2380 @command(
2383 2381 b'continue',
2384 2382 dryrunopts,
2385 2383 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2386 2384 helpbasic=True,
2387 2385 )
2388 2386 def continuecmd(ui, repo, **opts):
2389 2387 """resumes an interrupted operation (EXPERIMENTAL)
2390 2388
2391 2389 Finishes a multistep operation like graft, histedit, rebase, merge,
2392 2390 and unshelve if they are in an interrupted state.
2393 2391
2394 2392 use --dry-run/-n to dry run the command.
2395 2393 """
2396 2394 dryrun = opts.get('dry_run')
2397 2395 contstate = cmdutil.getunfinishedstate(repo)
2398 2396 if not contstate:
2399 2397 raise error.StateError(_(b'no operation in progress'))
2400 2398 if not contstate.continuefunc:
2401 2399 raise error.StateError(
2402 2400 (
2403 2401 _(b"%s in progress but does not support 'hg continue'")
2404 2402 % (contstate._opname)
2405 2403 ),
2406 2404 hint=contstate.continuemsg(),
2407 2405 )
2408 2406 if dryrun:
2409 2407 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2410 2408 return
2411 2409 return contstate.continuefunc(ui, repo)
2412 2410
2413 2411
2414 2412 @command(
2415 2413 b'copy|cp',
2416 2414 [
2417 2415 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2418 2416 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2419 2417 (
2420 2418 b'',
2421 2419 b'at-rev',
2422 2420 b'',
2423 2421 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2424 2422 _(b'REV'),
2425 2423 ),
2426 2424 (
2427 2425 b'f',
2428 2426 b'force',
2429 2427 None,
2430 2428 _(b'forcibly copy over an existing managed file'),
2431 2429 ),
2432 2430 ]
2433 2431 + walkopts
2434 2432 + dryrunopts,
2435 2433 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2436 2434 helpcategory=command.CATEGORY_FILE_CONTENTS,
2437 2435 )
2438 2436 def copy(ui, repo, *pats, **opts):
2439 2437 """mark files as copied for the next commit
2440 2438
2441 2439 Mark dest as having copies of source files. If dest is a
2442 2440 directory, copies are put in that directory. If dest is a file,
2443 2441 the source must be a single file.
2444 2442
2445 2443 By default, this command copies the contents of files as they
2446 2444 exist in the working directory. If invoked with -A/--after, the
2447 2445 operation is recorded, but no copying is performed.
2448 2446
2449 2447 To undo marking a destination file as copied, use --forget. With that
2450 2448 option, all given (positional) arguments are unmarked as copies. The
2451 2449 destination file(s) will be left in place (still tracked). Note that
2452 2450 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2453 2451
2454 2452 This command takes effect with the next commit by default.
2455 2453
2456 2454 Returns 0 on success, 1 if errors are encountered.
2457 2455 """
2458 2456 opts = pycompat.byteskwargs(opts)
2459 2457 with repo.wlock():
2460 2458 return cmdutil.copy(ui, repo, pats, opts)
2461 2459
2462 2460
2463 2461 @command(
2464 2462 b'debugcommands',
2465 2463 [],
2466 2464 _(b'[COMMAND]'),
2467 2465 helpcategory=command.CATEGORY_HELP,
2468 2466 norepo=True,
2469 2467 )
2470 2468 def debugcommands(ui, cmd=b'', *args):
2471 2469 """list all available commands and options"""
2472 2470 for cmd, vals in sorted(table.items()):
2473 2471 cmd = cmd.split(b'|')[0]
2474 2472 opts = b', '.join([i[1] for i in vals[1]])
2475 2473 ui.write(b'%s: %s\n' % (cmd, opts))
2476 2474
2477 2475
2478 2476 @command(
2479 2477 b'debugcomplete',
2480 2478 [(b'o', b'options', None, _(b'show the command options'))],
2481 2479 _(b'[-o] CMD'),
2482 2480 helpcategory=command.CATEGORY_HELP,
2483 2481 norepo=True,
2484 2482 )
2485 2483 def debugcomplete(ui, cmd=b'', **opts):
2486 2484 """returns the completion list associated with the given command"""
2487 2485
2488 2486 if opts.get('options'):
2489 2487 options = []
2490 2488 otables = [globalopts]
2491 2489 if cmd:
2492 2490 aliases, entry = cmdutil.findcmd(cmd, table, False)
2493 2491 otables.append(entry[1])
2494 2492 for t in otables:
2495 2493 for o in t:
2496 2494 if b"(DEPRECATED)" in o[3]:
2497 2495 continue
2498 2496 if o[0]:
2499 2497 options.append(b'-%s' % o[0])
2500 2498 options.append(b'--%s' % o[1])
2501 2499 ui.write(b"%s\n" % b"\n".join(options))
2502 2500 return
2503 2501
2504 2502 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2505 2503 if ui.verbose:
2506 2504 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2507 2505 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2508 2506
2509 2507
2510 2508 @command(
2511 2509 b'diff',
2512 2510 [
2513 2511 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2514 2512 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2515 2513 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2516 2514 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2517 2515 ]
2518 2516 + diffopts
2519 2517 + diffopts2
2520 2518 + walkopts
2521 2519 + subrepoopts,
2522 2520 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2523 2521 helpcategory=command.CATEGORY_FILE_CONTENTS,
2524 2522 helpbasic=True,
2525 2523 inferrepo=True,
2526 2524 intents={INTENT_READONLY},
2527 2525 )
2528 2526 def diff(ui, repo, *pats, **opts):
2529 2527 """diff repository (or selected files)
2530 2528
2531 2529 Show differences between revisions for the specified files.
2532 2530
2533 2531 Differences between files are shown using the unified diff format.
2534 2532
2535 2533 .. note::
2536 2534
2537 2535 :hg:`diff` may generate unexpected results for merges, as it will
2538 2536 default to comparing against the working directory's first
2539 2537 parent changeset if no revisions are specified. To diff against the
2540 2538 conflict regions, you can use `--config diff.merge=yes`.
2541 2539
2542 2540 By default, the working directory files are compared to its first parent. To
2543 2541 see the differences from another revision, use --from. To see the difference
2544 2542 to another revision, use --to. For example, :hg:`diff --from .^` will show
2545 2543 the differences from the working copy's grandparent to the working copy,
2546 2544 :hg:`diff --to .` will show the diff from the working copy to its parent
2547 2545 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2548 2546 show the diff between those two revisions.
2549 2547
2550 2548 Alternatively you can specify -c/--change with a revision to see the changes
2551 2549 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2552 2550 equivalent to :hg:`diff --from 42^ --to 42`)
2553 2551
2554 2552 Without the -a/--text option, diff will avoid generating diffs of
2555 2553 files it detects as binary. With -a, diff will generate a diff
2556 2554 anyway, probably with undesirable results.
2557 2555
2558 2556 Use the -g/--git option to generate diffs in the git extended diff
2559 2557 format. For more information, read :hg:`help diffs`.
2560 2558
2561 2559 .. container:: verbose
2562 2560
2563 2561 Examples:
2564 2562
2565 2563 - compare a file in the current working directory to its parent::
2566 2564
2567 2565 hg diff foo.c
2568 2566
2569 2567 - compare two historical versions of a directory, with rename info::
2570 2568
2571 2569 hg diff --git --from 1.0 --to 1.2 lib/
2572 2570
2573 2571 - get change stats relative to the last change on some date::
2574 2572
2575 2573 hg diff --stat --from "date('may 2')"
2576 2574
2577 2575 - diff all newly-added files that contain a keyword::
2578 2576
2579 2577 hg diff "set:added() and grep(GNU)"
2580 2578
2581 2579 - compare a revision and its parents::
2582 2580
2583 2581 hg diff -c 9353 # compare against first parent
2584 2582 hg diff --from 9353^ --to 9353 # same using revset syntax
2585 2583 hg diff --from 9353^2 --to 9353 # compare against the second parent
2586 2584
2587 2585 Returns 0 on success.
2588 2586 """
2589 2587
2590 2588 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2591 2589 opts = pycompat.byteskwargs(opts)
2592 2590 revs = opts.get(b'rev')
2593 2591 change = opts.get(b'change')
2594 2592 from_rev = opts.get(b'from')
2595 2593 to_rev = opts.get(b'to')
2596 2594 stat = opts.get(b'stat')
2597 2595 reverse = opts.get(b'reverse')
2598 2596
2599 2597 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2600 2598 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2601 2599 if change:
2602 2600 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2603 2601 ctx2 = logcmdutil.revsingle(repo, change, None)
2604 2602 ctx1 = logcmdutil.diff_parent(ctx2)
2605 2603 elif from_rev or to_rev:
2606 2604 repo = scmutil.unhidehashlikerevs(
2607 2605 repo, [from_rev] + [to_rev], b'nowarn'
2608 2606 )
2609 2607 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2610 2608 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2611 2609 else:
2612 2610 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2613 2611 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2614 2612
2615 2613 if reverse:
2616 2614 ctxleft = ctx2
2617 2615 ctxright = ctx1
2618 2616 else:
2619 2617 ctxleft = ctx1
2620 2618 ctxright = ctx2
2621 2619
2622 2620 diffopts = patch.diffallopts(ui, opts)
2623 2621 m = scmutil.match(ctx2, pats, opts)
2624 2622 m = repo.narrowmatch(m)
2625 2623 ui.pager(b'diff')
2626 2624 logcmdutil.diffordiffstat(
2627 2625 ui,
2628 2626 repo,
2629 2627 diffopts,
2630 2628 ctxleft,
2631 2629 ctxright,
2632 2630 m,
2633 2631 stat=stat,
2634 2632 listsubrepos=opts.get(b'subrepos'),
2635 2633 root=opts.get(b'root'),
2636 2634 )
2637 2635
2638 2636
2639 2637 @command(
2640 2638 b'export',
2641 2639 [
2642 2640 (
2643 2641 b'B',
2644 2642 b'bookmark',
2645 2643 b'',
2646 2644 _(b'export changes only reachable by given bookmark'),
2647 2645 _(b'BOOKMARK'),
2648 2646 ),
2649 2647 (
2650 2648 b'o',
2651 2649 b'output',
2652 2650 b'',
2653 2651 _(b'print output to file with formatted name'),
2654 2652 _(b'FORMAT'),
2655 2653 ),
2656 2654 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2657 2655 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2658 2656 ]
2659 2657 + diffopts
2660 2658 + formatteropts,
2661 2659 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2662 2660 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2663 2661 helpbasic=True,
2664 2662 intents={INTENT_READONLY},
2665 2663 )
2666 2664 def export(ui, repo, *changesets, **opts):
2667 2665 """dump the header and diffs for one or more changesets
2668 2666
2669 2667 Print the changeset header and diffs for one or more revisions.
2670 2668 If no revision is given, the parent of the working directory is used.
2671 2669
2672 2670 The information shown in the changeset header is: author, date,
2673 2671 branch name (if non-default), changeset hash, parent(s) and commit
2674 2672 comment.
2675 2673
2676 2674 .. note::
2677 2675
2678 2676 :hg:`export` may generate unexpected diff output for merge
2679 2677 changesets, as it will compare the merge changeset against its
2680 2678 first parent only.
2681 2679
2682 2680 Output may be to a file, in which case the name of the file is
2683 2681 given using a template string. See :hg:`help templates`. In addition
2684 2682 to the common template keywords, the following formatting rules are
2685 2683 supported:
2686 2684
2687 2685 :``%%``: literal "%" character
2688 2686 :``%H``: changeset hash (40 hexadecimal digits)
2689 2687 :``%N``: number of patches being generated
2690 2688 :``%R``: changeset revision number
2691 2689 :``%b``: basename of the exporting repository
2692 2690 :``%h``: short-form changeset hash (12 hexadecimal digits)
2693 2691 :``%m``: first line of the commit message (only alphanumeric characters)
2694 2692 :``%n``: zero-padded sequence number, starting at 1
2695 2693 :``%r``: zero-padded changeset revision number
2696 2694 :``\\``: literal "\\" character
2697 2695
2698 2696 Without the -a/--text option, export will avoid generating diffs
2699 2697 of files it detects as binary. With -a, export will generate a
2700 2698 diff anyway, probably with undesirable results.
2701 2699
2702 2700 With -B/--bookmark changesets reachable by the given bookmark are
2703 2701 selected.
2704 2702
2705 2703 Use the -g/--git option to generate diffs in the git extended diff
2706 2704 format. See :hg:`help diffs` for more information.
2707 2705
2708 2706 With the --switch-parent option, the diff will be against the
2709 2707 second parent. It can be useful to review a merge.
2710 2708
2711 2709 .. container:: verbose
2712 2710
2713 2711 Template:
2714 2712
2715 2713 The following keywords are supported in addition to the common template
2716 2714 keywords and functions. See also :hg:`help templates`.
2717 2715
2718 2716 :diff: String. Diff content.
2719 2717 :parents: List of strings. Parent nodes of the changeset.
2720 2718
2721 2719 Examples:
2722 2720
2723 2721 - use export and import to transplant a bugfix to the current
2724 2722 branch::
2725 2723
2726 2724 hg export -r 9353 | hg import -
2727 2725
2728 2726 - export all the changesets between two revisions to a file with
2729 2727 rename information::
2730 2728
2731 2729 hg export --git -r 123:150 > changes.txt
2732 2730
2733 2731 - split outgoing changes into a series of patches with
2734 2732 descriptive names::
2735 2733
2736 2734 hg export -r "outgoing()" -o "%n-%m.patch"
2737 2735
2738 2736 Returns 0 on success.
2739 2737 """
2740 2738 opts = pycompat.byteskwargs(opts)
2741 2739 bookmark = opts.get(b'bookmark')
2742 2740 changesets += tuple(opts.get(b'rev', []))
2743 2741
2744 2742 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2745 2743
2746 2744 if bookmark:
2747 2745 if bookmark not in repo._bookmarks:
2748 2746 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2749 2747
2750 2748 revs = scmutil.bookmarkrevs(repo, bookmark)
2751 2749 else:
2752 2750 if not changesets:
2753 2751 changesets = [b'.']
2754 2752
2755 2753 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2756 2754 revs = logcmdutil.revrange(repo, changesets)
2757 2755
2758 2756 if not revs:
2759 2757 raise error.InputError(_(b"export requires at least one changeset"))
2760 2758 if len(revs) > 1:
2761 2759 ui.note(_(b'exporting patches:\n'))
2762 2760 else:
2763 2761 ui.note(_(b'exporting patch:\n'))
2764 2762
2765 2763 fntemplate = opts.get(b'output')
2766 2764 if cmdutil.isstdiofilename(fntemplate):
2767 2765 fntemplate = b''
2768 2766
2769 2767 if fntemplate:
2770 2768 fm = formatter.nullformatter(ui, b'export', opts)
2771 2769 else:
2772 2770 ui.pager(b'export')
2773 2771 fm = ui.formatter(b'export', opts)
2774 2772 with fm:
2775 2773 cmdutil.export(
2776 2774 repo,
2777 2775 revs,
2778 2776 fm,
2779 2777 fntemplate=fntemplate,
2780 2778 switch_parent=opts.get(b'switch_parent'),
2781 2779 opts=patch.diffallopts(ui, opts),
2782 2780 )
2783 2781
2784 2782
2785 2783 @command(
2786 2784 b'files',
2787 2785 [
2788 2786 (
2789 2787 b'r',
2790 2788 b'rev',
2791 2789 b'',
2792 2790 _(b'search the repository as it is in REV'),
2793 2791 _(b'REV'),
2794 2792 ),
2795 2793 (
2796 2794 b'0',
2797 2795 b'print0',
2798 2796 None,
2799 2797 _(b'end filenames with NUL, for use with xargs'),
2800 2798 ),
2801 2799 ]
2802 2800 + walkopts
2803 2801 + formatteropts
2804 2802 + subrepoopts,
2805 2803 _(b'[OPTION]... [FILE]...'),
2806 2804 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2807 2805 intents={INTENT_READONLY},
2808 2806 )
2809 2807 def files(ui, repo, *pats, **opts):
2810 2808 """list tracked files
2811 2809
2812 2810 Print files under Mercurial control in the working directory or
2813 2811 specified revision for given files (excluding removed files).
2814 2812 Files can be specified as filenames or filesets.
2815 2813
2816 2814 If no files are given to match, this command prints the names
2817 2815 of all files under Mercurial control.
2818 2816
2819 2817 .. container:: verbose
2820 2818
2821 2819 Template:
2822 2820
2823 2821 The following keywords are supported in addition to the common template
2824 2822 keywords and functions. See also :hg:`help templates`.
2825 2823
2826 2824 :flags: String. Character denoting file's symlink and executable bits.
2827 2825 :path: String. Repository-absolute path of the file.
2828 2826 :size: Integer. Size of the file in bytes.
2829 2827
2830 2828 Examples:
2831 2829
2832 2830 - list all files under the current directory::
2833 2831
2834 2832 hg files .
2835 2833
2836 2834 - shows sizes and flags for current revision::
2837 2835
2838 2836 hg files -vr .
2839 2837
2840 2838 - list all files named README::
2841 2839
2842 2840 hg files -I "**/README"
2843 2841
2844 2842 - list all binary files::
2845 2843
2846 2844 hg files "set:binary()"
2847 2845
2848 2846 - find files containing a regular expression::
2849 2847
2850 2848 hg files "set:grep('bob')"
2851 2849
2852 2850 - search tracked file contents with xargs and grep::
2853 2851
2854 2852 hg files -0 | xargs -0 grep foo
2855 2853
2856 2854 See :hg:`help patterns` and :hg:`help filesets` for more information
2857 2855 on specifying file patterns.
2858 2856
2859 2857 Returns 0 if a match is found, 1 otherwise.
2860 2858
2861 2859 """
2862 2860
2863 2861 opts = pycompat.byteskwargs(opts)
2864 2862 rev = opts.get(b'rev')
2865 2863 if rev:
2866 2864 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2867 2865 ctx = logcmdutil.revsingle(repo, rev, None)
2868 2866
2869 2867 end = b'\n'
2870 2868 if opts.get(b'print0'):
2871 2869 end = b'\0'
2872 2870 fmt = b'%s' + end
2873 2871
2874 2872 m = scmutil.match(ctx, pats, opts)
2875 2873 ui.pager(b'files')
2876 2874 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2877 2875 with ui.formatter(b'files', opts) as fm:
2878 2876 return cmdutil.files(
2879 2877 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2880 2878 )
2881 2879
2882 2880
2883 2881 @command(
2884 2882 b'forget',
2885 2883 [
2886 2884 (b'i', b'interactive', None, _(b'use interactive mode')),
2887 2885 ]
2888 2886 + walkopts
2889 2887 + dryrunopts,
2890 2888 _(b'[OPTION]... FILE...'),
2891 2889 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2892 2890 helpbasic=True,
2893 2891 inferrepo=True,
2894 2892 )
2895 2893 def forget(ui, repo, *pats, **opts):
2896 2894 """forget the specified files on the next commit
2897 2895
2898 2896 Mark the specified files so they will no longer be tracked
2899 2897 after the next commit.
2900 2898
2901 2899 This only removes files from the current branch, not from the
2902 2900 entire project history, and it does not delete them from the
2903 2901 working directory.
2904 2902
2905 2903 To delete the file from the working directory, see :hg:`remove`.
2906 2904
2907 2905 To undo a forget before the next commit, see :hg:`add`.
2908 2906
2909 2907 .. container:: verbose
2910 2908
2911 2909 Examples:
2912 2910
2913 2911 - forget newly-added binary files::
2914 2912
2915 2913 hg forget "set:added() and binary()"
2916 2914
2917 2915 - forget files that would be excluded by .hgignore::
2918 2916
2919 2917 hg forget "set:hgignore()"
2920 2918
2921 2919 Returns 0 on success.
2922 2920 """
2923 2921
2924 2922 opts = pycompat.byteskwargs(opts)
2925 2923 if not pats:
2926 2924 raise error.InputError(_(b'no files specified'))
2927 2925
2928 2926 m = scmutil.match(repo[None], pats, opts)
2929 2927 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2930 2928 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2931 2929 rejected = cmdutil.forget(
2932 2930 ui,
2933 2931 repo,
2934 2932 m,
2935 2933 prefix=b"",
2936 2934 uipathfn=uipathfn,
2937 2935 explicitonly=False,
2938 2936 dryrun=dryrun,
2939 2937 interactive=interactive,
2940 2938 )[0]
2941 2939 return rejected and 1 or 0
2942 2940
2943 2941
2944 2942 @command(
2945 2943 b'graft',
2946 2944 [
2947 2945 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2948 2946 (
2949 2947 b'',
2950 2948 b'base',
2951 2949 b'',
2952 2950 _(b'base revision when doing the graft merge (ADVANCED)'),
2953 2951 _(b'REV'),
2954 2952 ),
2955 2953 (b'c', b'continue', False, _(b'resume interrupted graft')),
2956 2954 (b'', b'stop', False, _(b'stop interrupted graft')),
2957 2955 (b'', b'abort', False, _(b'abort interrupted graft')),
2958 2956 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2959 2957 (b'', b'log', None, _(b'append graft info to log message')),
2960 2958 (
2961 2959 b'',
2962 2960 b'no-commit',
2963 2961 None,
2964 2962 _(b"don't commit, just apply the changes in working directory"),
2965 2963 ),
2966 2964 (b'f', b'force', False, _(b'force graft')),
2967 2965 (
2968 2966 b'D',
2969 2967 b'currentdate',
2970 2968 False,
2971 2969 _(b'record the current date as commit date'),
2972 2970 ),
2973 2971 (
2974 2972 b'U',
2975 2973 b'currentuser',
2976 2974 False,
2977 2975 _(b'record the current user as committer'),
2978 2976 ),
2979 2977 ]
2980 2978 + commitopts2
2981 2979 + mergetoolopts
2982 2980 + dryrunopts,
2983 2981 _(b'[OPTION]... [-r REV]... REV...'),
2984 2982 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2985 2983 )
2986 2984 def graft(ui, repo, *revs, **opts):
2987 2985 """copy changes from other branches onto the current branch
2988 2986
2989 2987 This command uses Mercurial's merge logic to copy individual
2990 2988 changes from other branches without merging branches in the
2991 2989 history graph. This is sometimes known as 'backporting' or
2992 2990 'cherry-picking'. By default, graft will copy user, date, and
2993 2991 description from the source changesets.
2994 2992
2995 2993 Changesets that are ancestors of the current revision, that have
2996 2994 already been grafted, or that are merges will be skipped.
2997 2995
2998 2996 If --log is specified, log messages will have a comment appended
2999 2997 of the form::
3000 2998
3001 2999 (grafted from CHANGESETHASH)
3002 3000
3003 3001 If --force is specified, revisions will be grafted even if they
3004 3002 are already ancestors of, or have been grafted to, the destination.
3005 3003 This is useful when the revisions have since been backed out.
3006 3004
3007 3005 If a graft merge results in conflicts, the graft process is
3008 3006 interrupted so that the current merge can be manually resolved.
3009 3007 Once all conflicts are addressed, the graft process can be
3010 3008 continued with the -c/--continue option.
3011 3009
3012 3010 The -c/--continue option reapplies all the earlier options.
3013 3011
3014 3012 .. container:: verbose
3015 3013
3016 3014 The --base option exposes more of how graft internally uses merge with a
3017 3015 custom base revision. --base can be used to specify another ancestor than
3018 3016 the first and only parent.
3019 3017
3020 3018 The command::
3021 3019
3022 3020 hg graft -r 345 --base 234
3023 3021
3024 3022 is thus pretty much the same as::
3025 3023
3026 3024 hg diff --from 234 --to 345 | hg import
3027 3025
3028 3026 but using merge to resolve conflicts and track moved files.
3029 3027
3030 3028 The result of a merge can thus be backported as a single commit by
3031 3029 specifying one of the merge parents as base, and thus effectively
3032 3030 grafting the changes from the other side.
3033 3031
3034 3032 It is also possible to collapse multiple changesets and clean up history
3035 3033 by specifying another ancestor as base, much like rebase --collapse
3036 3034 --keep.
3037 3035
3038 3036 The commit message can be tweaked after the fact using commit --amend .
3039 3037
3040 3038 For using non-ancestors as the base to backout changes, see the backout
3041 3039 command and the hidden --parent option.
3042 3040
3043 3041 .. container:: verbose
3044 3042
3045 3043 Examples:
3046 3044
3047 3045 - copy a single change to the stable branch and edit its description::
3048 3046
3049 3047 hg update stable
3050 3048 hg graft --edit 9393
3051 3049
3052 3050 - graft a range of changesets with one exception, updating dates::
3053 3051
3054 3052 hg graft -D "2085::2093 and not 2091"
3055 3053
3056 3054 - continue a graft after resolving conflicts::
3057 3055
3058 3056 hg graft -c
3059 3057
3060 3058 - show the source of a grafted changeset::
3061 3059
3062 3060 hg log --debug -r .
3063 3061
3064 3062 - show revisions sorted by date::
3065 3063
3066 3064 hg log -r "sort(all(), date)"
3067 3065
3068 3066 - backport the result of a merge as a single commit::
3069 3067
3070 3068 hg graft -r 123 --base 123^
3071 3069
3072 3070 - land a feature branch as one changeset::
3073 3071
3074 3072 hg up -cr default
3075 3073 hg graft -r featureX --base "ancestor('featureX', 'default')"
3076 3074
3077 3075 See :hg:`help revisions` for more about specifying revisions.
3078 3076
3079 3077 Returns 0 on successful completion, 1 if there are unresolved files.
3080 3078 """
3081 3079 with repo.wlock():
3082 3080 return _dograft(ui, repo, *revs, **opts)
3083 3081
3084 3082
3085 3083 def _dograft(ui, repo, *revs, **opts):
3086 3084 if revs and opts.get('rev'):
3087 3085 ui.warn(
3088 3086 _(
3089 3087 b'warning: inconsistent use of --rev might give unexpected '
3090 3088 b'revision ordering!\n'
3091 3089 )
3092 3090 )
3093 3091
3094 3092 revs = list(revs)
3095 3093 revs.extend(opts.get('rev'))
3096 3094 # a dict of data to be stored in state file
3097 3095 statedata = {}
3098 3096 # list of new nodes created by ongoing graft
3099 3097 statedata[b'newnodes'] = []
3100 3098
3101 3099 cmdutil.resolve_commit_options(ui, opts)
3102 3100
3103 3101 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3104 3102
3105 3103 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3106 3104
3107 3105 cont = False
3108 3106 if opts.get('no_commit'):
3109 3107 cmdutil.check_incompatible_arguments(
3110 3108 opts,
3111 3109 'no_commit',
3112 3110 ['edit', 'currentuser', 'currentdate', 'log'],
3113 3111 )
3114 3112
3115 3113 graftstate = statemod.cmdstate(repo, b'graftstate')
3116 3114
3117 3115 if opts.get('stop'):
3118 3116 cmdutil.check_incompatible_arguments(
3119 3117 opts,
3120 3118 'stop',
3121 3119 [
3122 3120 'edit',
3123 3121 'log',
3124 3122 'user',
3125 3123 'date',
3126 3124 'currentdate',
3127 3125 'currentuser',
3128 3126 'rev',
3129 3127 ],
3130 3128 )
3131 3129 return _stopgraft(ui, repo, graftstate)
3132 3130 elif opts.get('abort'):
3133 3131 cmdutil.check_incompatible_arguments(
3134 3132 opts,
3135 3133 'abort',
3136 3134 [
3137 3135 'edit',
3138 3136 'log',
3139 3137 'user',
3140 3138 'date',
3141 3139 'currentdate',
3142 3140 'currentuser',
3143 3141 'rev',
3144 3142 ],
3145 3143 )
3146 3144 return cmdutil.abortgraft(ui, repo, graftstate)
3147 3145 elif opts.get('continue'):
3148 3146 cont = True
3149 3147 if revs:
3150 3148 raise error.InputError(_(b"can't specify --continue and revisions"))
3151 3149 # read in unfinished revisions
3152 3150 if graftstate.exists():
3153 3151 statedata = cmdutil.readgraftstate(repo, graftstate)
3154 3152 if statedata.get(b'date'):
3155 3153 opts['date'] = statedata[b'date']
3156 3154 if statedata.get(b'user'):
3157 3155 opts['user'] = statedata[b'user']
3158 3156 if statedata.get(b'log'):
3159 3157 opts['log'] = True
3160 3158 if statedata.get(b'no_commit'):
3161 3159 opts['no_commit'] = statedata.get(b'no_commit')
3162 3160 if statedata.get(b'base'):
3163 3161 opts['base'] = statedata.get(b'base')
3164 3162 nodes = statedata[b'nodes']
3165 3163 revs = [repo[node].rev() for node in nodes]
3166 3164 else:
3167 3165 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3168 3166 else:
3169 3167 if not revs:
3170 3168 raise error.InputError(_(b'no revisions specified'))
3171 3169 cmdutil.checkunfinished(repo)
3172 3170 cmdutil.bailifchanged(repo)
3173 3171 revs = logcmdutil.revrange(repo, revs)
3174 3172
3175 3173 skipped = set()
3176 3174 basectx = None
3177 3175 if opts.get('base'):
3178 3176 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3179 3177 if basectx is None:
3180 3178 # check for merges
3181 3179 for rev in repo.revs(b'%ld and merge()', revs):
3182 3180 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3183 3181 skipped.add(rev)
3184 3182 revs = [r for r in revs if r not in skipped]
3185 3183 if not revs:
3186 3184 return -1
3187 3185 if basectx is not None and len(revs) != 1:
3188 3186 raise error.InputError(_(b'only one revision allowed with --base '))
3189 3187
3190 3188 # Don't check in the --continue case, in effect retaining --force across
3191 3189 # --continues. That's because without --force, any revisions we decided to
3192 3190 # skip would have been filtered out here, so they wouldn't have made their
3193 3191 # way to the graftstate. With --force, any revisions we would have otherwise
3194 3192 # skipped would not have been filtered out, and if they hadn't been applied
3195 3193 # already, they'd have been in the graftstate.
3196 3194 if not (cont or opts.get('force')) and basectx is None:
3197 3195 # check for ancestors of dest branch
3198 3196 ancestors = repo.revs(b'%ld & (::.)', revs)
3199 3197 for rev in ancestors:
3200 3198 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3201 3199
3202 3200 revs = [r for r in revs if r not in ancestors]
3203 3201
3204 3202 if not revs:
3205 3203 return -1
3206 3204
3207 3205 # analyze revs for earlier grafts
3208 3206 ids = {}
3209 3207 for ctx in repo.set(b"%ld", revs):
3210 3208 ids[ctx.hex()] = ctx.rev()
3211 3209 n = ctx.extra().get(b'source')
3212 3210 if n:
3213 3211 ids[n] = ctx.rev()
3214 3212
3215 3213 # check ancestors for earlier grafts
3216 3214 ui.debug(b'scanning for duplicate grafts\n')
3217 3215
3218 3216 # The only changesets we can be sure doesn't contain grafts of any
3219 3217 # revs, are the ones that are common ancestors of *all* revs:
3220 3218 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3221 3219 ctx = repo[rev]
3222 3220 n = ctx.extra().get(b'source')
3223 3221 if n in ids:
3224 3222 try:
3225 3223 r = repo[n].rev()
3226 3224 except error.RepoLookupError:
3227 3225 r = None
3228 3226 if r in revs:
3229 3227 ui.warn(
3230 3228 _(
3231 3229 b'skipping revision %d:%s '
3232 3230 b'(already grafted to %d:%s)\n'
3233 3231 )
3234 3232 % (r, repo[r], rev, ctx)
3235 3233 )
3236 3234 revs.remove(r)
3237 3235 elif ids[n] in revs:
3238 3236 if r is None:
3239 3237 ui.warn(
3240 3238 _(
3241 3239 b'skipping already grafted revision %d:%s '
3242 3240 b'(%d:%s also has unknown origin %s)\n'
3243 3241 )
3244 3242 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3245 3243 )
3246 3244 else:
3247 3245 ui.warn(
3248 3246 _(
3249 3247 b'skipping already grafted revision %d:%s '
3250 3248 b'(%d:%s also has origin %d:%s)\n'
3251 3249 )
3252 3250 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3253 3251 )
3254 3252 revs.remove(ids[n])
3255 3253 elif ctx.hex() in ids:
3256 3254 r = ids[ctx.hex()]
3257 3255 if r in revs:
3258 3256 ui.warn(
3259 3257 _(
3260 3258 b'skipping already grafted revision %d:%s '
3261 3259 b'(was grafted from %d:%s)\n'
3262 3260 )
3263 3261 % (r, repo[r], rev, ctx)
3264 3262 )
3265 3263 revs.remove(r)
3266 3264 if not revs:
3267 3265 return -1
3268 3266
3269 3267 if opts.get('no_commit'):
3270 3268 statedata[b'no_commit'] = True
3271 3269 if opts.get('base'):
3272 3270 statedata[b'base'] = opts['base']
3273 3271 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3274 3272 desc = b'%d:%s "%s"' % (
3275 3273 ctx.rev(),
3276 3274 ctx,
3277 3275 ctx.description().split(b'\n', 1)[0],
3278 3276 )
3279 3277 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3280 3278 if names:
3281 3279 desc += b' (%s)' % b' '.join(names)
3282 3280 ui.status(_(b'grafting %s\n') % desc)
3283 3281 if opts.get('dry_run'):
3284 3282 continue
3285 3283
3286 3284 source = ctx.extra().get(b'source')
3287 3285 extra = {}
3288 3286 if source:
3289 3287 extra[b'source'] = source
3290 3288 extra[b'intermediate-source'] = ctx.hex()
3291 3289 else:
3292 3290 extra[b'source'] = ctx.hex()
3293 3291 user = ctx.user()
3294 3292 if opts.get('user'):
3295 3293 user = opts['user']
3296 3294 statedata[b'user'] = user
3297 3295 date = ctx.date()
3298 3296 if opts.get('date'):
3299 3297 date = opts['date']
3300 3298 statedata[b'date'] = date
3301 3299 message = ctx.description()
3302 3300 if opts.get('log'):
3303 3301 message += b'\n(grafted from %s)' % ctx.hex()
3304 3302 statedata[b'log'] = True
3305 3303
3306 3304 # we don't merge the first commit when continuing
3307 3305 if not cont:
3308 3306 # perform the graft merge with p1(rev) as 'ancestor'
3309 3307 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3310 3308 base = ctx.p1() if basectx is None else basectx
3311 3309 with ui.configoverride(overrides, b'graft'):
3312 3310 stats = mergemod.graft(
3313 3311 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3314 3312 )
3315 3313 # report any conflicts
3316 3314 if stats.unresolvedcount > 0:
3317 3315 # write out state for --continue
3318 3316 nodes = [repo[rev].hex() for rev in revs[pos:]]
3319 3317 statedata[b'nodes'] = nodes
3320 3318 stateversion = 1
3321 3319 graftstate.save(stateversion, statedata)
3322 3320 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3323 3321 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3324 3322 return 1
3325 3323 else:
3326 3324 cont = False
3327 3325
3328 3326 # commit if --no-commit is false
3329 3327 if not opts.get('no_commit'):
3330 3328 node = repo.commit(
3331 3329 text=message, user=user, date=date, extra=extra, editor=editor
3332 3330 )
3333 3331 if node is None:
3334 3332 ui.warn(
3335 3333 _(b'note: graft of %d:%s created no changes to commit\n')
3336 3334 % (ctx.rev(), ctx)
3337 3335 )
3338 3336 # checking that newnodes exist because old state files won't have it
3339 3337 elif statedata.get(b'newnodes') is not None:
3340 3338 nn = statedata[b'newnodes']
3341 3339 assert isinstance(nn, list) # list of bytes
3342 3340 nn.append(node)
3343 3341
3344 3342 # remove state when we complete successfully
3345 3343 if not opts.get('dry_run'):
3346 3344 graftstate.delete()
3347 3345
3348 3346 return 0
3349 3347
3350 3348
3351 3349 def _stopgraft(ui, repo, graftstate):
3352 3350 """stop the interrupted graft"""
3353 3351 if not graftstate.exists():
3354 3352 raise error.StateError(_(b"no interrupted graft found"))
3355 3353 pctx = repo[b'.']
3356 3354 mergemod.clean_update(pctx)
3357 3355 graftstate.delete()
3358 3356 ui.status(_(b"stopped the interrupted graft\n"))
3359 3357 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3360 3358 return 0
3361 3359
3362 3360
3363 3361 statemod.addunfinished(
3364 3362 b'graft',
3365 3363 fname=b'graftstate',
3366 3364 clearable=True,
3367 3365 stopflag=True,
3368 3366 continueflag=True,
3369 3367 abortfunc=cmdutil.hgabortgraft,
3370 3368 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3371 3369 )
3372 3370
3373 3371
3374 3372 @command(
3375 3373 b'grep',
3376 3374 [
3377 3375 (b'0', b'print0', None, _(b'end fields with NUL')),
3378 3376 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3379 3377 (
3380 3378 b'',
3381 3379 b'diff',
3382 3380 None,
3383 3381 _(
3384 3382 b'search revision differences for when the pattern was added '
3385 3383 b'or removed'
3386 3384 ),
3387 3385 ),
3388 3386 (b'a', b'text', None, _(b'treat all files as text')),
3389 3387 (
3390 3388 b'f',
3391 3389 b'follow',
3392 3390 None,
3393 3391 _(
3394 3392 b'follow changeset history,'
3395 3393 b' or file history across copies and renames'
3396 3394 ),
3397 3395 ),
3398 3396 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3399 3397 (
3400 3398 b'l',
3401 3399 b'files-with-matches',
3402 3400 None,
3403 3401 _(b'print only filenames and revisions that match'),
3404 3402 ),
3405 3403 (b'n', b'line-number', None, _(b'print matching line numbers')),
3406 3404 (
3407 3405 b'r',
3408 3406 b'rev',
3409 3407 [],
3410 3408 _(b'search files changed within revision range'),
3411 3409 _(b'REV'),
3412 3410 ),
3413 3411 (
3414 3412 b'',
3415 3413 b'all-files',
3416 3414 None,
3417 3415 _(
3418 3416 b'include all files in the changeset while grepping (DEPRECATED)'
3419 3417 ),
3420 3418 ),
3421 3419 (b'u', b'user', None, _(b'list the author (long with -v)')),
3422 3420 (b'd', b'date', None, _(b'list the date (short with -q)')),
3423 3421 ]
3424 3422 + formatteropts
3425 3423 + walkopts,
3426 3424 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3427 3425 helpcategory=command.CATEGORY_FILE_CONTENTS,
3428 3426 inferrepo=True,
3429 3427 intents={INTENT_READONLY},
3430 3428 )
3431 3429 def grep(ui, repo, pattern, *pats, **opts):
3432 3430 """search for a pattern in specified files
3433 3431
3434 3432 Search the working directory or revision history for a regular
3435 3433 expression in the specified files for the entire repository.
3436 3434
3437 3435 By default, grep searches the repository files in the working
3438 3436 directory and prints the files where it finds a match. To specify
3439 3437 historical revisions instead of the working directory, use the
3440 3438 --rev flag.
3441 3439
3442 3440 To search instead historical revision differences that contains a
3443 3441 change in match status ("-" for a match that becomes a non-match,
3444 3442 or "+" for a non-match that becomes a match), use the --diff flag.
3445 3443
3446 3444 PATTERN can be any Python (roughly Perl-compatible) regular
3447 3445 expression.
3448 3446
3449 3447 If no FILEs are specified and the --rev flag isn't supplied, all
3450 3448 files in the working directory are searched. When using the --rev
3451 3449 flag and specifying FILEs, use the --follow argument to also
3452 3450 follow the specified FILEs across renames and copies.
3453 3451
3454 3452 .. container:: verbose
3455 3453
3456 3454 Template:
3457 3455
3458 3456 The following keywords are supported in addition to the common template
3459 3457 keywords and functions. See also :hg:`help templates`.
3460 3458
3461 3459 :change: String. Character denoting insertion ``+`` or removal ``-``.
3462 3460 Available if ``--diff`` is specified.
3463 3461 :lineno: Integer. Line number of the match.
3464 3462 :path: String. Repository-absolute path of the file.
3465 3463 :texts: List of text chunks.
3466 3464
3467 3465 And each entry of ``{texts}`` provides the following sub-keywords.
3468 3466
3469 3467 :matched: Boolean. True if the chunk matches the specified pattern.
3470 3468 :text: String. Chunk content.
3471 3469
3472 3470 See :hg:`help templates.operators` for the list expansion syntax.
3473 3471
3474 3472 Returns 0 if a match is found, 1 otherwise.
3475 3473
3476 3474 """
3477 3475 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3478 3476 opts = pycompat.byteskwargs(opts)
3479 3477 diff = opts.get(b'all') or opts.get(b'diff')
3480 3478 follow = opts.get(b'follow')
3481 3479 if opts.get(b'all_files') is None and not diff:
3482 3480 opts[b'all_files'] = True
3483 3481 plaingrep = (
3484 3482 opts.get(b'all_files')
3485 3483 and not opts.get(b'rev')
3486 3484 and not opts.get(b'follow')
3487 3485 )
3488 3486 all_files = opts.get(b'all_files')
3489 3487 if plaingrep:
3490 3488 opts[b'rev'] = [b'wdir()']
3491 3489
3492 3490 reflags = re.M
3493 3491 if opts.get(b'ignore_case'):
3494 3492 reflags |= re.I
3495 3493 try:
3496 3494 regexp = util.re.compile(pattern, reflags)
3497 3495 except re.error as inst:
3498 3496 ui.warn(
3499 3497 _(b"grep: invalid match pattern: %s\n")
3500 3498 % stringutil.forcebytestr(inst)
3501 3499 )
3502 3500 return 1
3503 3501 sep, eol = b':', b'\n'
3504 3502 if opts.get(b'print0'):
3505 3503 sep = eol = b'\0'
3506 3504
3507 3505 searcher = grepmod.grepsearcher(
3508 3506 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3509 3507 )
3510 3508
3511 3509 getfile = searcher._getfile
3512 3510
3513 3511 uipathfn = scmutil.getuipathfn(repo)
3514 3512
3515 3513 def display(fm, fn, ctx, pstates, states):
3516 3514 rev = scmutil.intrev(ctx)
3517 3515 if fm.isplain():
3518 3516 formatuser = ui.shortuser
3519 3517 else:
3520 3518 formatuser = pycompat.bytestr
3521 3519 if ui.quiet:
3522 3520 datefmt = b'%Y-%m-%d'
3523 3521 else:
3524 3522 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3525 3523 found = False
3526 3524
3527 3525 @util.cachefunc
3528 3526 def binary():
3529 3527 flog = getfile(fn)
3530 3528 try:
3531 3529 return stringutil.binary(flog.read(ctx.filenode(fn)))
3532 3530 except error.WdirUnsupported:
3533 3531 return ctx[fn].isbinary()
3534 3532
3535 3533 fieldnamemap = {b'linenumber': b'lineno'}
3536 3534 if diff:
3537 3535 iter = grepmod.difflinestates(pstates, states)
3538 3536 else:
3539 3537 iter = [(b'', l) for l in states]
3540 3538 for change, l in iter:
3541 3539 fm.startitem()
3542 3540 fm.context(ctx=ctx)
3543 3541 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3544 3542 fm.plain(uipathfn(fn), label=b'grep.filename')
3545 3543
3546 3544 cols = [
3547 3545 (b'rev', b'%d', rev, not plaingrep, b''),
3548 3546 (
3549 3547 b'linenumber',
3550 3548 b'%d',
3551 3549 l.linenum,
3552 3550 opts.get(b'line_number'),
3553 3551 b'',
3554 3552 ),
3555 3553 ]
3556 3554 if diff:
3557 3555 cols.append(
3558 3556 (
3559 3557 b'change',
3560 3558 b'%s',
3561 3559 change,
3562 3560 True,
3563 3561 b'grep.inserted '
3564 3562 if change == b'+'
3565 3563 else b'grep.deleted ',
3566 3564 )
3567 3565 )
3568 3566 cols.extend(
3569 3567 [
3570 3568 (
3571 3569 b'user',
3572 3570 b'%s',
3573 3571 formatuser(ctx.user()),
3574 3572 opts.get(b'user'),
3575 3573 b'',
3576 3574 ),
3577 3575 (
3578 3576 b'date',
3579 3577 b'%s',
3580 3578 fm.formatdate(ctx.date(), datefmt),
3581 3579 opts.get(b'date'),
3582 3580 b'',
3583 3581 ),
3584 3582 ]
3585 3583 )
3586 3584 for name, fmt, data, cond, extra_label in cols:
3587 3585 if cond:
3588 3586 fm.plain(sep, label=b'grep.sep')
3589 3587 field = fieldnamemap.get(name, name)
3590 3588 label = extra_label + (b'grep.%s' % name)
3591 3589 fm.condwrite(cond, field, fmt, data, label=label)
3592 3590 if not opts.get(b'files_with_matches'):
3593 3591 fm.plain(sep, label=b'grep.sep')
3594 3592 if not opts.get(b'text') and binary():
3595 3593 fm.plain(_(b" Binary file matches"))
3596 3594 else:
3597 3595 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3598 3596 fm.plain(eol)
3599 3597 found = True
3600 3598 if opts.get(b'files_with_matches'):
3601 3599 break
3602 3600 return found
3603 3601
3604 3602 def displaymatches(fm, l):
3605 3603 p = 0
3606 3604 for s, e in l.findpos(regexp):
3607 3605 if p < s:
3608 3606 fm.startitem()
3609 3607 fm.write(b'text', b'%s', l.line[p:s])
3610 3608 fm.data(matched=False)
3611 3609 fm.startitem()
3612 3610 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3613 3611 fm.data(matched=True)
3614 3612 p = e
3615 3613 if p < len(l.line):
3616 3614 fm.startitem()
3617 3615 fm.write(b'text', b'%s', l.line[p:])
3618 3616 fm.data(matched=False)
3619 3617 fm.end()
3620 3618
3621 3619 found = False
3622 3620
3623 3621 wopts = logcmdutil.walkopts(
3624 3622 pats=pats,
3625 3623 opts=opts,
3626 3624 revspec=opts[b'rev'],
3627 3625 include_pats=opts[b'include'],
3628 3626 exclude_pats=opts[b'exclude'],
3629 3627 follow=follow,
3630 3628 force_changelog_traversal=all_files,
3631 3629 filter_revisions_by_pats=not all_files,
3632 3630 )
3633 3631 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3634 3632
3635 3633 ui.pager(b'grep')
3636 3634 fm = ui.formatter(b'grep', opts)
3637 3635 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3638 3636 r = display(fm, fn, ctx, pstates, states)
3639 3637 found = found or r
3640 3638 if r and not diff and not all_files:
3641 3639 searcher.skipfile(fn, ctx.rev())
3642 3640 fm.end()
3643 3641
3644 3642 return not found
3645 3643
3646 3644
3647 3645 @command(
3648 3646 b'heads',
3649 3647 [
3650 3648 (
3651 3649 b'r',
3652 3650 b'rev',
3653 3651 b'',
3654 3652 _(b'show only heads which are descendants of STARTREV'),
3655 3653 _(b'STARTREV'),
3656 3654 ),
3657 3655 (b't', b'topo', False, _(b'show topological heads only')),
3658 3656 (
3659 3657 b'a',
3660 3658 b'active',
3661 3659 False,
3662 3660 _(b'show active branchheads only (DEPRECATED)'),
3663 3661 ),
3664 3662 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3665 3663 ]
3666 3664 + templateopts,
3667 3665 _(b'[-ct] [-r STARTREV] [REV]...'),
3668 3666 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3669 3667 intents={INTENT_READONLY},
3670 3668 )
3671 3669 def heads(ui, repo, *branchrevs, **opts):
3672 3670 """show branch heads
3673 3671
3674 3672 With no arguments, show all open branch heads in the repository.
3675 3673 Branch heads are changesets that have no descendants on the
3676 3674 same branch. They are where development generally takes place and
3677 3675 are the usual targets for update and merge operations.
3678 3676
3679 3677 If one or more REVs are given, only open branch heads on the
3680 3678 branches associated with the specified changesets are shown. This
3681 3679 means that you can use :hg:`heads .` to see the heads on the
3682 3680 currently checked-out branch.
3683 3681
3684 3682 If -c/--closed is specified, also show branch heads marked closed
3685 3683 (see :hg:`commit --close-branch`).
3686 3684
3687 3685 If STARTREV is specified, only those heads that are descendants of
3688 3686 STARTREV will be displayed.
3689 3687
3690 3688 If -t/--topo is specified, named branch mechanics will be ignored and only
3691 3689 topological heads (changesets with no children) will be shown.
3692 3690
3693 3691 Returns 0 if matching heads are found, 1 if not.
3694 3692 """
3695 3693
3696 3694 opts = pycompat.byteskwargs(opts)
3697 3695 start = None
3698 3696 rev = opts.get(b'rev')
3699 3697 if rev:
3700 3698 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3701 3699 start = logcmdutil.revsingle(repo, rev, None).node()
3702 3700
3703 3701 if opts.get(b'topo'):
3704 3702 heads = [repo[h] for h in repo.heads(start)]
3705 3703 else:
3706 3704 heads = []
3707 3705 for branch in repo.branchmap():
3708 3706 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3709 3707 heads = [repo[h] for h in heads]
3710 3708
3711 3709 if branchrevs:
3712 3710 branches = {
3713 3711 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3714 3712 }
3715 3713 heads = [h for h in heads if h.branch() in branches]
3716 3714
3717 3715 if opts.get(b'active') and branchrevs:
3718 3716 dagheads = repo.heads(start)
3719 3717 heads = [h for h in heads if h.node() in dagheads]
3720 3718
3721 3719 if branchrevs:
3722 3720 haveheads = {h.branch() for h in heads}
3723 3721 if branches - haveheads:
3724 3722 headless = b', '.join(b for b in branches - haveheads)
3725 3723 msg = _(b'no open branch heads found on branches %s')
3726 3724 if opts.get(b'rev'):
3727 3725 msg += _(b' (started at %s)') % opts[b'rev']
3728 3726 ui.warn((msg + b'\n') % headless)
3729 3727
3730 3728 if not heads:
3731 3729 return 1
3732 3730
3733 3731 ui.pager(b'heads')
3734 3732 heads = sorted(heads, key=lambda x: -(x.rev()))
3735 3733 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3736 3734 for ctx in heads:
3737 3735 displayer.show(ctx)
3738 3736 displayer.close()
3739 3737
3740 3738
3741 3739 @command(
3742 3740 b'help',
3743 3741 [
3744 3742 (b'e', b'extension', None, _(b'show only help for extensions')),
3745 3743 (b'c', b'command', None, _(b'show only help for commands')),
3746 3744 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3747 3745 (
3748 3746 b's',
3749 3747 b'system',
3750 3748 [],
3751 3749 _(b'show help for specific platform(s)'),
3752 3750 _(b'PLATFORM'),
3753 3751 ),
3754 3752 ],
3755 3753 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3756 3754 helpcategory=command.CATEGORY_HELP,
3757 3755 norepo=True,
3758 3756 intents={INTENT_READONLY},
3759 3757 )
3760 3758 def help_(ui, name=None, **opts):
3761 3759 """show help for a given topic or a help overview
3762 3760
3763 3761 With no arguments, print a list of commands with short help messages.
3764 3762
3765 3763 Given a topic, extension, or command name, print help for that
3766 3764 topic.
3767 3765
3768 3766 Returns 0 if successful.
3769 3767 """
3770 3768
3771 3769 keep = opts.get('system') or []
3772 3770 if len(keep) == 0:
3773 3771 if pycompat.sysplatform.startswith(b'win'):
3774 3772 keep.append(b'windows')
3775 3773 elif pycompat.sysplatform == b'OpenVMS':
3776 3774 keep.append(b'vms')
3777 3775 elif pycompat.sysplatform == b'plan9':
3778 3776 keep.append(b'plan9')
3779 3777 else:
3780 3778 keep.append(b'unix')
3781 3779 keep.append(pycompat.sysplatform.lower())
3782 3780 if ui.verbose:
3783 3781 keep.append(b'verbose')
3784 3782
3785 3783 commands = sys.modules[__name__]
3786 3784 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3787 3785 ui.pager(b'help')
3788 3786 ui.write(formatted)
3789 3787
3790 3788
3791 3789 @command(
3792 3790 b'identify|id',
3793 3791 [
3794 3792 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3795 3793 (b'n', b'num', None, _(b'show local revision number')),
3796 3794 (b'i', b'id', None, _(b'show global revision id')),
3797 3795 (b'b', b'branch', None, _(b'show branch')),
3798 3796 (b't', b'tags', None, _(b'show tags')),
3799 3797 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3800 3798 ]
3801 3799 + remoteopts
3802 3800 + formatteropts,
3803 3801 _(b'[-nibtB] [-r REV] [SOURCE]'),
3804 3802 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3805 3803 optionalrepo=True,
3806 3804 intents={INTENT_READONLY},
3807 3805 )
3808 3806 def identify(
3809 3807 ui,
3810 3808 repo,
3811 3809 source=None,
3812 3810 rev=None,
3813 3811 num=None,
3814 3812 id=None,
3815 3813 branch=None,
3816 3814 tags=None,
3817 3815 bookmarks=None,
3818 3816 **opts
3819 3817 ):
3820 3818 """identify the working directory or specified revision
3821 3819
3822 3820 Print a summary identifying the repository state at REV using one or
3823 3821 two parent hash identifiers, followed by a "+" if the working
3824 3822 directory has uncommitted changes, the branch name (if not default),
3825 3823 a list of tags, and a list of bookmarks.
3826 3824
3827 3825 When REV is not given, print a summary of the current state of the
3828 3826 repository including the working directory. Specify -r. to get information
3829 3827 of the working directory parent without scanning uncommitted changes.
3830 3828
3831 3829 Specifying a path to a repository root or Mercurial bundle will
3832 3830 cause lookup to operate on that repository/bundle.
3833 3831
3834 3832 .. container:: verbose
3835 3833
3836 3834 Template:
3837 3835
3838 3836 The following keywords are supported in addition to the common template
3839 3837 keywords and functions. See also :hg:`help templates`.
3840 3838
3841 3839 :dirty: String. Character ``+`` denoting if the working directory has
3842 3840 uncommitted changes.
3843 3841 :id: String. One or two nodes, optionally followed by ``+``.
3844 3842 :parents: List of strings. Parent nodes of the changeset.
3845 3843
3846 3844 Examples:
3847 3845
3848 3846 - generate a build identifier for the working directory::
3849 3847
3850 3848 hg id --id > build-id.dat
3851 3849
3852 3850 - find the revision corresponding to a tag::
3853 3851
3854 3852 hg id -n -r 1.3
3855 3853
3856 3854 - check the most recent revision of a remote repository::
3857 3855
3858 3856 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3859 3857
3860 3858 See :hg:`log` for generating more information about specific revisions,
3861 3859 including full hash identifiers.
3862 3860
3863 3861 Returns 0 if successful.
3864 3862 """
3865 3863
3866 3864 opts = pycompat.byteskwargs(opts)
3867 3865 if not repo and not source:
3868 3866 raise error.InputError(
3869 3867 _(b"there is no Mercurial repository here (.hg not found)")
3870 3868 )
3871 3869
3872 3870 default = not (num or id or branch or tags or bookmarks)
3873 3871 output = []
3874 3872 revs = []
3875 3873
3876 3874 peer = None
3877 3875 try:
3878 3876 if source:
3879 3877 source, branches = urlutil.get_unique_pull_path(
3880 3878 b'identify', repo, ui, source
3881 3879 )
3882 3880 # only pass ui when no repo
3883 3881 peer = hg.peer(repo or ui, opts, source)
3884 3882 repo = peer.local()
3885 3883 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3886 3884
3887 3885 fm = ui.formatter(b'identify', opts)
3888 3886 fm.startitem()
3889 3887
3890 3888 if not repo:
3891 3889 if num or branch or tags:
3892 3890 raise error.InputError(
3893 3891 _(b"can't query remote revision number, branch, or tags")
3894 3892 )
3895 3893 if not rev and revs:
3896 3894 rev = revs[0]
3897 3895 if not rev:
3898 3896 rev = b"tip"
3899 3897
3900 3898 remoterev = peer.lookup(rev)
3901 3899 hexrev = fm.hexfunc(remoterev)
3902 3900 if default or id:
3903 3901 output = [hexrev]
3904 3902 fm.data(id=hexrev)
3905 3903
3906 3904 @util.cachefunc
3907 3905 def getbms():
3908 3906 bms = []
3909 3907
3910 3908 if b'bookmarks' in peer.listkeys(b'namespaces'):
3911 3909 hexremoterev = hex(remoterev)
3912 3910 bms = [
3913 3911 bm
3914 3912 for bm, bmr in peer.listkeys(b'bookmarks').items()
3915 3913 if bmr == hexremoterev
3916 3914 ]
3917 3915
3918 3916 return sorted(bms)
3919 3917
3920 3918 if fm.isplain():
3921 3919 if bookmarks:
3922 3920 output.extend(getbms())
3923 3921 elif default and not ui.quiet:
3924 3922 # multiple bookmarks for a single parent separated by '/'
3925 3923 bm = b'/'.join(getbms())
3926 3924 if bm:
3927 3925 output.append(bm)
3928 3926 else:
3929 3927 fm.data(node=hex(remoterev))
3930 3928 if bookmarks or b'bookmarks' in fm.datahint():
3931 3929 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3932 3930 else:
3933 3931 if rev:
3934 3932 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3935 3933 ctx = logcmdutil.revsingle(repo, rev, None)
3936 3934
3937 3935 if ctx.rev() is None:
3938 3936 ctx = repo[None]
3939 3937 parents = ctx.parents()
3940 3938 taglist = []
3941 3939 for p in parents:
3942 3940 taglist.extend(p.tags())
3943 3941
3944 3942 dirty = b""
3945 3943 if ctx.dirty(missing=True, merge=False, branch=False):
3946 3944 dirty = b'+'
3947 3945 fm.data(dirty=dirty)
3948 3946
3949 3947 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3950 3948 if default or id:
3951 3949 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3952 3950 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3953 3951
3954 3952 if num:
3955 3953 numoutput = [b"%d" % p.rev() for p in parents]
3956 3954 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3957 3955
3958 3956 fm.data(
3959 3957 parents=fm.formatlist(
3960 3958 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3961 3959 )
3962 3960 )
3963 3961 else:
3964 3962 hexoutput = fm.hexfunc(ctx.node())
3965 3963 if default or id:
3966 3964 output = [hexoutput]
3967 3965 fm.data(id=hexoutput)
3968 3966
3969 3967 if num:
3970 3968 output.append(pycompat.bytestr(ctx.rev()))
3971 3969 taglist = ctx.tags()
3972 3970
3973 3971 if default and not ui.quiet:
3974 3972 b = ctx.branch()
3975 3973 if b != b'default':
3976 3974 output.append(b"(%s)" % b)
3977 3975
3978 3976 # multiple tags for a single parent separated by '/'
3979 3977 t = b'/'.join(taglist)
3980 3978 if t:
3981 3979 output.append(t)
3982 3980
3983 3981 # multiple bookmarks for a single parent separated by '/'
3984 3982 bm = b'/'.join(ctx.bookmarks())
3985 3983 if bm:
3986 3984 output.append(bm)
3987 3985 else:
3988 3986 if branch:
3989 3987 output.append(ctx.branch())
3990 3988
3991 3989 if tags:
3992 3990 output.extend(taglist)
3993 3991
3994 3992 if bookmarks:
3995 3993 output.extend(ctx.bookmarks())
3996 3994
3997 3995 fm.data(node=ctx.hex())
3998 3996 fm.data(branch=ctx.branch())
3999 3997 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4000 3998 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4001 3999 fm.context(ctx=ctx)
4002 4000
4003 4001 fm.plain(b"%s\n" % b' '.join(output))
4004 4002 fm.end()
4005 4003 finally:
4006 4004 if peer:
4007 4005 peer.close()
4008 4006
4009 4007
4010 4008 @command(
4011 4009 b'import|patch',
4012 4010 [
4013 4011 (
4014 4012 b'p',
4015 4013 b'strip',
4016 4014 1,
4017 4015 _(
4018 4016 b'directory strip option for patch. This has the same '
4019 4017 b'meaning as the corresponding patch option'
4020 4018 ),
4021 4019 _(b'NUM'),
4022 4020 ),
4023 4021 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4024 4022 (b'', b'secret', None, _(b'use the secret phase for committing')),
4025 4023 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4026 4024 (
4027 4025 b'f',
4028 4026 b'force',
4029 4027 None,
4030 4028 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4031 4029 ),
4032 4030 (
4033 4031 b'',
4034 4032 b'no-commit',
4035 4033 None,
4036 4034 _(b"don't commit, just update the working directory"),
4037 4035 ),
4038 4036 (
4039 4037 b'',
4040 4038 b'bypass',
4041 4039 None,
4042 4040 _(b"apply patch without touching the working directory"),
4043 4041 ),
4044 4042 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4045 4043 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4046 4044 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4047 4045 (
4048 4046 b'',
4049 4047 b'import-branch',
4050 4048 None,
4051 4049 _(b'use any branch information in patch (implied by --exact)'),
4052 4050 ),
4053 4051 ]
4054 4052 + commitopts
4055 4053 + commitopts2
4056 4054 + similarityopts,
4057 4055 _(b'[OPTION]... PATCH...'),
4058 4056 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4059 4057 )
4060 4058 def import_(ui, repo, patch1=None, *patches, **opts):
4061 4059 """import an ordered set of patches
4062 4060
4063 4061 Import a list of patches and commit them individually (unless
4064 4062 --no-commit is specified).
4065 4063
4066 4064 To read a patch from standard input (stdin), use "-" as the patch
4067 4065 name. If a URL is specified, the patch will be downloaded from
4068 4066 there.
4069 4067
4070 4068 Import first applies changes to the working directory (unless
4071 4069 --bypass is specified), import will abort if there are outstanding
4072 4070 changes.
4073 4071
4074 4072 Use --bypass to apply and commit patches directly to the
4075 4073 repository, without affecting the working directory. Without
4076 4074 --exact, patches will be applied on top of the working directory
4077 4075 parent revision.
4078 4076
4079 4077 You can import a patch straight from a mail message. Even patches
4080 4078 as attachments work (to use the body part, it must have type
4081 4079 text/plain or text/x-patch). From and Subject headers of email
4082 4080 message are used as default committer and commit message. All
4083 4081 text/plain body parts before first diff are added to the commit
4084 4082 message.
4085 4083
4086 4084 If the imported patch was generated by :hg:`export`, user and
4087 4085 description from patch override values from message headers and
4088 4086 body. Values given on command line with -m/--message and -u/--user
4089 4087 override these.
4090 4088
4091 4089 If --exact is specified, import will set the working directory to
4092 4090 the parent of each patch before applying it, and will abort if the
4093 4091 resulting changeset has a different ID than the one recorded in
4094 4092 the patch. This will guard against various ways that portable
4095 4093 patch formats and mail systems might fail to transfer Mercurial
4096 4094 data or metadata. See :hg:`bundle` for lossless transmission.
4097 4095
4098 4096 Use --partial to ensure a changeset will be created from the patch
4099 4097 even if some hunks fail to apply. Hunks that fail to apply will be
4100 4098 written to a <target-file>.rej file. Conflicts can then be resolved
4101 4099 by hand before :hg:`commit --amend` is run to update the created
4102 4100 changeset. This flag exists to let people import patches that
4103 4101 partially apply without losing the associated metadata (author,
4104 4102 date, description, ...).
4105 4103
4106 4104 .. note::
4107 4105
4108 4106 When no hunks apply cleanly, :hg:`import --partial` will create
4109 4107 an empty changeset, importing only the patch metadata.
4110 4108
4111 4109 With -s/--similarity, hg will attempt to discover renames and
4112 4110 copies in the patch in the same way as :hg:`addremove`.
4113 4111
4114 4112 It is possible to use external patch programs to perform the patch
4115 4113 by setting the ``ui.patch`` configuration option. For the default
4116 4114 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4117 4115 See :hg:`help config` for more information about configuration
4118 4116 files and how to use these options.
4119 4117
4120 4118 See :hg:`help dates` for a list of formats valid for -d/--date.
4121 4119
4122 4120 .. container:: verbose
4123 4121
4124 4122 Examples:
4125 4123
4126 4124 - import a traditional patch from a website and detect renames::
4127 4125
4128 4126 hg import -s 80 http://example.com/bugfix.patch
4129 4127
4130 4128 - import a changeset from an hgweb server::
4131 4129
4132 4130 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4133 4131
4134 4132 - import all the patches in an Unix-style mbox::
4135 4133
4136 4134 hg import incoming-patches.mbox
4137 4135
4138 4136 - import patches from stdin::
4139 4137
4140 4138 hg import -
4141 4139
4142 4140 - attempt to exactly restore an exported changeset (not always
4143 4141 possible)::
4144 4142
4145 4143 hg import --exact proposed-fix.patch
4146 4144
4147 4145 - use an external tool to apply a patch which is too fuzzy for
4148 4146 the default internal tool.
4149 4147
4150 4148 hg import --config ui.patch="patch --merge" fuzzy.patch
4151 4149
4152 4150 - change the default fuzzing from 2 to a less strict 7
4153 4151
4154 4152 hg import --config ui.fuzz=7 fuzz.patch
4155 4153
4156 4154 Returns 0 on success, 1 on partial success (see --partial).
4157 4155 """
4158 4156
4159 4157 cmdutil.check_incompatible_arguments(
4160 4158 opts, 'no_commit', ['bypass', 'secret']
4161 4159 )
4162 4160 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4163 4161 opts = pycompat.byteskwargs(opts)
4164 4162 if not patch1:
4165 4163 raise error.InputError(_(b'need at least one patch to import'))
4166 4164
4167 4165 patches = (patch1,) + patches
4168 4166
4169 4167 date = opts.get(b'date')
4170 4168 if date:
4171 4169 opts[b'date'] = dateutil.parsedate(date)
4172 4170
4173 4171 exact = opts.get(b'exact')
4174 4172 update = not opts.get(b'bypass')
4175 4173 try:
4176 4174 sim = float(opts.get(b'similarity') or 0)
4177 4175 except ValueError:
4178 4176 raise error.InputError(_(b'similarity must be a number'))
4179 4177 if sim < 0 or sim > 100:
4180 4178 raise error.InputError(_(b'similarity must be between 0 and 100'))
4181 4179 if sim and not update:
4182 4180 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4183 4181
4184 4182 base = opts[b"base"]
4185 4183 msgs = []
4186 4184 ret = 0
4187 4185
4188 4186 with repo.wlock():
4189 4187 if update:
4190 4188 cmdutil.checkunfinished(repo)
4191 4189 if exact or not opts.get(b'force'):
4192 4190 cmdutil.bailifchanged(repo)
4193 4191
4194 4192 if not opts.get(b'no_commit'):
4195 4193 lock = repo.lock
4196 4194 tr = lambda: repo.transaction(b'import')
4197 4195 dsguard = util.nullcontextmanager
4198 4196 else:
4199 4197 lock = util.nullcontextmanager
4200 4198 tr = util.nullcontextmanager
4201 4199 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4202 4200 with lock(), tr(), dsguard():
4203 4201 parents = repo[None].parents()
4204 4202 for patchurl in patches:
4205 4203 if patchurl == b'-':
4206 4204 ui.status(_(b'applying patch from stdin\n'))
4207 4205 patchfile = ui.fin
4208 4206 patchurl = b'stdin' # for error message
4209 4207 else:
4210 4208 patchurl = os.path.join(base, patchurl)
4211 4209 ui.status(_(b'applying %s\n') % patchurl)
4212 4210 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4213 4211
4214 4212 haspatch = False
4215 4213 for hunk in patch.split(patchfile):
4216 4214 with patch.extract(ui, hunk) as patchdata:
4217 4215 msg, node, rej = cmdutil.tryimportone(
4218 4216 ui, repo, patchdata, parents, opts, msgs, hg.clean
4219 4217 )
4220 4218 if msg:
4221 4219 haspatch = True
4222 4220 ui.note(msg + b'\n')
4223 4221 if update or exact:
4224 4222 parents = repo[None].parents()
4225 4223 else:
4226 4224 parents = [repo[node]]
4227 4225 if rej:
4228 4226 ui.write_err(_(b"patch applied partially\n"))
4229 4227 ui.write_err(
4230 4228 _(
4231 4229 b"(fix the .rej files and run "
4232 4230 b"`hg commit --amend`)\n"
4233 4231 )
4234 4232 )
4235 4233 ret = 1
4236 4234 break
4237 4235
4238 4236 if not haspatch:
4239 4237 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4240 4238
4241 4239 if msgs:
4242 4240 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4243 4241 return ret
4244 4242
4245 4243
4246 4244 @command(
4247 4245 b'incoming|in',
4248 4246 [
4249 4247 (
4250 4248 b'f',
4251 4249 b'force',
4252 4250 None,
4253 4251 _(b'run even if remote repository is unrelated'),
4254 4252 ),
4255 4253 (b'n', b'newest-first', None, _(b'show newest record first')),
4256 4254 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4257 4255 (
4258 4256 b'r',
4259 4257 b'rev',
4260 4258 [],
4261 4259 _(b'a remote changeset intended to be added'),
4262 4260 _(b'REV'),
4263 4261 ),
4264 4262 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4265 4263 (
4266 4264 b'b',
4267 4265 b'branch',
4268 4266 [],
4269 4267 _(b'a specific branch you would like to pull'),
4270 4268 _(b'BRANCH'),
4271 4269 ),
4272 4270 ]
4273 4271 + logopts
4274 4272 + remoteopts
4275 4273 + subrepoopts,
4276 4274 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4277 4275 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4278 4276 )
4279 4277 def incoming(ui, repo, source=b"default", **opts):
4280 4278 """show new changesets found in source
4281 4279
4282 4280 Show new changesets found in the specified path/URL or the default
4283 4281 pull location. These are the changesets that would have been pulled
4284 4282 by :hg:`pull` at the time you issued this command.
4285 4283
4286 4284 See pull for valid source format details.
4287 4285
4288 4286 .. container:: verbose
4289 4287
4290 4288 With -B/--bookmarks, the result of bookmark comparison between
4291 4289 local and remote repositories is displayed. With -v/--verbose,
4292 4290 status is also displayed for each bookmark like below::
4293 4291
4294 4292 BM1 01234567890a added
4295 4293 BM2 1234567890ab advanced
4296 4294 BM3 234567890abc diverged
4297 4295 BM4 34567890abcd changed
4298 4296
4299 4297 The action taken locally when pulling depends on the
4300 4298 status of each bookmark:
4301 4299
4302 4300 :``added``: pull will create it
4303 4301 :``advanced``: pull will update it
4304 4302 :``diverged``: pull will create a divergent bookmark
4305 4303 :``changed``: result depends on remote changesets
4306 4304
4307 4305 From the point of view of pulling behavior, bookmark
4308 4306 existing only in the remote repository are treated as ``added``,
4309 4307 even if it is in fact locally deleted.
4310 4308
4311 4309 .. container:: verbose
4312 4310
4313 4311 For remote repository, using --bundle avoids downloading the
4314 4312 changesets twice if the incoming is followed by a pull.
4315 4313
4316 4314 Examples:
4317 4315
4318 4316 - show incoming changes with patches and full description::
4319 4317
4320 4318 hg incoming -vp
4321 4319
4322 4320 - show incoming changes excluding merges, store a bundle::
4323 4321
4324 4322 hg in -vpM --bundle incoming.hg
4325 4323 hg pull incoming.hg
4326 4324
4327 4325 - briefly list changes inside a bundle::
4328 4326
4329 4327 hg in changes.hg -T "{desc|firstline}\\n"
4330 4328
4331 4329 Returns 0 if there are incoming changes, 1 otherwise.
4332 4330 """
4333 4331 opts = pycompat.byteskwargs(opts)
4334 4332 if opts.get(b'graph'):
4335 4333 logcmdutil.checkunsupportedgraphflags([], opts)
4336 4334
4337 4335 def display(other, chlist, displayer):
4338 4336 revdag = logcmdutil.graphrevs(other, chlist, opts)
4339 4337 logcmdutil.displaygraph(
4340 4338 ui, repo, revdag, displayer, graphmod.asciiedges
4341 4339 )
4342 4340
4343 4341 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4344 4342 return 0
4345 4343
4346 4344 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4347 4345
4348 4346 if opts.get(b'bookmarks'):
4349 4347 srcs = urlutil.get_pull_paths(repo, ui, [source])
4350 4348 for path in srcs:
4351 4349 source, branches = urlutil.parseurl(
4352 4350 path.rawloc, opts.get(b'branch')
4353 4351 )
4354 4352 other = hg.peer(repo, opts, source)
4355 4353 try:
4356 4354 if b'bookmarks' not in other.listkeys(b'namespaces'):
4357 4355 ui.warn(_(b"remote doesn't support bookmarks\n"))
4358 4356 return 0
4359 4357 ui.pager(b'incoming')
4360 4358 ui.status(
4361 4359 _(b'comparing with %s\n') % urlutil.hidepassword(source)
4362 4360 )
4363 4361 return bookmarks.incoming(
4364 4362 ui, repo, other, mode=path.bookmarks_mode
4365 4363 )
4366 4364 finally:
4367 4365 other.close()
4368 4366
4369 4367 return hg.incoming(ui, repo, source, opts)
4370 4368
4371 4369
4372 4370 @command(
4373 4371 b'init',
4374 4372 remoteopts,
4375 4373 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4376 4374 helpcategory=command.CATEGORY_REPO_CREATION,
4377 4375 helpbasic=True,
4378 4376 norepo=True,
4379 4377 )
4380 4378 def init(ui, dest=b".", **opts):
4381 4379 """create a new repository in the given directory
4382 4380
4383 4381 Initialize a new repository in the given directory. If the given
4384 4382 directory does not exist, it will be created.
4385 4383
4386 4384 If no directory is given, the current directory is used.
4387 4385
4388 4386 It is possible to specify an ``ssh://`` URL as the destination.
4389 4387 See :hg:`help urls` for more information.
4390 4388
4391 4389 Returns 0 on success.
4392 4390 """
4393 4391 opts = pycompat.byteskwargs(opts)
4394 4392 path = urlutil.get_clone_path(ui, dest)[1]
4395 4393 peer = hg.peer(ui, opts, path, create=True)
4396 4394 peer.close()
4397 4395
4398 4396
4399 4397 @command(
4400 4398 b'locate',
4401 4399 [
4402 4400 (
4403 4401 b'r',
4404 4402 b'rev',
4405 4403 b'',
4406 4404 _(b'search the repository as it is in REV'),
4407 4405 _(b'REV'),
4408 4406 ),
4409 4407 (
4410 4408 b'0',
4411 4409 b'print0',
4412 4410 None,
4413 4411 _(b'end filenames with NUL, for use with xargs'),
4414 4412 ),
4415 4413 (
4416 4414 b'f',
4417 4415 b'fullpath',
4418 4416 None,
4419 4417 _(b'print complete paths from the filesystem root'),
4420 4418 ),
4421 4419 ]
4422 4420 + walkopts,
4423 4421 _(b'[OPTION]... [PATTERN]...'),
4424 4422 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4425 4423 )
4426 4424 def locate(ui, repo, *pats, **opts):
4427 4425 """locate files matching specific patterns (DEPRECATED)
4428 4426
4429 4427 Print files under Mercurial control in the working directory whose
4430 4428 names match the given patterns.
4431 4429
4432 4430 By default, this command searches all directories in the working
4433 4431 directory. To search just the current directory and its
4434 4432 subdirectories, use "--include .".
4435 4433
4436 4434 If no patterns are given to match, this command prints the names
4437 4435 of all files under Mercurial control in the working directory.
4438 4436
4439 4437 If you want to feed the output of this command into the "xargs"
4440 4438 command, use the -0 option to both this command and "xargs". This
4441 4439 will avoid the problem of "xargs" treating single filenames that
4442 4440 contain whitespace as multiple filenames.
4443 4441
4444 4442 See :hg:`help files` for a more versatile command.
4445 4443
4446 4444 Returns 0 if a match is found, 1 otherwise.
4447 4445 """
4448 4446 opts = pycompat.byteskwargs(opts)
4449 4447 if opts.get(b'print0'):
4450 4448 end = b'\0'
4451 4449 else:
4452 4450 end = b'\n'
4453 4451 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4454 4452
4455 4453 ret = 1
4456 4454 m = scmutil.match(
4457 4455 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4458 4456 )
4459 4457
4460 4458 ui.pager(b'locate')
4461 4459 if ctx.rev() is None:
4462 4460 # When run on the working copy, "locate" includes removed files, so
4463 4461 # we get the list of files from the dirstate.
4464 4462 filesgen = sorted(repo.dirstate.matches(m))
4465 4463 else:
4466 4464 filesgen = ctx.matches(m)
4467 4465 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4468 4466 for abs in filesgen:
4469 4467 if opts.get(b'fullpath'):
4470 4468 ui.write(repo.wjoin(abs), end)
4471 4469 else:
4472 4470 ui.write(uipathfn(abs), end)
4473 4471 ret = 0
4474 4472
4475 4473 return ret
4476 4474
4477 4475
4478 4476 @command(
4479 4477 b'log|history',
4480 4478 [
4481 4479 (
4482 4480 b'f',
4483 4481 b'follow',
4484 4482 None,
4485 4483 _(
4486 4484 b'follow changeset history, or file history across copies and renames'
4487 4485 ),
4488 4486 ),
4489 4487 (
4490 4488 b'',
4491 4489 b'follow-first',
4492 4490 None,
4493 4491 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4494 4492 ),
4495 4493 (
4496 4494 b'd',
4497 4495 b'date',
4498 4496 b'',
4499 4497 _(b'show revisions matching date spec'),
4500 4498 _(b'DATE'),
4501 4499 ),
4502 4500 (b'C', b'copies', None, _(b'show copied files')),
4503 4501 (
4504 4502 b'k',
4505 4503 b'keyword',
4506 4504 [],
4507 4505 _(b'do case-insensitive search for a given text'),
4508 4506 _(b'TEXT'),
4509 4507 ),
4510 4508 (
4511 4509 b'r',
4512 4510 b'rev',
4513 4511 [],
4514 4512 _(b'revisions to select or follow from'),
4515 4513 _(b'REV'),
4516 4514 ),
4517 4515 (
4518 4516 b'L',
4519 4517 b'line-range',
4520 4518 [],
4521 4519 _(b'follow line range of specified file (EXPERIMENTAL)'),
4522 4520 _(b'FILE,RANGE'),
4523 4521 ),
4524 4522 (
4525 4523 b'',
4526 4524 b'removed',
4527 4525 None,
4528 4526 _(b'include revisions where files were removed'),
4529 4527 ),
4530 4528 (
4531 4529 b'm',
4532 4530 b'only-merges',
4533 4531 None,
4534 4532 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4535 4533 ),
4536 4534 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4537 4535 (
4538 4536 b'',
4539 4537 b'only-branch',
4540 4538 [],
4541 4539 _(
4542 4540 b'show only changesets within the given named branch (DEPRECATED)'
4543 4541 ),
4544 4542 _(b'BRANCH'),
4545 4543 ),
4546 4544 (
4547 4545 b'b',
4548 4546 b'branch',
4549 4547 [],
4550 4548 _(b'show changesets within the given named branch'),
4551 4549 _(b'BRANCH'),
4552 4550 ),
4553 4551 (
4554 4552 b'B',
4555 4553 b'bookmark',
4556 4554 [],
4557 4555 _(b"show changesets within the given bookmark"),
4558 4556 _(b'BOOKMARK'),
4559 4557 ),
4560 4558 (
4561 4559 b'P',
4562 4560 b'prune',
4563 4561 [],
4564 4562 _(b'do not display revision or any of its ancestors'),
4565 4563 _(b'REV'),
4566 4564 ),
4567 4565 ]
4568 4566 + logopts
4569 4567 + walkopts,
4570 4568 _(b'[OPTION]... [FILE]'),
4571 4569 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4572 4570 helpbasic=True,
4573 4571 inferrepo=True,
4574 4572 intents={INTENT_READONLY},
4575 4573 )
4576 4574 def log(ui, repo, *pats, **opts):
4577 4575 """show revision history of entire repository or files
4578 4576
4579 4577 Print the revision history of the specified files or the entire
4580 4578 project.
4581 4579
4582 4580 If no revision range is specified, the default is ``tip:0`` unless
4583 4581 --follow is set.
4584 4582
4585 4583 File history is shown without following rename or copy history of
4586 4584 files. Use -f/--follow with a filename to follow history across
4587 4585 renames and copies. --follow without a filename will only show
4588 4586 ancestors of the starting revisions. The starting revisions can be
4589 4587 specified by -r/--rev, which default to the working directory parent.
4590 4588
4591 4589 By default this command prints revision number and changeset id,
4592 4590 tags, non-trivial parents, user, date and time, and a summary for
4593 4591 each commit. When the -v/--verbose switch is used, the list of
4594 4592 changed files and full commit message are shown.
4595 4593
4596 4594 With --graph the revisions are shown as an ASCII art DAG with the most
4597 4595 recent changeset at the top.
4598 4596 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4599 4597 involved in an unresolved merge conflict, '_' closes a branch,
4600 4598 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4601 4599 changeset from the lines below is a parent of the 'o' merge on the same
4602 4600 line.
4603 4601 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4604 4602 of a '|' indicates one or more revisions in a path are omitted.
4605 4603
4606 4604 .. container:: verbose
4607 4605
4608 4606 Use -L/--line-range FILE,M:N options to follow the history of lines
4609 4607 from M to N in FILE. With -p/--patch only diff hunks affecting
4610 4608 specified line range will be shown. This option requires --follow;
4611 4609 it can be specified multiple times. Currently, this option is not
4612 4610 compatible with --graph. This option is experimental.
4613 4611
4614 4612 .. note::
4615 4613
4616 4614 :hg:`log --patch` may generate unexpected diff output for merge
4617 4615 changesets, as it will only compare the merge changeset against
4618 4616 its first parent. Also, only files different from BOTH parents
4619 4617 will appear in files:.
4620 4618
4621 4619 .. note::
4622 4620
4623 4621 For performance reasons, :hg:`log FILE` may omit duplicate changes
4624 4622 made on branches and will not show removals or mode changes. To
4625 4623 see all such changes, use the --removed switch.
4626 4624
4627 4625 .. container:: verbose
4628 4626
4629 4627 .. note::
4630 4628
4631 4629 The history resulting from -L/--line-range options depends on diff
4632 4630 options; for instance if white-spaces are ignored, respective changes
4633 4631 with only white-spaces in specified line range will not be listed.
4634 4632
4635 4633 .. container:: verbose
4636 4634
4637 4635 Some examples:
4638 4636
4639 4637 - changesets with full descriptions and file lists::
4640 4638
4641 4639 hg log -v
4642 4640
4643 4641 - changesets ancestral to the working directory::
4644 4642
4645 4643 hg log -f
4646 4644
4647 4645 - last 10 commits on the current branch::
4648 4646
4649 4647 hg log -l 10 -b .
4650 4648
4651 4649 - changesets showing all modifications of a file, including removals::
4652 4650
4653 4651 hg log --removed file.c
4654 4652
4655 4653 - all changesets that touch a directory, with diffs, excluding merges::
4656 4654
4657 4655 hg log -Mp lib/
4658 4656
4659 4657 - all revision numbers that match a keyword::
4660 4658
4661 4659 hg log -k bug --template "{rev}\\n"
4662 4660
4663 4661 - the full hash identifier of the working directory parent::
4664 4662
4665 4663 hg log -r . --template "{node}\\n"
4666 4664
4667 4665 - list available log templates::
4668 4666
4669 4667 hg log -T list
4670 4668
4671 4669 - check if a given changeset is included in a tagged release::
4672 4670
4673 4671 hg log -r "a21ccf and ancestor(1.9)"
4674 4672
4675 4673 - find all changesets by some user in a date range::
4676 4674
4677 4675 hg log -k alice -d "may 2008 to jul 2008"
4678 4676
4679 4677 - summary of all changesets after the last tag::
4680 4678
4681 4679 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4682 4680
4683 4681 - changesets touching lines 13 to 23 for file.c::
4684 4682
4685 4683 hg log -L file.c,13:23
4686 4684
4687 4685 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4688 4686 main.c with patch::
4689 4687
4690 4688 hg log -L file.c,13:23 -L main.c,2:6 -p
4691 4689
4692 4690 See :hg:`help dates` for a list of formats valid for -d/--date.
4693 4691
4694 4692 See :hg:`help revisions` for more about specifying and ordering
4695 4693 revisions.
4696 4694
4697 4695 See :hg:`help templates` for more about pre-packaged styles and
4698 4696 specifying custom templates. The default template used by the log
4699 4697 command can be customized via the ``command-templates.log`` configuration
4700 4698 setting.
4701 4699
4702 4700 Returns 0 on success.
4703 4701
4704 4702 """
4705 4703 opts = pycompat.byteskwargs(opts)
4706 4704 linerange = opts.get(b'line_range')
4707 4705
4708 4706 if linerange and not opts.get(b'follow'):
4709 4707 raise error.InputError(_(b'--line-range requires --follow'))
4710 4708
4711 4709 if linerange and pats:
4712 4710 # TODO: take pats as patterns with no line-range filter
4713 4711 raise error.InputError(
4714 4712 _(b'FILE arguments are not compatible with --line-range option')
4715 4713 )
4716 4714
4717 4715 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4718 4716 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4719 4717 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4720 4718 if linerange:
4721 4719 # TODO: should follow file history from logcmdutil._initialrevs(),
4722 4720 # then filter the result by logcmdutil._makerevset() and --limit
4723 4721 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4724 4722
4725 4723 getcopies = None
4726 4724 if opts.get(b'copies'):
4727 4725 endrev = None
4728 4726 if revs:
4729 4727 endrev = revs.max() + 1
4730 4728 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4731 4729
4732 4730 ui.pager(b'log')
4733 4731 displayer = logcmdutil.changesetdisplayer(
4734 4732 ui, repo, opts, differ, buffered=True
4735 4733 )
4736 4734 if opts.get(b'graph'):
4737 4735 displayfn = logcmdutil.displaygraphrevs
4738 4736 else:
4739 4737 displayfn = logcmdutil.displayrevs
4740 4738 displayfn(ui, repo, revs, displayer, getcopies)
4741 4739
4742 4740
4743 4741 @command(
4744 4742 b'manifest',
4745 4743 [
4746 4744 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4747 4745 (b'', b'all', False, _(b"list files from all revisions")),
4748 4746 ]
4749 4747 + formatteropts,
4750 4748 _(b'[-r REV]'),
4751 4749 helpcategory=command.CATEGORY_MAINTENANCE,
4752 4750 intents={INTENT_READONLY},
4753 4751 )
4754 4752 def manifest(ui, repo, node=None, rev=None, **opts):
4755 4753 """output the current or given revision of the project manifest
4756 4754
4757 4755 Print a list of version controlled files for the given revision.
4758 4756 If no revision is given, the first parent of the working directory
4759 4757 is used, or the null revision if no revision is checked out.
4760 4758
4761 4759 With -v, print file permissions, symlink and executable bits.
4762 4760 With --debug, print file revision hashes.
4763 4761
4764 4762 If option --all is specified, the list of all files from all revisions
4765 4763 is printed. This includes deleted and renamed files.
4766 4764
4767 4765 Returns 0 on success.
4768 4766 """
4769 4767 opts = pycompat.byteskwargs(opts)
4770 4768 fm = ui.formatter(b'manifest', opts)
4771 4769
4772 4770 if opts.get(b'all'):
4773 4771 if rev or node:
4774 4772 raise error.InputError(_(b"can't specify a revision with --all"))
4775 4773
4776 4774 res = set()
4777 4775 for rev in repo:
4778 4776 ctx = repo[rev]
4779 4777 res |= set(ctx.files())
4780 4778
4781 4779 ui.pager(b'manifest')
4782 4780 for f in sorted(res):
4783 4781 fm.startitem()
4784 4782 fm.write(b"path", b'%s\n', f)
4785 4783 fm.end()
4786 4784 return
4787 4785
4788 4786 if rev and node:
4789 4787 raise error.InputError(_(b"please specify just one revision"))
4790 4788
4791 4789 if not node:
4792 4790 node = rev
4793 4791
4794 4792 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4795 4793 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4796 4794 if node:
4797 4795 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4798 4796 ctx = logcmdutil.revsingle(repo, node)
4799 4797 mf = ctx.manifest()
4800 4798 ui.pager(b'manifest')
4801 4799 for f in ctx:
4802 4800 fm.startitem()
4803 4801 fm.context(ctx=ctx)
4804 4802 fl = ctx[f].flags()
4805 4803 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4806 4804 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4807 4805 fm.write(b'path', b'%s\n', f)
4808 4806 fm.end()
4809 4807
4810 4808
4811 4809 @command(
4812 4810 b'merge',
4813 4811 [
4814 4812 (
4815 4813 b'f',
4816 4814 b'force',
4817 4815 None,
4818 4816 _(b'force a merge including outstanding changes (DEPRECATED)'),
4819 4817 ),
4820 4818 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4821 4819 (
4822 4820 b'P',
4823 4821 b'preview',
4824 4822 None,
4825 4823 _(b'review revisions to merge (no merge is performed)'),
4826 4824 ),
4827 4825 (b'', b'abort', None, _(b'abort the ongoing merge')),
4828 4826 ]
4829 4827 + mergetoolopts,
4830 4828 _(b'[-P] [[-r] REV]'),
4831 4829 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4832 4830 helpbasic=True,
4833 4831 )
4834 4832 def merge(ui, repo, node=None, **opts):
4835 4833 """merge another revision into working directory
4836 4834
4837 4835 The current working directory is updated with all changes made in
4838 4836 the requested revision since the last common predecessor revision.
4839 4837
4840 4838 Files that changed between either parent are marked as changed for
4841 4839 the next commit and a commit must be performed before any further
4842 4840 updates to the repository are allowed. The next commit will have
4843 4841 two parents.
4844 4842
4845 4843 ``--tool`` can be used to specify the merge tool used for file
4846 4844 merges. It overrides the HGMERGE environment variable and your
4847 4845 configuration files. See :hg:`help merge-tools` for options.
4848 4846
4849 4847 If no revision is specified, the working directory's parent is a
4850 4848 head revision, and the current branch contains exactly one other
4851 4849 head, the other head is merged with by default. Otherwise, an
4852 4850 explicit revision with which to merge must be provided.
4853 4851
4854 4852 See :hg:`help resolve` for information on handling file conflicts.
4855 4853
4856 4854 To undo an uncommitted merge, use :hg:`merge --abort` which
4857 4855 will check out a clean copy of the original merge parent, losing
4858 4856 all changes.
4859 4857
4860 4858 Returns 0 on success, 1 if there are unresolved files.
4861 4859 """
4862 4860
4863 4861 opts = pycompat.byteskwargs(opts)
4864 4862 abort = opts.get(b'abort')
4865 4863 if abort and repo.dirstate.p2() == repo.nullid:
4866 4864 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4867 4865 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4868 4866 if abort:
4869 4867 state = cmdutil.getunfinishedstate(repo)
4870 4868 if state and state._opname != b'merge':
4871 4869 raise error.StateError(
4872 4870 _(b'cannot abort merge with %s in progress') % (state._opname),
4873 4871 hint=state.hint(),
4874 4872 )
4875 4873 if node:
4876 4874 raise error.InputError(_(b"cannot specify a node with --abort"))
4877 4875 return hg.abortmerge(repo.ui, repo)
4878 4876
4879 4877 if opts.get(b'rev') and node:
4880 4878 raise error.InputError(_(b"please specify just one revision"))
4881 4879 if not node:
4882 4880 node = opts.get(b'rev')
4883 4881
4884 4882 if node:
4885 4883 ctx = logcmdutil.revsingle(repo, node)
4886 4884 else:
4887 4885 if ui.configbool(b'commands', b'merge.require-rev'):
4888 4886 raise error.InputError(
4889 4887 _(
4890 4888 b'configuration requires specifying revision to merge '
4891 4889 b'with'
4892 4890 )
4893 4891 )
4894 4892 ctx = repo[destutil.destmerge(repo)]
4895 4893
4896 4894 if ctx.node() is None:
4897 4895 raise error.InputError(
4898 4896 _(b'merging with the working copy has no effect')
4899 4897 )
4900 4898
4901 4899 if opts.get(b'preview'):
4902 4900 # find nodes that are ancestors of p2 but not of p1
4903 4901 p1 = repo[b'.'].node()
4904 4902 p2 = ctx.node()
4905 4903 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4906 4904
4907 4905 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4908 4906 for node in nodes:
4909 4907 displayer.show(repo[node])
4910 4908 displayer.close()
4911 4909 return 0
4912 4910
4913 4911 # ui.forcemerge is an internal variable, do not document
4914 4912 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4915 4913 with ui.configoverride(overrides, b'merge'):
4916 4914 force = opts.get(b'force')
4917 4915 labels = [b'working copy', b'merge rev', b'common ancestor']
4918 4916 return hg.merge(ctx, force=force, labels=labels)
4919 4917
4920 4918
4921 4919 statemod.addunfinished(
4922 4920 b'merge',
4923 4921 fname=None,
4924 4922 clearable=True,
4925 4923 allowcommit=True,
4926 4924 cmdmsg=_(b'outstanding uncommitted merge'),
4927 4925 abortfunc=hg.abortmerge,
4928 4926 statushint=_(
4929 4927 b'To continue: hg commit\nTo abort: hg merge --abort'
4930 4928 ),
4931 4929 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4932 4930 )
4933 4931
4934 4932
4935 4933 @command(
4936 4934 b'outgoing|out',
4937 4935 [
4938 4936 (
4939 4937 b'f',
4940 4938 b'force',
4941 4939 None,
4942 4940 _(b'run even when the destination is unrelated'),
4943 4941 ),
4944 4942 (
4945 4943 b'r',
4946 4944 b'rev',
4947 4945 [],
4948 4946 _(b'a changeset intended to be included in the destination'),
4949 4947 _(b'REV'),
4950 4948 ),
4951 4949 (b'n', b'newest-first', None, _(b'show newest record first')),
4952 4950 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4953 4951 (
4954 4952 b'b',
4955 4953 b'branch',
4956 4954 [],
4957 4955 _(b'a specific branch you would like to push'),
4958 4956 _(b'BRANCH'),
4959 4957 ),
4960 4958 ]
4961 4959 + logopts
4962 4960 + remoteopts
4963 4961 + subrepoopts,
4964 4962 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
4965 4963 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4966 4964 )
4967 4965 def outgoing(ui, repo, *dests, **opts):
4968 4966 """show changesets not found in the destination
4969 4967
4970 4968 Show changesets not found in the specified destination repository
4971 4969 or the default push location. These are the changesets that would
4972 4970 be pushed if a push was requested.
4973 4971
4974 4972 See pull for details of valid destination formats.
4975 4973
4976 4974 .. container:: verbose
4977 4975
4978 4976 With -B/--bookmarks, the result of bookmark comparison between
4979 4977 local and remote repositories is displayed. With -v/--verbose,
4980 4978 status is also displayed for each bookmark like below::
4981 4979
4982 4980 BM1 01234567890a added
4983 4981 BM2 deleted
4984 4982 BM3 234567890abc advanced
4985 4983 BM4 34567890abcd diverged
4986 4984 BM5 4567890abcde changed
4987 4985
4988 4986 The action taken when pushing depends on the
4989 4987 status of each bookmark:
4990 4988
4991 4989 :``added``: push with ``-B`` will create it
4992 4990 :``deleted``: push with ``-B`` will delete it
4993 4991 :``advanced``: push will update it
4994 4992 :``diverged``: push with ``-B`` will update it
4995 4993 :``changed``: push with ``-B`` will update it
4996 4994
4997 4995 From the point of view of pushing behavior, bookmarks
4998 4996 existing only in the remote repository are treated as
4999 4997 ``deleted``, even if it is in fact added remotely.
5000 4998
5001 4999 Returns 0 if there are outgoing changes, 1 otherwise.
5002 5000 """
5003 5001 opts = pycompat.byteskwargs(opts)
5004 5002 if opts.get(b'bookmarks'):
5005 5003 for path in urlutil.get_push_paths(repo, ui, dests):
5006 5004 dest = path.pushloc or path.loc
5007 5005 other = hg.peer(repo, opts, dest)
5008 5006 try:
5009 5007 if b'bookmarks' not in other.listkeys(b'namespaces'):
5010 5008 ui.warn(_(b"remote doesn't support bookmarks\n"))
5011 5009 return 0
5012 5010 ui.status(
5013 5011 _(b'comparing with %s\n') % urlutil.hidepassword(dest)
5014 5012 )
5015 5013 ui.pager(b'outgoing')
5016 5014 return bookmarks.outgoing(ui, repo, other)
5017 5015 finally:
5018 5016 other.close()
5019 5017
5020 5018 return hg.outgoing(ui, repo, dests, opts)
5021 5019
5022 5020
5023 5021 @command(
5024 5022 b'parents',
5025 5023 [
5026 5024 (
5027 5025 b'r',
5028 5026 b'rev',
5029 5027 b'',
5030 5028 _(b'show parents of the specified revision'),
5031 5029 _(b'REV'),
5032 5030 ),
5033 5031 ]
5034 5032 + templateopts,
5035 5033 _(b'[-r REV] [FILE]'),
5036 5034 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5037 5035 inferrepo=True,
5038 5036 )
5039 5037 def parents(ui, repo, file_=None, **opts):
5040 5038 """show the parents of the working directory or revision (DEPRECATED)
5041 5039
5042 5040 Print the working directory's parent revisions. If a revision is
5043 5041 given via -r/--rev, the parent of that revision will be printed.
5044 5042 If a file argument is given, the revision in which the file was
5045 5043 last changed (before the working directory revision or the
5046 5044 argument to --rev if given) is printed.
5047 5045
5048 5046 This command is equivalent to::
5049 5047
5050 5048 hg log -r "p1()+p2()" or
5051 5049 hg log -r "p1(REV)+p2(REV)" or
5052 5050 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5053 5051 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5054 5052
5055 5053 See :hg:`summary` and :hg:`help revsets` for related information.
5056 5054
5057 5055 Returns 0 on success.
5058 5056 """
5059 5057
5060 5058 opts = pycompat.byteskwargs(opts)
5061 5059 rev = opts.get(b'rev')
5062 5060 if rev:
5063 5061 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5064 5062 ctx = logcmdutil.revsingle(repo, rev, None)
5065 5063
5066 5064 if file_:
5067 5065 m = scmutil.match(ctx, (file_,), opts)
5068 5066 if m.anypats() or len(m.files()) != 1:
5069 5067 raise error.InputError(_(b'can only specify an explicit filename'))
5070 5068 file_ = m.files()[0]
5071 5069 filenodes = []
5072 5070 for cp in ctx.parents():
5073 5071 if not cp:
5074 5072 continue
5075 5073 try:
5076 5074 filenodes.append(cp.filenode(file_))
5077 5075 except error.LookupError:
5078 5076 pass
5079 5077 if not filenodes:
5080 5078 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5081 5079 p = []
5082 5080 for fn in filenodes:
5083 5081 fctx = repo.filectx(file_, fileid=fn)
5084 5082 p.append(fctx.node())
5085 5083 else:
5086 5084 p = [cp.node() for cp in ctx.parents()]
5087 5085
5088 5086 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5089 5087 for n in p:
5090 5088 if n != repo.nullid:
5091 5089 displayer.show(repo[n])
5092 5090 displayer.close()
5093 5091
5094 5092
5095 5093 @command(
5096 5094 b'paths',
5097 5095 formatteropts,
5098 5096 _(b'[NAME]'),
5099 5097 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5100 5098 optionalrepo=True,
5101 5099 intents={INTENT_READONLY},
5102 5100 )
5103 5101 def paths(ui, repo, search=None, **opts):
5104 5102 """show aliases for remote repositories
5105 5103
5106 5104 Show definition of symbolic path name NAME. If no name is given,
5107 5105 show definition of all available names.
5108 5106
5109 5107 Option -q/--quiet suppresses all output when searching for NAME
5110 5108 and shows only the path names when listing all definitions.
5111 5109
5112 5110 Path names are defined in the [paths] section of your
5113 5111 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5114 5112 repository, ``.hg/hgrc`` is used, too.
5115 5113
5116 5114 The path names ``default`` and ``default-push`` have a special
5117 5115 meaning. When performing a push or pull operation, they are used
5118 5116 as fallbacks if no location is specified on the command-line.
5119 5117 When ``default-push`` is set, it will be used for push and
5120 5118 ``default`` will be used for pull; otherwise ``default`` is used
5121 5119 as the fallback for both. When cloning a repository, the clone
5122 5120 source is written as ``default`` in ``.hg/hgrc``.
5123 5121
5124 5122 .. note::
5125 5123
5126 5124 ``default`` and ``default-push`` apply to all inbound (e.g.
5127 5125 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5128 5126 and :hg:`bundle`) operations.
5129 5127
5130 5128 See :hg:`help urls` for more information.
5131 5129
5132 5130 .. container:: verbose
5133 5131
5134 5132 Template:
5135 5133
5136 5134 The following keywords are supported. See also :hg:`help templates`.
5137 5135
5138 5136 :name: String. Symbolic name of the path alias.
5139 5137 :pushurl: String. URL for push operations.
5140 5138 :url: String. URL or directory path for the other operations.
5141 5139
5142 5140 Returns 0 on success.
5143 5141 """
5144 5142
5145 5143 opts = pycompat.byteskwargs(opts)
5146 5144
5147 5145 pathitems = urlutil.list_paths(ui, search)
5148 5146 ui.pager(b'paths')
5149 5147
5150 5148 fm = ui.formatter(b'paths', opts)
5151 5149 if fm.isplain():
5152 5150 hidepassword = urlutil.hidepassword
5153 5151 else:
5154 5152 hidepassword = bytes
5155 5153 if ui.quiet:
5156 5154 namefmt = b'%s\n'
5157 5155 else:
5158 5156 namefmt = b'%s = '
5159 5157 showsubopts = not search and not ui.quiet
5160 5158
5161 5159 for name, path in pathitems:
5162 5160 fm.startitem()
5163 5161 fm.condwrite(not search, b'name', namefmt, name)
5164 5162 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5165 5163 for subopt, value in sorted(path.suboptions.items()):
5166 5164 assert subopt not in (b'name', b'url')
5167 5165 if showsubopts:
5168 5166 fm.plain(b'%s:%s = ' % (name, subopt))
5169 5167 if isinstance(value, bool):
5170 5168 if value:
5171 5169 value = b'yes'
5172 5170 else:
5173 5171 value = b'no'
5174 5172 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5175 5173
5176 5174 fm.end()
5177 5175
5178 5176 if search and not pathitems:
5179 5177 if not ui.quiet:
5180 5178 ui.warn(_(b"not found!\n"))
5181 5179 return 1
5182 5180 else:
5183 5181 return 0
5184 5182
5185 5183
5186 5184 @command(
5187 5185 b'phase',
5188 5186 [
5189 5187 (b'p', b'public', False, _(b'set changeset phase to public')),
5190 5188 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5191 5189 (b's', b'secret', False, _(b'set changeset phase to secret')),
5192 5190 (b'f', b'force', False, _(b'allow to move boundary backward')),
5193 5191 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5194 5192 ],
5195 5193 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5196 5194 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5197 5195 )
5198 5196 def phase(ui, repo, *revs, **opts):
5199 5197 """set or show the current phase name
5200 5198
5201 5199 With no argument, show the phase name of the current revision(s).
5202 5200
5203 5201 With one of -p/--public, -d/--draft or -s/--secret, change the
5204 5202 phase value of the specified revisions.
5205 5203
5206 5204 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5207 5205 lower phase to a higher phase. Phases are ordered as follows::
5208 5206
5209 5207 public < draft < secret
5210 5208
5211 5209 Returns 0 on success, 1 if some phases could not be changed.
5212 5210
5213 5211 (For more information about the phases concept, see :hg:`help phases`.)
5214 5212 """
5215 5213 opts = pycompat.byteskwargs(opts)
5216 5214 # search for a unique phase argument
5217 5215 targetphase = None
5218 5216 for idx, name in enumerate(phases.cmdphasenames):
5219 5217 if opts[name]:
5220 5218 if targetphase is not None:
5221 5219 raise error.InputError(_(b'only one phase can be specified'))
5222 5220 targetphase = idx
5223 5221
5224 5222 # look for specified revision
5225 5223 revs = list(revs)
5226 5224 revs.extend(opts[b'rev'])
5227 5225 if revs:
5228 5226 revs = logcmdutil.revrange(repo, revs)
5229 5227 else:
5230 5228 # display both parents as the second parent phase can influence
5231 5229 # the phase of a merge commit
5232 5230 revs = [c.rev() for c in repo[None].parents()]
5233 5231
5234 5232 ret = 0
5235 5233 if targetphase is None:
5236 5234 # display
5237 5235 for r in revs:
5238 5236 ctx = repo[r]
5239 5237 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5240 5238 else:
5241 5239 with repo.lock(), repo.transaction(b"phase") as tr:
5242 5240 # set phase
5243 5241 if not revs:
5244 5242 raise error.InputError(_(b'empty revision set'))
5245 5243 nodes = [repo[r].node() for r in revs]
5246 5244 # moving revision from public to draft may hide them
5247 5245 # We have to check result on an unfiltered repository
5248 5246 unfi = repo.unfiltered()
5249 5247 getphase = unfi._phasecache.phase
5250 5248 olddata = [getphase(unfi, r) for r in unfi]
5251 5249 phases.advanceboundary(repo, tr, targetphase, nodes)
5252 5250 if opts[b'force']:
5253 5251 phases.retractboundary(repo, tr, targetphase, nodes)
5254 5252 getphase = unfi._phasecache.phase
5255 5253 newdata = [getphase(unfi, r) for r in unfi]
5256 5254 changes = sum(newdata[r] != olddata[r] for r in unfi)
5257 5255 cl = unfi.changelog
5258 5256 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5259 5257 if rejected:
5260 5258 ui.warn(
5261 5259 _(
5262 5260 b'cannot move %i changesets to a higher '
5263 5261 b'phase, use --force\n'
5264 5262 )
5265 5263 % len(rejected)
5266 5264 )
5267 5265 ret = 1
5268 5266 if changes:
5269 5267 msg = _(b'phase changed for %i changesets\n') % changes
5270 5268 if ret:
5271 5269 ui.status(msg)
5272 5270 else:
5273 5271 ui.note(msg)
5274 5272 else:
5275 5273 ui.warn(_(b'no phases changed\n'))
5276 5274 return ret
5277 5275
5278 5276
5279 5277 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5280 5278 """Run after a changegroup has been added via pull/unbundle
5281 5279
5282 5280 This takes arguments below:
5283 5281
5284 5282 :modheads: change of heads by pull/unbundle
5285 5283 :optupdate: updating working directory is needed or not
5286 5284 :checkout: update destination revision (or None to default destination)
5287 5285 :brev: a name, which might be a bookmark to be activated after updating
5288 5286
5289 5287 return True if update raise any conflict, False otherwise.
5290 5288 """
5291 5289 if modheads == 0:
5292 5290 return False
5293 5291 if optupdate:
5294 5292 try:
5295 5293 return hg.updatetotally(ui, repo, checkout, brev)
5296 5294 except error.UpdateAbort as inst:
5297 5295 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5298 5296 hint = inst.hint
5299 5297 raise error.UpdateAbort(msg, hint=hint)
5300 5298 if modheads is not None and modheads > 1:
5301 5299 currentbranchheads = len(repo.branchheads())
5302 5300 if currentbranchheads == modheads:
5303 5301 ui.status(
5304 5302 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5305 5303 )
5306 5304 elif currentbranchheads > 1:
5307 5305 ui.status(
5308 5306 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5309 5307 )
5310 5308 else:
5311 5309 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5312 5310 elif not ui.configbool(b'commands', b'update.requiredest'):
5313 5311 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5314 5312 return False
5315 5313
5316 5314
5317 5315 @command(
5318 5316 b'pull',
5319 5317 [
5320 5318 (
5321 5319 b'u',
5322 5320 b'update',
5323 5321 None,
5324 5322 _(b'update to new branch head if new descendants were pulled'),
5325 5323 ),
5326 5324 (
5327 5325 b'f',
5328 5326 b'force',
5329 5327 None,
5330 5328 _(b'run even when remote repository is unrelated'),
5331 5329 ),
5332 5330 (
5333 5331 b'',
5334 5332 b'confirm',
5335 5333 None,
5336 5334 _(b'confirm pull before applying changes'),
5337 5335 ),
5338 5336 (
5339 5337 b'r',
5340 5338 b'rev',
5341 5339 [],
5342 5340 _(b'a remote changeset intended to be added'),
5343 5341 _(b'REV'),
5344 5342 ),
5345 5343 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5346 5344 (
5347 5345 b'b',
5348 5346 b'branch',
5349 5347 [],
5350 5348 _(b'a specific branch you would like to pull'),
5351 5349 _(b'BRANCH'),
5352 5350 ),
5353 5351 ]
5354 5352 + remoteopts,
5355 5353 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5356 5354 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5357 5355 helpbasic=True,
5358 5356 )
5359 5357 def pull(ui, repo, *sources, **opts):
5360 5358 """pull changes from the specified source
5361 5359
5362 5360 Pull changes from a remote repository to a local one.
5363 5361
5364 5362 This finds all changes from the repository at the specified path
5365 5363 or URL and adds them to a local repository (the current one unless
5366 5364 -R is specified). By default, this does not update the copy of the
5367 5365 project in the working directory.
5368 5366
5369 5367 When cloning from servers that support it, Mercurial may fetch
5370 5368 pre-generated data. When this is done, hooks operating on incoming
5371 5369 changesets and changegroups may fire more than once, once for each
5372 5370 pre-generated bundle and as well as for any additional remaining
5373 5371 data. See :hg:`help -e clonebundles` for more.
5374 5372
5375 5373 Use :hg:`incoming` if you want to see what would have been added
5376 5374 by a pull at the time you issued this command. If you then decide
5377 5375 to add those changes to the repository, you should use :hg:`pull
5378 5376 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5379 5377
5380 5378 If SOURCE is omitted, the 'default' path will be used.
5381 5379 See :hg:`help urls` for more information.
5382 5380
5383 5381 If multiple sources are specified, they will be pulled sequentially as if
5384 5382 the command was run multiple time. If --update is specify and the command
5385 5383 will stop at the first failed --update.
5386 5384
5387 5385 Specifying bookmark as ``.`` is equivalent to specifying the active
5388 5386 bookmark's name.
5389 5387
5390 5388 Returns 0 on success, 1 if an update had unresolved files.
5391 5389 """
5392 5390
5393 5391 opts = pycompat.byteskwargs(opts)
5394 5392 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5395 5393 b'update'
5396 5394 ):
5397 5395 msg = _(b'update destination required by configuration')
5398 5396 hint = _(b'use hg pull followed by hg update DEST')
5399 5397 raise error.InputError(msg, hint=hint)
5400 5398
5401 5399 for path in urlutil.get_pull_paths(repo, ui, sources):
5402 5400 source, branches = urlutil.parseurl(path.rawloc, opts.get(b'branch'))
5403 5401 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source))
5404 5402 ui.flush()
5405 5403 other = hg.peer(repo, opts, source)
5406 5404 update_conflict = None
5407 5405 try:
5408 5406 revs, checkout = hg.addbranchrevs(
5409 5407 repo, other, branches, opts.get(b'rev')
5410 5408 )
5411 5409
5412 5410 pullopargs = {}
5413 5411
5414 5412 nodes = None
5415 5413 if opts.get(b'bookmark') or revs:
5416 5414 # The list of bookmark used here is the same used to actually update
5417 5415 # the bookmark names, to avoid the race from issue 4689 and we do
5418 5416 # all lookup and bookmark queries in one go so they see the same
5419 5417 # version of the server state (issue 4700).
5420 5418 nodes = []
5421 5419 fnodes = []
5422 5420 revs = revs or []
5423 5421 if revs and not other.capable(b'lookup'):
5424 5422 err = _(
5425 5423 b"other repository doesn't support revision lookup, "
5426 5424 b"so a rev cannot be specified."
5427 5425 )
5428 5426 raise error.Abort(err)
5429 5427 with other.commandexecutor() as e:
5430 5428 fremotebookmarks = e.callcommand(
5431 5429 b'listkeys', {b'namespace': b'bookmarks'}
5432 5430 )
5433 5431 for r in revs:
5434 5432 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5435 5433 remotebookmarks = fremotebookmarks.result()
5436 5434 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5437 5435 pullopargs[b'remotebookmarks'] = remotebookmarks
5438 5436 for b in opts.get(b'bookmark', []):
5439 5437 b = repo._bookmarks.expandname(b)
5440 5438 if b not in remotebookmarks:
5441 5439 raise error.InputError(
5442 5440 _(b'remote bookmark %s not found!') % b
5443 5441 )
5444 5442 nodes.append(remotebookmarks[b])
5445 5443 for i, rev in enumerate(revs):
5446 5444 node = fnodes[i].result()
5447 5445 nodes.append(node)
5448 5446 if rev == checkout:
5449 5447 checkout = node
5450 5448
5451 5449 wlock = util.nullcontextmanager()
5452 5450 if opts.get(b'update'):
5453 5451 wlock = repo.wlock()
5454 5452 with wlock:
5455 5453 pullopargs.update(opts.get(b'opargs', {}))
5456 5454 modheads = exchange.pull(
5457 5455 repo,
5458 5456 other,
5459 5457 path=path,
5460 5458 heads=nodes,
5461 5459 force=opts.get(b'force'),
5462 5460 bookmarks=opts.get(b'bookmark', ()),
5463 5461 opargs=pullopargs,
5464 5462 confirm=opts.get(b'confirm'),
5465 5463 ).cgresult
5466 5464
5467 5465 # brev is a name, which might be a bookmark to be activated at
5468 5466 # the end of the update. In other words, it is an explicit
5469 5467 # destination of the update
5470 5468 brev = None
5471 5469
5472 5470 if checkout:
5473 5471 checkout = repo.unfiltered().changelog.rev(checkout)
5474 5472
5475 5473 # order below depends on implementation of
5476 5474 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5477 5475 # because 'checkout' is determined without it.
5478 5476 if opts.get(b'rev'):
5479 5477 brev = opts[b'rev'][0]
5480 5478 elif opts.get(b'branch'):
5481 5479 brev = opts[b'branch'][0]
5482 5480 else:
5483 5481 brev = branches[0]
5484 5482 repo._subtoppath = source
5485 5483 try:
5486 5484 update_conflict = postincoming(
5487 5485 ui, repo, modheads, opts.get(b'update'), checkout, brev
5488 5486 )
5489 5487 except error.FilteredRepoLookupError as exc:
5490 5488 msg = _(b'cannot update to target: %s') % exc.args[0]
5491 5489 exc.args = (msg,) + exc.args[1:]
5492 5490 raise
5493 5491 finally:
5494 5492 del repo._subtoppath
5495 5493
5496 5494 finally:
5497 5495 other.close()
5498 5496 # skip the remaining pull source if they are some conflict.
5499 5497 if update_conflict:
5500 5498 break
5501 5499 if update_conflict:
5502 5500 return 1
5503 5501 else:
5504 5502 return 0
5505 5503
5506 5504
5507 5505 @command(
5508 5506 b'purge|clean',
5509 5507 [
5510 5508 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5511 5509 (b'', b'all', None, _(b'purge ignored files too')),
5512 5510 (b'i', b'ignored', None, _(b'purge only ignored files')),
5513 5511 (b'', b'dirs', None, _(b'purge empty directories')),
5514 5512 (b'', b'files', None, _(b'purge files')),
5515 5513 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5516 5514 (
5517 5515 b'0',
5518 5516 b'print0',
5519 5517 None,
5520 5518 _(
5521 5519 b'end filenames with NUL, for use with xargs'
5522 5520 b' (implies -p/--print)'
5523 5521 ),
5524 5522 ),
5525 5523 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5526 5524 ]
5527 5525 + cmdutil.walkopts,
5528 5526 _(b'hg purge [OPTION]... [DIR]...'),
5529 5527 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5530 5528 )
5531 5529 def purge(ui, repo, *dirs, **opts):
5532 5530 """removes files not tracked by Mercurial
5533 5531
5534 5532 Delete files not known to Mercurial. This is useful to test local
5535 5533 and uncommitted changes in an otherwise-clean source tree.
5536 5534
5537 5535 This means that purge will delete the following by default:
5538 5536
5539 5537 - Unknown files: files marked with "?" by :hg:`status`
5540 5538 - Empty directories: in fact Mercurial ignores directories unless
5541 5539 they contain files under source control management
5542 5540
5543 5541 But it will leave untouched:
5544 5542
5545 5543 - Modified and unmodified tracked files
5546 5544 - Ignored files (unless -i or --all is specified)
5547 5545 - New files added to the repository (with :hg:`add`)
5548 5546
5549 5547 The --files and --dirs options can be used to direct purge to delete
5550 5548 only files, only directories, or both. If neither option is given,
5551 5549 both will be deleted.
5552 5550
5553 5551 If directories are given on the command line, only files in these
5554 5552 directories are considered.
5555 5553
5556 5554 Be careful with purge, as you could irreversibly delete some files
5557 5555 you forgot to add to the repository. If you only want to print the
5558 5556 list of files that this program would delete, use the --print
5559 5557 option.
5560 5558 """
5561 5559 opts = pycompat.byteskwargs(opts)
5562 5560 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5563 5561
5564 5562 act = not opts.get(b'print')
5565 5563 eol = b'\n'
5566 5564 if opts.get(b'print0'):
5567 5565 eol = b'\0'
5568 5566 act = False # --print0 implies --print
5569 5567 if opts.get(b'all', False):
5570 5568 ignored = True
5571 5569 unknown = True
5572 5570 else:
5573 5571 ignored = opts.get(b'ignored', False)
5574 5572 unknown = not ignored
5575 5573
5576 5574 removefiles = opts.get(b'files')
5577 5575 removedirs = opts.get(b'dirs')
5578 5576 confirm = opts.get(b'confirm')
5579 5577 if confirm is None:
5580 5578 try:
5581 5579 extensions.find(b'purge')
5582 5580 confirm = False
5583 5581 except KeyError:
5584 5582 confirm = True
5585 5583
5586 5584 if not removefiles and not removedirs:
5587 5585 removefiles = True
5588 5586 removedirs = True
5589 5587
5590 5588 match = scmutil.match(repo[None], dirs, opts)
5591 5589
5592 5590 paths = mergemod.purge(
5593 5591 repo,
5594 5592 match,
5595 5593 unknown=unknown,
5596 5594 ignored=ignored,
5597 5595 removeemptydirs=removedirs,
5598 5596 removefiles=removefiles,
5599 5597 abortonerror=opts.get(b'abort_on_err'),
5600 5598 noop=not act,
5601 5599 confirm=confirm,
5602 5600 )
5603 5601
5604 5602 for path in paths:
5605 5603 if not act:
5606 5604 ui.write(b'%s%s' % (path, eol))
5607 5605
5608 5606
5609 5607 @command(
5610 5608 b'push',
5611 5609 [
5612 5610 (b'f', b'force', None, _(b'force push')),
5613 5611 (
5614 5612 b'r',
5615 5613 b'rev',
5616 5614 [],
5617 5615 _(b'a changeset intended to be included in the destination'),
5618 5616 _(b'REV'),
5619 5617 ),
5620 5618 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5621 5619 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5622 5620 (
5623 5621 b'b',
5624 5622 b'branch',
5625 5623 [],
5626 5624 _(b'a specific branch you would like to push'),
5627 5625 _(b'BRANCH'),
5628 5626 ),
5629 5627 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5630 5628 (
5631 5629 b'',
5632 5630 b'pushvars',
5633 5631 [],
5634 5632 _(b'variables that can be sent to server (ADVANCED)'),
5635 5633 ),
5636 5634 (
5637 5635 b'',
5638 5636 b'publish',
5639 5637 False,
5640 5638 _(b'push the changeset as public (EXPERIMENTAL)'),
5641 5639 ),
5642 5640 ]
5643 5641 + remoteopts,
5644 5642 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5645 5643 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5646 5644 helpbasic=True,
5647 5645 )
5648 5646 def push(ui, repo, *dests, **opts):
5649 5647 """push changes to the specified destination
5650 5648
5651 5649 Push changesets from the local repository to the specified
5652 5650 destination.
5653 5651
5654 5652 This operation is symmetrical to pull: it is identical to a pull
5655 5653 in the destination repository from the current one.
5656 5654
5657 5655 By default, push will not allow creation of new heads at the
5658 5656 destination, since multiple heads would make it unclear which head
5659 5657 to use. In this situation, it is recommended to pull and merge
5660 5658 before pushing.
5661 5659
5662 5660 Use --new-branch if you want to allow push to create a new named
5663 5661 branch that is not present at the destination. This allows you to
5664 5662 only create a new branch without forcing other changes.
5665 5663
5666 5664 .. note::
5667 5665
5668 5666 Extra care should be taken with the -f/--force option,
5669 5667 which will push all new heads on all branches, an action which will
5670 5668 almost always cause confusion for collaborators.
5671 5669
5672 5670 If -r/--rev is used, the specified revision and all its ancestors
5673 5671 will be pushed to the remote repository.
5674 5672
5675 5673 If -B/--bookmark is used, the specified bookmarked revision, its
5676 5674 ancestors, and the bookmark will be pushed to the remote
5677 5675 repository. Specifying ``.`` is equivalent to specifying the active
5678 5676 bookmark's name. Use the --all-bookmarks option for pushing all
5679 5677 current bookmarks.
5680 5678
5681 5679 Please see :hg:`help urls` for important details about ``ssh://``
5682 5680 URLs. If DESTINATION is omitted, a default path will be used.
5683 5681
5684 5682 When passed multiple destinations, push will process them one after the
5685 5683 other, but stop should an error occur.
5686 5684
5687 5685 .. container:: verbose
5688 5686
5689 5687 The --pushvars option sends strings to the server that become
5690 5688 environment variables prepended with ``HG_USERVAR_``. For example,
5691 5689 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5692 5690 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5693 5691
5694 5692 pushvars can provide for user-overridable hooks as well as set debug
5695 5693 levels. One example is having a hook that blocks commits containing
5696 5694 conflict markers, but enables the user to override the hook if the file
5697 5695 is using conflict markers for testing purposes or the file format has
5698 5696 strings that look like conflict markers.
5699 5697
5700 5698 By default, servers will ignore `--pushvars`. To enable it add the
5701 5699 following to your configuration file::
5702 5700
5703 5701 [push]
5704 5702 pushvars.server = true
5705 5703
5706 5704 Returns 0 if push was successful, 1 if nothing to push.
5707 5705 """
5708 5706
5709 5707 opts = pycompat.byteskwargs(opts)
5710 5708
5711 5709 if opts.get(b'all_bookmarks'):
5712 5710 cmdutil.check_incompatible_arguments(
5713 5711 opts,
5714 5712 b'all_bookmarks',
5715 5713 [b'bookmark', b'rev'],
5716 5714 )
5717 5715 opts[b'bookmark'] = list(repo._bookmarks)
5718 5716
5719 5717 if opts.get(b'bookmark'):
5720 5718 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5721 5719 for b in opts[b'bookmark']:
5722 5720 # translate -B options to -r so changesets get pushed
5723 5721 b = repo._bookmarks.expandname(b)
5724 5722 if b in repo._bookmarks:
5725 5723 opts.setdefault(b'rev', []).append(b)
5726 5724 else:
5727 5725 # if we try to push a deleted bookmark, translate it to null
5728 5726 # this lets simultaneous -r, -b options continue working
5729 5727 opts.setdefault(b'rev', []).append(b"null")
5730 5728
5731 5729 some_pushed = False
5732 5730 result = 0
5733 5731 for path in urlutil.get_push_paths(repo, ui, dests):
5734 5732 dest = path.pushloc or path.loc
5735 5733 branches = (path.branch, opts.get(b'branch') or [])
5736 5734 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5737 5735 revs, checkout = hg.addbranchrevs(
5738 5736 repo, repo, branches, opts.get(b'rev')
5739 5737 )
5740 5738 other = hg.peer(repo, opts, dest)
5741 5739
5742 5740 try:
5743 5741 if revs:
5744 5742 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5745 5743 if not revs:
5746 5744 raise error.InputError(
5747 5745 _(b"specified revisions evaluate to an empty set"),
5748 5746 hint=_(b"use different revision arguments"),
5749 5747 )
5750 5748 elif path.pushrev:
5751 5749 # It doesn't make any sense to specify ancestor revisions. So limit
5752 5750 # to DAG heads to make discovery simpler.
5753 5751 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5754 5752 revs = scmutil.revrange(repo, [expr])
5755 5753 revs = [repo[rev].node() for rev in revs]
5756 5754 if not revs:
5757 5755 raise error.InputError(
5758 5756 _(
5759 5757 b'default push revset for path evaluates to an empty set'
5760 5758 )
5761 5759 )
5762 5760 elif ui.configbool(b'commands', b'push.require-revs'):
5763 5761 raise error.InputError(
5764 5762 _(b'no revisions specified to push'),
5765 5763 hint=_(b'did you mean "hg push -r ."?'),
5766 5764 )
5767 5765
5768 5766 repo._subtoppath = dest
5769 5767 try:
5770 5768 # push subrepos depth-first for coherent ordering
5771 5769 c = repo[b'.']
5772 5770 subs = c.substate # only repos that are committed
5773 5771 for s in sorted(subs):
5774 5772 sub_result = c.sub(s).push(opts)
5775 5773 if sub_result == 0:
5776 5774 return 1
5777 5775 finally:
5778 5776 del repo._subtoppath
5779 5777
5780 5778 opargs = dict(
5781 5779 opts.get(b'opargs', {})
5782 5780 ) # copy opargs since we may mutate it
5783 5781 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5784 5782
5785 5783 pushop = exchange.push(
5786 5784 repo,
5787 5785 other,
5788 5786 opts.get(b'force'),
5789 5787 revs=revs,
5790 5788 newbranch=opts.get(b'new_branch'),
5791 5789 bookmarks=opts.get(b'bookmark', ()),
5792 5790 publish=opts.get(b'publish'),
5793 5791 opargs=opargs,
5794 5792 )
5795 5793
5796 5794 if pushop.cgresult == 0:
5797 5795 result = 1
5798 5796 elif pushop.cgresult is not None:
5799 5797 some_pushed = True
5800 5798
5801 5799 if pushop.bkresult is not None:
5802 5800 if pushop.bkresult == 2:
5803 5801 result = 2
5804 5802 elif not result and pushop.bkresult:
5805 5803 result = 2
5806 5804
5807 5805 if result:
5808 5806 break
5809 5807
5810 5808 finally:
5811 5809 other.close()
5812 5810 if result == 0 and not some_pushed:
5813 5811 result = 1
5814 5812 return result
5815 5813
5816 5814
5817 5815 @command(
5818 5816 b'recover',
5819 5817 [
5820 5818 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5821 5819 ],
5822 5820 helpcategory=command.CATEGORY_MAINTENANCE,
5823 5821 )
5824 5822 def recover(ui, repo, **opts):
5825 5823 """roll back an interrupted transaction
5826 5824
5827 5825 Recover from an interrupted commit or pull.
5828 5826
5829 5827 This command tries to fix the repository status after an
5830 5828 interrupted operation. It should only be necessary when Mercurial
5831 5829 suggests it.
5832 5830
5833 5831 Returns 0 if successful, 1 if nothing to recover or verify fails.
5834 5832 """
5835 5833 ret = repo.recover()
5836 5834 if ret:
5837 5835 if opts['verify']:
5838 5836 return hg.verify(repo)
5839 5837 else:
5840 5838 msg = _(
5841 5839 b"(verify step skipped, run `hg verify` to check your "
5842 5840 b"repository content)\n"
5843 5841 )
5844 5842 ui.warn(msg)
5845 5843 return 0
5846 5844 return 1
5847 5845
5848 5846
5849 5847 @command(
5850 5848 b'remove|rm',
5851 5849 [
5852 5850 (b'A', b'after', None, _(b'record delete for missing files')),
5853 5851 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5854 5852 ]
5855 5853 + subrepoopts
5856 5854 + walkopts
5857 5855 + dryrunopts,
5858 5856 _(b'[OPTION]... FILE...'),
5859 5857 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5860 5858 helpbasic=True,
5861 5859 inferrepo=True,
5862 5860 )
5863 5861 def remove(ui, repo, *pats, **opts):
5864 5862 """remove the specified files on the next commit
5865 5863
5866 5864 Schedule the indicated files for removal from the current branch.
5867 5865
5868 5866 This command schedules the files to be removed at the next commit.
5869 5867 To undo a remove before that, see :hg:`revert`. To undo added
5870 5868 files, see :hg:`forget`.
5871 5869
5872 5870 .. container:: verbose
5873 5871
5874 5872 -A/--after can be used to remove only files that have already
5875 5873 been deleted, -f/--force can be used to force deletion, and -Af
5876 5874 can be used to remove files from the next revision without
5877 5875 deleting them from the working directory.
5878 5876
5879 5877 The following table details the behavior of remove for different
5880 5878 file states (columns) and option combinations (rows). The file
5881 5879 states are Added [A], Clean [C], Modified [M] and Missing [!]
5882 5880 (as reported by :hg:`status`). The actions are Warn, Remove
5883 5881 (from branch) and Delete (from disk):
5884 5882
5885 5883 ========= == == == ==
5886 5884 opt/state A C M !
5887 5885 ========= == == == ==
5888 5886 none W RD W R
5889 5887 -f R RD RD R
5890 5888 -A W W W R
5891 5889 -Af R R R R
5892 5890 ========= == == == ==
5893 5891
5894 5892 .. note::
5895 5893
5896 5894 :hg:`remove` never deletes files in Added [A] state from the
5897 5895 working directory, not even if ``--force`` is specified.
5898 5896
5899 5897 Returns 0 on success, 1 if any warnings encountered.
5900 5898 """
5901 5899
5902 5900 opts = pycompat.byteskwargs(opts)
5903 5901 after, force = opts.get(b'after'), opts.get(b'force')
5904 5902 dryrun = opts.get(b'dry_run')
5905 5903 if not pats and not after:
5906 5904 raise error.InputError(_(b'no files specified'))
5907 5905
5908 5906 m = scmutil.match(repo[None], pats, opts)
5909 5907 subrepos = opts.get(b'subrepos')
5910 5908 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5911 5909 return cmdutil.remove(
5912 5910 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5913 5911 )
5914 5912
5915 5913
5916 5914 @command(
5917 5915 b'rename|move|mv',
5918 5916 [
5919 5917 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5920 5918 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5921 5919 (
5922 5920 b'',
5923 5921 b'at-rev',
5924 5922 b'',
5925 5923 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5926 5924 _(b'REV'),
5927 5925 ),
5928 5926 (
5929 5927 b'f',
5930 5928 b'force',
5931 5929 None,
5932 5930 _(b'forcibly move over an existing managed file'),
5933 5931 ),
5934 5932 ]
5935 5933 + walkopts
5936 5934 + dryrunopts,
5937 5935 _(b'[OPTION]... SOURCE... DEST'),
5938 5936 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5939 5937 )
5940 5938 def rename(ui, repo, *pats, **opts):
5941 5939 """rename files; equivalent of copy + remove
5942 5940
5943 5941 Mark dest as copies of sources; mark sources for deletion. If dest
5944 5942 is a directory, copies are put in that directory. If dest is a
5945 5943 file, there can only be one source.
5946 5944
5947 5945 By default, this command copies the contents of files as they
5948 5946 exist in the working directory. If invoked with -A/--after, the
5949 5947 operation is recorded, but no copying is performed.
5950 5948
5951 5949 To undo marking a destination file as renamed, use --forget. With that
5952 5950 option, all given (positional) arguments are unmarked as renames. The
5953 5951 destination file(s) will be left in place (still tracked). The source
5954 5952 file(s) will not be restored. Note that :hg:`rename --forget` behaves
5955 5953 the same way as :hg:`copy --forget`.
5956 5954
5957 5955 This command takes effect with the next commit by default.
5958 5956
5959 5957 Returns 0 on success, 1 if errors are encountered.
5960 5958 """
5961 5959 opts = pycompat.byteskwargs(opts)
5962 5960 with repo.wlock():
5963 5961 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5964 5962
5965 5963
5966 5964 @command(
5967 5965 b'resolve',
5968 5966 [
5969 5967 (b'a', b'all', None, _(b'select all unresolved files')),
5970 5968 (b'l', b'list', None, _(b'list state of files needing merge')),
5971 5969 (b'm', b'mark', None, _(b'mark files as resolved')),
5972 5970 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5973 5971 (b'n', b'no-status', None, _(b'hide status prefix')),
5974 5972 (b'', b're-merge', None, _(b're-merge files')),
5975 5973 ]
5976 5974 + mergetoolopts
5977 5975 + walkopts
5978 5976 + formatteropts,
5979 5977 _(b'[OPTION]... [FILE]...'),
5980 5978 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5981 5979 inferrepo=True,
5982 5980 )
5983 5981 def resolve(ui, repo, *pats, **opts):
5984 5982 """redo merges or set/view the merge status of files
5985 5983
5986 5984 Merges with unresolved conflicts are often the result of
5987 5985 non-interactive merging using the ``internal:merge`` configuration
5988 5986 setting, or a command-line merge tool like ``diff3``. The resolve
5989 5987 command is used to manage the files involved in a merge, after
5990 5988 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5991 5989 working directory must have two parents). See :hg:`help
5992 5990 merge-tools` for information on configuring merge tools.
5993 5991
5994 5992 The resolve command can be used in the following ways:
5995 5993
5996 5994 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5997 5995 the specified files, discarding any previous merge attempts. Re-merging
5998 5996 is not performed for files already marked as resolved. Use ``--all/-a``
5999 5997 to select all unresolved files. ``--tool`` can be used to specify
6000 5998 the merge tool used for the given files. It overrides the HGMERGE
6001 5999 environment variable and your configuration files. Previous file
6002 6000 contents are saved with a ``.orig`` suffix.
6003 6001
6004 6002 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6005 6003 (e.g. after having manually fixed-up the files). The default is
6006 6004 to mark all unresolved files.
6007 6005
6008 6006 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6009 6007 default is to mark all resolved files.
6010 6008
6011 6009 - :hg:`resolve -l`: list files which had or still have conflicts.
6012 6010 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6013 6011 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6014 6012 the list. See :hg:`help filesets` for details.
6015 6013
6016 6014 .. note::
6017 6015
6018 6016 Mercurial will not let you commit files with unresolved merge
6019 6017 conflicts. You must use :hg:`resolve -m ...` before you can
6020 6018 commit after a conflicting merge.
6021 6019
6022 6020 .. container:: verbose
6023 6021
6024 6022 Template:
6025 6023
6026 6024 The following keywords are supported in addition to the common template
6027 6025 keywords and functions. See also :hg:`help templates`.
6028 6026
6029 6027 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6030 6028 :path: String. Repository-absolute path of the file.
6031 6029
6032 6030 Returns 0 on success, 1 if any files fail a resolve attempt.
6033 6031 """
6034 6032
6035 6033 opts = pycompat.byteskwargs(opts)
6036 6034 confirm = ui.configbool(b'commands', b'resolve.confirm')
6037 6035 flaglist = b'all mark unmark list no_status re_merge'.split()
6038 6036 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6039 6037
6040 6038 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6041 6039 if actioncount > 1:
6042 6040 raise error.InputError(_(b"too many actions specified"))
6043 6041 elif actioncount == 0 and ui.configbool(
6044 6042 b'commands', b'resolve.explicit-re-merge'
6045 6043 ):
6046 6044 hint = _(b'use --mark, --unmark, --list or --re-merge')
6047 6045 raise error.InputError(_(b'no action specified'), hint=hint)
6048 6046 if pats and all:
6049 6047 raise error.InputError(_(b"can't specify --all and patterns"))
6050 6048 if not (all or pats or show or mark or unmark):
6051 6049 raise error.InputError(
6052 6050 _(b'no files or directories specified'),
6053 6051 hint=b'use --all to re-merge all unresolved files',
6054 6052 )
6055 6053
6056 6054 if confirm:
6057 6055 if all:
6058 6056 if ui.promptchoice(
6059 6057 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6060 6058 ):
6061 6059 raise error.CanceledError(_(b'user quit'))
6062 6060 if mark and not pats:
6063 6061 if ui.promptchoice(
6064 6062 _(
6065 6063 b'mark all unresolved files as resolved (yn)?'
6066 6064 b'$$ &Yes $$ &No'
6067 6065 )
6068 6066 ):
6069 6067 raise error.CanceledError(_(b'user quit'))
6070 6068 if unmark and not pats:
6071 6069 if ui.promptchoice(
6072 6070 _(
6073 6071 b'mark all resolved files as unresolved (yn)?'
6074 6072 b'$$ &Yes $$ &No'
6075 6073 )
6076 6074 ):
6077 6075 raise error.CanceledError(_(b'user quit'))
6078 6076
6079 6077 uipathfn = scmutil.getuipathfn(repo)
6080 6078
6081 6079 if show:
6082 6080 ui.pager(b'resolve')
6083 6081 fm = ui.formatter(b'resolve', opts)
6084 6082 ms = mergestatemod.mergestate.read(repo)
6085 6083 wctx = repo[None]
6086 6084 m = scmutil.match(wctx, pats, opts)
6087 6085
6088 6086 # Labels and keys based on merge state. Unresolved path conflicts show
6089 6087 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6090 6088 # resolved conflicts.
6091 6089 mergestateinfo = {
6092 6090 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6093 6091 b'resolve.unresolved',
6094 6092 b'U',
6095 6093 ),
6096 6094 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6097 6095 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6098 6096 b'resolve.unresolved',
6099 6097 b'P',
6100 6098 ),
6101 6099 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6102 6100 b'resolve.resolved',
6103 6101 b'R',
6104 6102 ),
6105 6103 }
6106 6104
6107 6105 for f in ms:
6108 6106 if not m(f):
6109 6107 continue
6110 6108
6111 6109 label, key = mergestateinfo[ms[f]]
6112 6110 fm.startitem()
6113 6111 fm.context(ctx=wctx)
6114 6112 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6115 6113 fm.data(path=f)
6116 6114 fm.plain(b'%s\n' % uipathfn(f), label=label)
6117 6115 fm.end()
6118 6116 return 0
6119 6117
6120 6118 with repo.wlock():
6121 6119 ms = mergestatemod.mergestate.read(repo)
6122 6120
6123 6121 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6124 6122 raise error.StateError(
6125 6123 _(b'resolve command not applicable when not merging')
6126 6124 )
6127 6125
6128 6126 wctx = repo[None]
6129 6127 m = scmutil.match(wctx, pats, opts)
6130 6128 ret = 0
6131 6129 didwork = False
6132 6130
6133 6131 hasconflictmarkers = []
6134 6132 if mark:
6135 6133 markcheck = ui.config(b'commands', b'resolve.mark-check')
6136 6134 if markcheck not in [b'warn', b'abort']:
6137 6135 # Treat all invalid / unrecognized values as 'none'.
6138 6136 markcheck = False
6139 6137 for f in ms:
6140 6138 if not m(f):
6141 6139 continue
6142 6140
6143 6141 didwork = True
6144 6142
6145 6143 # path conflicts must be resolved manually
6146 6144 if ms[f] in (
6147 6145 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6148 6146 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6149 6147 ):
6150 6148 if mark:
6151 6149 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6152 6150 elif unmark:
6153 6151 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6154 6152 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6155 6153 ui.warn(
6156 6154 _(b'%s: path conflict must be resolved manually\n')
6157 6155 % uipathfn(f)
6158 6156 )
6159 6157 continue
6160 6158
6161 6159 if mark:
6162 6160 if markcheck:
6163 6161 fdata = repo.wvfs.tryread(f)
6164 6162 if (
6165 6163 filemerge.hasconflictmarkers(fdata)
6166 6164 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6167 6165 ):
6168 6166 hasconflictmarkers.append(f)
6169 6167 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6170 6168 elif unmark:
6171 6169 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6172 6170 else:
6173 6171 # backup pre-resolve (merge uses .orig for its own purposes)
6174 6172 a = repo.wjoin(f)
6175 6173 try:
6176 6174 util.copyfile(a, a + b".resolve")
6177 6175 except FileNotFoundError:
6178 6176 pass
6179 6177
6180 6178 try:
6181 6179 # preresolve file
6182 6180 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6183 6181 with ui.configoverride(overrides, b'resolve'):
6184 6182 r = ms.resolve(f, wctx)
6185 6183 if r:
6186 6184 ret = 1
6187 6185 finally:
6188 6186 ms.commit()
6189 6187
6190 6188 # replace filemerge's .orig file with our resolve file
6191 6189 try:
6192 6190 util.rename(
6193 6191 a + b".resolve", scmutil.backuppath(ui, repo, f)
6194 6192 )
6195 6193 except FileNotFoundError:
6196 6194 pass
6197 6195
6198 6196 if hasconflictmarkers:
6199 6197 ui.warn(
6200 6198 _(
6201 6199 b'warning: the following files still have conflict '
6202 6200 b'markers:\n'
6203 6201 )
6204 6202 + b''.join(
6205 6203 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6206 6204 )
6207 6205 )
6208 6206 if markcheck == b'abort' and not all and not pats:
6209 6207 raise error.StateError(
6210 6208 _(b'conflict markers detected'),
6211 6209 hint=_(b'use --all to mark anyway'),
6212 6210 )
6213 6211
6214 6212 ms.commit()
6215 6213 branchmerge = repo.dirstate.p2() != repo.nullid
6216 6214 # resolve is not doing a parent change here, however, `record updates`
6217 6215 # will call some dirstate API that at intended for parent changes call.
6218 6216 # Ideally we would not need this and could implement a lighter version
6219 6217 # of the recordupdateslogic that will not have to deal with the part
6220 6218 # related to parent changes. However this would requires that:
6221 6219 # - we are sure we passed around enough information at update/merge
6222 6220 # time to no longer needs it at `hg resolve time`
6223 6221 # - we are sure we store that information well enough to be able to reuse it
6224 6222 # - we are the necessary logic to reuse it right.
6225 6223 #
6226 6224 # All this should eventually happens, but in the mean time, we use this
6227 6225 # context manager slightly out of the context it should be.
6228 6226 with repo.dirstate.parentchange():
6229 6227 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6230 6228
6231 6229 if not didwork and pats:
6232 6230 hint = None
6233 6231 if not any([p for p in pats if p.find(b':') >= 0]):
6234 6232 pats = [b'path:%s' % p for p in pats]
6235 6233 m = scmutil.match(wctx, pats, opts)
6236 6234 for f in ms:
6237 6235 if not m(f):
6238 6236 continue
6239 6237
6240 6238 def flag(o):
6241 6239 if o == b're_merge':
6242 6240 return b'--re-merge '
6243 6241 return b'-%s ' % o[0:1]
6244 6242
6245 6243 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6246 6244 hint = _(b"(try: hg resolve %s%s)\n") % (
6247 6245 flags,
6248 6246 b' '.join(pats),
6249 6247 )
6250 6248 break
6251 6249 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6252 6250 if hint:
6253 6251 ui.warn(hint)
6254 6252
6255 6253 unresolvedf = ms.unresolvedcount()
6256 6254 if not unresolvedf:
6257 6255 ui.status(_(b'(no more unresolved files)\n'))
6258 6256 cmdutil.checkafterresolved(repo)
6259 6257
6260 6258 return ret
6261 6259
6262 6260
6263 6261 @command(
6264 6262 b'revert',
6265 6263 [
6266 6264 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6267 6265 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6268 6266 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6269 6267 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6270 6268 (b'i', b'interactive', None, _(b'interactively select the changes')),
6271 6269 ]
6272 6270 + walkopts
6273 6271 + dryrunopts,
6274 6272 _(b'[OPTION]... [-r REV] [NAME]...'),
6275 6273 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6276 6274 )
6277 6275 def revert(ui, repo, *pats, **opts):
6278 6276 """restore files to their checkout state
6279 6277
6280 6278 .. note::
6281 6279
6282 6280 To check out earlier revisions, you should use :hg:`update REV`.
6283 6281 To cancel an uncommitted merge (and lose your changes),
6284 6282 use :hg:`merge --abort`.
6285 6283
6286 6284 With no revision specified, revert the specified files or directories
6287 6285 to the contents they had in the parent of the working directory.
6288 6286 This restores the contents of files to an unmodified
6289 6287 state and unschedules adds, removes, copies, and renames. If the
6290 6288 working directory has two parents, you must explicitly specify a
6291 6289 revision.
6292 6290
6293 6291 Using the -r/--rev or -d/--date options, revert the given files or
6294 6292 directories to their states as of a specific revision. Because
6295 6293 revert does not change the working directory parents, this will
6296 6294 cause these files to appear modified. This can be helpful to "back
6297 6295 out" some or all of an earlier change. See :hg:`backout` for a
6298 6296 related method.
6299 6297
6300 6298 Modified files are saved with a .orig suffix before reverting.
6301 6299 To disable these backups, use --no-backup. It is possible to store
6302 6300 the backup files in a custom directory relative to the root of the
6303 6301 repository by setting the ``ui.origbackuppath`` configuration
6304 6302 option.
6305 6303
6306 6304 See :hg:`help dates` for a list of formats valid for -d/--date.
6307 6305
6308 6306 See :hg:`help backout` for a way to reverse the effect of an
6309 6307 earlier changeset.
6310 6308
6311 6309 Returns 0 on success.
6312 6310 """
6313 6311
6314 6312 opts = pycompat.byteskwargs(opts)
6315 6313 if opts.get(b"date"):
6316 6314 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6317 6315 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6318 6316
6319 6317 parent, p2 = repo.dirstate.parents()
6320 6318 if not opts.get(b'rev') and p2 != repo.nullid:
6321 6319 # revert after merge is a trap for new users (issue2915)
6322 6320 raise error.InputError(
6323 6321 _(b'uncommitted merge with no revision specified'),
6324 6322 hint=_(b"use 'hg update' or see 'hg help revert'"),
6325 6323 )
6326 6324
6327 6325 rev = opts.get(b'rev')
6328 6326 if rev:
6329 6327 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6330 6328 ctx = logcmdutil.revsingle(repo, rev)
6331 6329
6332 6330 if not (
6333 6331 pats
6334 6332 or opts.get(b'include')
6335 6333 or opts.get(b'exclude')
6336 6334 or opts.get(b'all')
6337 6335 or opts.get(b'interactive')
6338 6336 ):
6339 6337 msg = _(b"no files or directories specified")
6340 6338 if p2 != repo.nullid:
6341 6339 hint = _(
6342 6340 b"uncommitted merge, use --all to discard all changes,"
6343 6341 b" or 'hg update -C .' to abort the merge"
6344 6342 )
6345 6343 raise error.InputError(msg, hint=hint)
6346 6344 dirty = any(repo.status())
6347 6345 node = ctx.node()
6348 6346 if node != parent:
6349 6347 if dirty:
6350 6348 hint = (
6351 6349 _(
6352 6350 b"uncommitted changes, use --all to discard all"
6353 6351 b" changes, or 'hg update %d' to update"
6354 6352 )
6355 6353 % ctx.rev()
6356 6354 )
6357 6355 else:
6358 6356 hint = (
6359 6357 _(
6360 6358 b"use --all to revert all files,"
6361 6359 b" or 'hg update %d' to update"
6362 6360 )
6363 6361 % ctx.rev()
6364 6362 )
6365 6363 elif dirty:
6366 6364 hint = _(b"uncommitted changes, use --all to discard all changes")
6367 6365 else:
6368 6366 hint = _(b"use --all to revert all files")
6369 6367 raise error.InputError(msg, hint=hint)
6370 6368
6371 6369 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6372 6370
6373 6371
6374 6372 @command(
6375 6373 b'rollback',
6376 6374 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6377 6375 helpcategory=command.CATEGORY_MAINTENANCE,
6378 6376 )
6379 6377 def rollback(ui, repo, **opts):
6380 6378 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6381 6379
6382 6380 Please use :hg:`commit --amend` instead of rollback to correct
6383 6381 mistakes in the last commit.
6384 6382
6385 6383 This command should be used with care. There is only one level of
6386 6384 rollback, and there is no way to undo a rollback. It will also
6387 6385 restore the dirstate at the time of the last transaction, losing
6388 6386 any dirstate changes since that time. This command does not alter
6389 6387 the working directory.
6390 6388
6391 6389 Transactions are used to encapsulate the effects of all commands
6392 6390 that create new changesets or propagate existing changesets into a
6393 6391 repository.
6394 6392
6395 6393 .. container:: verbose
6396 6394
6397 6395 For example, the following commands are transactional, and their
6398 6396 effects can be rolled back:
6399 6397
6400 6398 - commit
6401 6399 - import
6402 6400 - pull
6403 6401 - push (with this repository as the destination)
6404 6402 - unbundle
6405 6403
6406 6404 To avoid permanent data loss, rollback will refuse to rollback a
6407 6405 commit transaction if it isn't checked out. Use --force to
6408 6406 override this protection.
6409 6407
6410 6408 The rollback command can be entirely disabled by setting the
6411 6409 ``ui.rollback`` configuration setting to false. If you're here
6412 6410 because you want to use rollback and it's disabled, you can
6413 6411 re-enable the command by setting ``ui.rollback`` to true.
6414 6412
6415 6413 This command is not intended for use on public repositories. Once
6416 6414 changes are visible for pull by other users, rolling a transaction
6417 6415 back locally is ineffective (someone else may already have pulled
6418 6416 the changes). Furthermore, a race is possible with readers of the
6419 6417 repository; for example an in-progress pull from the repository
6420 6418 may fail if a rollback is performed.
6421 6419
6422 6420 Returns 0 on success, 1 if no rollback data is available.
6423 6421 """
6424 6422 if not ui.configbool(b'ui', b'rollback'):
6425 6423 raise error.Abort(
6426 6424 _(b'rollback is disabled because it is unsafe'),
6427 6425 hint=b'see `hg help -v rollback` for information',
6428 6426 )
6429 6427 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6430 6428
6431 6429
6432 6430 @command(
6433 6431 b'root',
6434 6432 [] + formatteropts,
6435 6433 intents={INTENT_READONLY},
6436 6434 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6437 6435 )
6438 6436 def root(ui, repo, **opts):
6439 6437 """print the root (top) of the current working directory
6440 6438
6441 6439 Print the root directory of the current repository.
6442 6440
6443 6441 .. container:: verbose
6444 6442
6445 6443 Template:
6446 6444
6447 6445 The following keywords are supported in addition to the common template
6448 6446 keywords and functions. See also :hg:`help templates`.
6449 6447
6450 6448 :hgpath: String. Path to the .hg directory.
6451 6449 :storepath: String. Path to the directory holding versioned data.
6452 6450
6453 6451 Returns 0 on success.
6454 6452 """
6455 6453 opts = pycompat.byteskwargs(opts)
6456 6454 with ui.formatter(b'root', opts) as fm:
6457 6455 fm.startitem()
6458 6456 fm.write(b'reporoot', b'%s\n', repo.root)
6459 6457 fm.data(hgpath=repo.path, storepath=repo.spath)
6460 6458
6461 6459
6462 6460 @command(
6463 6461 b'serve',
6464 6462 [
6465 6463 (
6466 6464 b'A',
6467 6465 b'accesslog',
6468 6466 b'',
6469 6467 _(b'name of access log file to write to'),
6470 6468 _(b'FILE'),
6471 6469 ),
6472 6470 (b'd', b'daemon', None, _(b'run server in background')),
6473 6471 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6474 6472 (
6475 6473 b'E',
6476 6474 b'errorlog',
6477 6475 b'',
6478 6476 _(b'name of error log file to write to'),
6479 6477 _(b'FILE'),
6480 6478 ),
6481 6479 # use string type, then we can check if something was passed
6482 6480 (
6483 6481 b'p',
6484 6482 b'port',
6485 6483 b'',
6486 6484 _(b'port to listen on (default: 8000)'),
6487 6485 _(b'PORT'),
6488 6486 ),
6489 6487 (
6490 6488 b'a',
6491 6489 b'address',
6492 6490 b'',
6493 6491 _(b'address to listen on (default: all interfaces)'),
6494 6492 _(b'ADDR'),
6495 6493 ),
6496 6494 (
6497 6495 b'',
6498 6496 b'prefix',
6499 6497 b'',
6500 6498 _(b'prefix path to serve from (default: server root)'),
6501 6499 _(b'PREFIX'),
6502 6500 ),
6503 6501 (
6504 6502 b'n',
6505 6503 b'name',
6506 6504 b'',
6507 6505 _(b'name to show in web pages (default: working directory)'),
6508 6506 _(b'NAME'),
6509 6507 ),
6510 6508 (
6511 6509 b'',
6512 6510 b'web-conf',
6513 6511 b'',
6514 6512 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6515 6513 _(b'FILE'),
6516 6514 ),
6517 6515 (
6518 6516 b'',
6519 6517 b'webdir-conf',
6520 6518 b'',
6521 6519 _(b'name of the hgweb config file (DEPRECATED)'),
6522 6520 _(b'FILE'),
6523 6521 ),
6524 6522 (
6525 6523 b'',
6526 6524 b'pid-file',
6527 6525 b'',
6528 6526 _(b'name of file to write process ID to'),
6529 6527 _(b'FILE'),
6530 6528 ),
6531 6529 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6532 6530 (
6533 6531 b'',
6534 6532 b'cmdserver',
6535 6533 b'',
6536 6534 _(b'for remote clients (ADVANCED)'),
6537 6535 _(b'MODE'),
6538 6536 ),
6539 6537 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6540 6538 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6541 6539 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6542 6540 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6543 6541 (b'', b'print-url', None, _(b'start and print only the URL')),
6544 6542 ]
6545 6543 + subrepoopts,
6546 6544 _(b'[OPTION]...'),
6547 6545 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6548 6546 helpbasic=True,
6549 6547 optionalrepo=True,
6550 6548 )
6551 6549 def serve(ui, repo, **opts):
6552 6550 """start stand-alone webserver
6553 6551
6554 6552 Start a local HTTP repository browser and pull server. You can use
6555 6553 this for ad-hoc sharing and browsing of repositories. It is
6556 6554 recommended to use a real web server to serve a repository for
6557 6555 longer periods of time.
6558 6556
6559 6557 Please note that the server does not implement access control.
6560 6558 This means that, by default, anybody can read from the server and
6561 6559 nobody can write to it by default. Set the ``web.allow-push``
6562 6560 option to ``*`` to allow everybody to push to the server. You
6563 6561 should use a real web server if you need to authenticate users.
6564 6562
6565 6563 By default, the server logs accesses to stdout and errors to
6566 6564 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6567 6565 files.
6568 6566
6569 6567 To have the server choose a free port number to listen on, specify
6570 6568 a port number of 0; in this case, the server will print the port
6571 6569 number it uses.
6572 6570
6573 6571 Returns 0 on success.
6574 6572 """
6575 6573
6576 6574 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6577 6575 opts = pycompat.byteskwargs(opts)
6578 6576 if opts[b"print_url"] and ui.verbose:
6579 6577 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6580 6578
6581 6579 if opts[b"stdio"]:
6582 6580 if repo is None:
6583 6581 raise error.RepoError(
6584 6582 _(b"there is no Mercurial repository here (.hg not found)")
6585 6583 )
6586 6584 s = wireprotoserver.sshserver(ui, repo)
6587 6585 s.serve_forever()
6588 6586 return
6589 6587
6590 6588 service = server.createservice(ui, repo, opts)
6591 6589 return server.runservice(opts, initfn=service.init, runfn=service.run)
6592 6590
6593 6591
6594 6592 @command(
6595 6593 b'shelve',
6596 6594 [
6597 6595 (
6598 6596 b'A',
6599 6597 b'addremove',
6600 6598 None,
6601 6599 _(b'mark new/missing files as added/removed before shelving'),
6602 6600 ),
6603 6601 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6604 6602 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6605 6603 (
6606 6604 b'',
6607 6605 b'date',
6608 6606 b'',
6609 6607 _(b'shelve with the specified commit date'),
6610 6608 _(b'DATE'),
6611 6609 ),
6612 6610 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6613 6611 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6614 6612 (
6615 6613 b'k',
6616 6614 b'keep',
6617 6615 False,
6618 6616 _(b'shelve, but keep changes in the working directory'),
6619 6617 ),
6620 6618 (b'l', b'list', None, _(b'list current shelves')),
6621 6619 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6622 6620 (
6623 6621 b'n',
6624 6622 b'name',
6625 6623 b'',
6626 6624 _(b'use the given name for the shelved commit'),
6627 6625 _(b'NAME'),
6628 6626 ),
6629 6627 (
6630 6628 b'p',
6631 6629 b'patch',
6632 6630 None,
6633 6631 _(
6634 6632 b'output patches for changes (provide the names of the shelved '
6635 6633 b'changes as positional arguments)'
6636 6634 ),
6637 6635 ),
6638 6636 (b'i', b'interactive', None, _(b'interactive mode')),
6639 6637 (
6640 6638 b'',
6641 6639 b'stat',
6642 6640 None,
6643 6641 _(
6644 6642 b'output diffstat-style summary of changes (provide the names of '
6645 6643 b'the shelved changes as positional arguments)'
6646 6644 ),
6647 6645 ),
6648 6646 ]
6649 6647 + cmdutil.walkopts,
6650 6648 _(b'hg shelve [OPTION]... [FILE]...'),
6651 6649 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6652 6650 )
6653 6651 def shelve(ui, repo, *pats, **opts):
6654 6652 """save and set aside changes from the working directory
6655 6653
6656 6654 Shelving takes files that "hg status" reports as not clean, saves
6657 6655 the modifications to a bundle (a shelved change), and reverts the
6658 6656 files so that their state in the working directory becomes clean.
6659 6657
6660 6658 To restore these changes to the working directory, using "hg
6661 6659 unshelve"; this will work even if you switch to a different
6662 6660 commit.
6663 6661
6664 6662 When no files are specified, "hg shelve" saves all not-clean
6665 6663 files. If specific files or directories are named, only changes to
6666 6664 those files are shelved.
6667 6665
6668 6666 In bare shelve (when no files are specified, without interactive,
6669 6667 include and exclude option), shelving remembers information if the
6670 6668 working directory was on newly created branch, in other words working
6671 6669 directory was on different branch than its first parent. In this
6672 6670 situation unshelving restores branch information to the working directory.
6673 6671
6674 6672 Each shelved change has a name that makes it easier to find later.
6675 6673 The name of a shelved change defaults to being based on the active
6676 6674 bookmark, or if there is no active bookmark, the current named
6677 6675 branch. To specify a different name, use ``--name``.
6678 6676
6679 6677 To see a list of existing shelved changes, use the ``--list``
6680 6678 option. For each shelved change, this will print its name, age,
6681 6679 and description; use ``--patch`` or ``--stat`` for more details.
6682 6680
6683 6681 To delete specific shelved changes, use ``--delete``. To delete
6684 6682 all shelved changes, use ``--cleanup``.
6685 6683 """
6686 6684 opts = pycompat.byteskwargs(opts)
6687 6685 allowables = [
6688 6686 (b'addremove', {b'create'}), # 'create' is pseudo action
6689 6687 (b'unknown', {b'create'}),
6690 6688 (b'cleanup', {b'cleanup'}),
6691 6689 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6692 6690 (b'delete', {b'delete'}),
6693 6691 (b'edit', {b'create'}),
6694 6692 (b'keep', {b'create'}),
6695 6693 (b'list', {b'list'}),
6696 6694 (b'message', {b'create'}),
6697 6695 (b'name', {b'create'}),
6698 6696 (b'patch', {b'patch', b'list'}),
6699 6697 (b'stat', {b'stat', b'list'}),
6700 6698 ]
6701 6699
6702 6700 def checkopt(opt):
6703 6701 if opts.get(opt):
6704 6702 for i, allowable in allowables:
6705 6703 if opts[i] and opt not in allowable:
6706 6704 raise error.InputError(
6707 6705 _(
6708 6706 b"options '--%s' and '--%s' may not be "
6709 6707 b"used together"
6710 6708 )
6711 6709 % (opt, i)
6712 6710 )
6713 6711 return True
6714 6712
6715 6713 if checkopt(b'cleanup'):
6716 6714 if pats:
6717 6715 raise error.InputError(
6718 6716 _(b"cannot specify names when using '--cleanup'")
6719 6717 )
6720 6718 return shelvemod.cleanupcmd(ui, repo)
6721 6719 elif checkopt(b'delete'):
6722 6720 return shelvemod.deletecmd(ui, repo, pats)
6723 6721 elif checkopt(b'list'):
6724 6722 return shelvemod.listcmd(ui, repo, pats, opts)
6725 6723 elif checkopt(b'patch') or checkopt(b'stat'):
6726 6724 return shelvemod.patchcmds(ui, repo, pats, opts)
6727 6725 else:
6728 6726 return shelvemod.createcmd(ui, repo, pats, opts)
6729 6727
6730 6728
6731 6729 _NOTTERSE = b'nothing'
6732 6730
6733 6731
6734 6732 @command(
6735 6733 b'status|st',
6736 6734 [
6737 6735 (b'A', b'all', None, _(b'show status of all files')),
6738 6736 (b'm', b'modified', None, _(b'show only modified files')),
6739 6737 (b'a', b'added', None, _(b'show only added files')),
6740 6738 (b'r', b'removed', None, _(b'show only removed files')),
6741 6739 (b'd', b'deleted', None, _(b'show only missing files')),
6742 6740 (b'c', b'clean', None, _(b'show only files without changes')),
6743 6741 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6744 6742 (b'i', b'ignored', None, _(b'show only ignored files')),
6745 6743 (b'n', b'no-status', None, _(b'hide status prefix')),
6746 6744 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6747 6745 (
6748 6746 b'C',
6749 6747 b'copies',
6750 6748 None,
6751 6749 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6752 6750 ),
6753 6751 (
6754 6752 b'0',
6755 6753 b'print0',
6756 6754 None,
6757 6755 _(b'end filenames with NUL, for use with xargs'),
6758 6756 ),
6759 6757 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6760 6758 (
6761 6759 b'',
6762 6760 b'change',
6763 6761 b'',
6764 6762 _(b'list the changed files of a revision'),
6765 6763 _(b'REV'),
6766 6764 ),
6767 6765 ]
6768 6766 + walkopts
6769 6767 + subrepoopts
6770 6768 + formatteropts,
6771 6769 _(b'[OPTION]... [FILE]...'),
6772 6770 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6773 6771 helpbasic=True,
6774 6772 inferrepo=True,
6775 6773 intents={INTENT_READONLY},
6776 6774 )
6777 6775 def status(ui, repo, *pats, **opts):
6778 6776 """show changed files in the working directory
6779 6777
6780 6778 Show status of files in the repository. If names are given, only
6781 6779 files that match are shown. Files that are clean or ignored or
6782 6780 the source of a copy/move operation, are not listed unless
6783 6781 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6784 6782 Unless options described with "show only ..." are given, the
6785 6783 options -mardu are used.
6786 6784
6787 6785 Option -q/--quiet hides untracked (unknown and ignored) files
6788 6786 unless explicitly requested with -u/--unknown or -i/--ignored.
6789 6787
6790 6788 .. note::
6791 6789
6792 6790 :hg:`status` may appear to disagree with diff if permissions have
6793 6791 changed or a merge has occurred. The standard diff format does
6794 6792 not report permission changes and diff only reports changes
6795 6793 relative to one merge parent.
6796 6794
6797 6795 If one revision is given, it is used as the base revision.
6798 6796 If two revisions are given, the differences between them are
6799 6797 shown. The --change option can also be used as a shortcut to list
6800 6798 the changed files of a revision from its first parent.
6801 6799
6802 6800 The codes used to show the status of files are::
6803 6801
6804 6802 M = modified
6805 6803 A = added
6806 6804 R = removed
6807 6805 C = clean
6808 6806 ! = missing (deleted by non-hg command, but still tracked)
6809 6807 ? = not tracked
6810 6808 I = ignored
6811 6809 = origin of the previous file (with --copies)
6812 6810
6813 6811 .. container:: verbose
6814 6812
6815 6813 The -t/--terse option abbreviates the output by showing only the directory
6816 6814 name if all the files in it share the same status. The option takes an
6817 6815 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6818 6816 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6819 6817 for 'ignored' and 'c' for clean.
6820 6818
6821 6819 It abbreviates only those statuses which are passed. Note that clean and
6822 6820 ignored files are not displayed with '--terse ic' unless the -c/--clean
6823 6821 and -i/--ignored options are also used.
6824 6822
6825 6823 The -v/--verbose option shows information when the repository is in an
6826 6824 unfinished merge, shelve, rebase state etc. You can have this behavior
6827 6825 turned on by default by enabling the ``commands.status.verbose`` option.
6828 6826
6829 6827 You can skip displaying some of these states by setting
6830 6828 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6831 6829 'histedit', 'merge', 'rebase', or 'unshelve'.
6832 6830
6833 6831 Template:
6834 6832
6835 6833 The following keywords are supported in addition to the common template
6836 6834 keywords and functions. See also :hg:`help templates`.
6837 6835
6838 6836 :path: String. Repository-absolute path of the file.
6839 6837 :source: String. Repository-absolute path of the file originated from.
6840 6838 Available if ``--copies`` is specified.
6841 6839 :status: String. Character denoting file's status.
6842 6840
6843 6841 Examples:
6844 6842
6845 6843 - show changes in the working directory relative to a
6846 6844 changeset::
6847 6845
6848 6846 hg status --rev 9353
6849 6847
6850 6848 - show changes in the working directory relative to the
6851 6849 current directory (see :hg:`help patterns` for more information)::
6852 6850
6853 6851 hg status re:
6854 6852
6855 6853 - show all changes including copies in an existing changeset::
6856 6854
6857 6855 hg status --copies --change 9353
6858 6856
6859 6857 - get a NUL separated list of added files, suitable for xargs::
6860 6858
6861 6859 hg status -an0
6862 6860
6863 6861 - show more information about the repository status, abbreviating
6864 6862 added, removed, modified, deleted, and untracked paths::
6865 6863
6866 6864 hg status -v -t mardu
6867 6865
6868 6866 Returns 0 on success.
6869 6867
6870 6868 """
6871 6869
6872 6870 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6873 6871 opts = pycompat.byteskwargs(opts)
6874 6872 revs = opts.get(b'rev', [])
6875 6873 change = opts.get(b'change', b'')
6876 6874 terse = opts.get(b'terse', _NOTTERSE)
6877 6875 if terse is _NOTTERSE:
6878 6876 if revs:
6879 6877 terse = b''
6880 6878 else:
6881 6879 terse = ui.config(b'commands', b'status.terse')
6882 6880
6883 6881 if revs and terse:
6884 6882 msg = _(b'cannot use --terse with --rev')
6885 6883 raise error.InputError(msg)
6886 6884 elif change:
6887 6885 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6888 6886 ctx2 = logcmdutil.revsingle(repo, change, None)
6889 6887 ctx1 = ctx2.p1()
6890 6888 else:
6891 6889 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6892 6890 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6893 6891
6894 6892 forcerelativevalue = None
6895 6893 if ui.hasconfig(b'commands', b'status.relative'):
6896 6894 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6897 6895 uipathfn = scmutil.getuipathfn(
6898 6896 repo,
6899 6897 legacyrelativevalue=bool(pats),
6900 6898 forcerelativevalue=forcerelativevalue,
6901 6899 )
6902 6900
6903 6901 if opts.get(b'print0'):
6904 6902 end = b'\0'
6905 6903 else:
6906 6904 end = b'\n'
6907 6905 states = b'modified added removed deleted unknown ignored clean'.split()
6908 6906 show = [k for k in states if opts.get(k)]
6909 6907 if opts.get(b'all'):
6910 6908 show += ui.quiet and (states[:4] + [b'clean']) or states
6911 6909
6912 6910 if not show:
6913 6911 if ui.quiet:
6914 6912 show = states[:4]
6915 6913 else:
6916 6914 show = states[:5]
6917 6915
6918 6916 m = scmutil.match(ctx2, pats, opts)
6919 6917 if terse:
6920 6918 # we need to compute clean and unknown to terse
6921 6919 stat = repo.status(
6922 6920 ctx1.node(),
6923 6921 ctx2.node(),
6924 6922 m,
6925 6923 b'ignored' in show or b'i' in terse,
6926 6924 clean=True,
6927 6925 unknown=True,
6928 6926 listsubrepos=opts.get(b'subrepos'),
6929 6927 )
6930 6928
6931 6929 stat = cmdutil.tersedir(stat, terse)
6932 6930 else:
6933 6931 stat = repo.status(
6934 6932 ctx1.node(),
6935 6933 ctx2.node(),
6936 6934 m,
6937 6935 b'ignored' in show,
6938 6936 b'clean' in show,
6939 6937 b'unknown' in show,
6940 6938 opts.get(b'subrepos'),
6941 6939 )
6942 6940
6943 6941 changestates = zip(
6944 6942 states,
6945 6943 pycompat.iterbytestr(b'MAR!?IC'),
6946 6944 [getattr(stat, s.decode('utf8')) for s in states],
6947 6945 )
6948 6946
6949 6947 copy = {}
6950 6948 if (
6951 6949 opts.get(b'all')
6952 6950 or opts.get(b'copies')
6953 6951 or ui.configbool(b'ui', b'statuscopies')
6954 6952 ) and not opts.get(b'no_status'):
6955 6953 copy = copies.pathcopies(ctx1, ctx2, m)
6956 6954
6957 6955 morestatus = None
6958 6956 if (
6959 6957 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6960 6958 and not ui.plain()
6961 6959 and not opts.get(b'print0')
6962 6960 ):
6963 6961 morestatus = cmdutil.readmorestatus(repo)
6964 6962
6965 6963 ui.pager(b'status')
6966 6964 fm = ui.formatter(b'status', opts)
6967 6965 fmt = b'%s' + end
6968 6966 showchar = not opts.get(b'no_status')
6969 6967
6970 6968 for state, char, files in changestates:
6971 6969 if state in show:
6972 6970 label = b'status.' + state
6973 6971 for f in files:
6974 6972 fm.startitem()
6975 6973 fm.context(ctx=ctx2)
6976 6974 fm.data(itemtype=b'file', path=f)
6977 6975 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6978 6976 fm.plain(fmt % uipathfn(f), label=label)
6979 6977 if f in copy:
6980 6978 fm.data(source=copy[f])
6981 6979 fm.plain(
6982 6980 (b' %s' + end) % uipathfn(copy[f]),
6983 6981 label=b'status.copied',
6984 6982 )
6985 6983 if morestatus:
6986 6984 morestatus.formatfile(f, fm)
6987 6985
6988 6986 if morestatus:
6989 6987 morestatus.formatfooter(fm)
6990 6988 fm.end()
6991 6989
6992 6990
6993 6991 @command(
6994 6992 b'summary|sum',
6995 6993 [(b'', b'remote', None, _(b'check for push and pull'))],
6996 6994 b'[--remote]',
6997 6995 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6998 6996 helpbasic=True,
6999 6997 intents={INTENT_READONLY},
7000 6998 )
7001 6999 def summary(ui, repo, **opts):
7002 7000 """summarize working directory state
7003 7001
7004 7002 This generates a brief summary of the working directory state,
7005 7003 including parents, branch, commit status, phase and available updates.
7006 7004
7007 7005 With the --remote option, this will check the default paths for
7008 7006 incoming and outgoing changes. This can be time-consuming.
7009 7007
7010 7008 Returns 0 on success.
7011 7009 """
7012 7010
7013 7011 opts = pycompat.byteskwargs(opts)
7014 7012 ui.pager(b'summary')
7015 7013 ctx = repo[None]
7016 7014 parents = ctx.parents()
7017 7015 pnode = parents[0].node()
7018 7016 marks = []
7019 7017
7020 7018 try:
7021 7019 ms = mergestatemod.mergestate.read(repo)
7022 7020 except error.UnsupportedMergeRecords as e:
7023 7021 s = b' '.join(e.recordtypes)
7024 7022 ui.warn(
7025 7023 _(b'warning: merge state has unsupported record types: %s\n') % s
7026 7024 )
7027 7025 unresolved = []
7028 7026 else:
7029 7027 unresolved = list(ms.unresolved())
7030 7028
7031 7029 for p in parents:
7032 7030 # label with log.changeset (instead of log.parent) since this
7033 7031 # shows a working directory parent *changeset*:
7034 7032 # i18n: column positioning for "hg summary"
7035 7033 ui.write(
7036 7034 _(b'parent: %d:%s ') % (p.rev(), p),
7037 7035 label=logcmdutil.changesetlabels(p),
7038 7036 )
7039 7037 ui.write(b' '.join(p.tags()), label=b'log.tag')
7040 7038 if p.bookmarks():
7041 7039 marks.extend(p.bookmarks())
7042 7040 if p.rev() == -1:
7043 7041 if not len(repo):
7044 7042 ui.write(_(b' (empty repository)'))
7045 7043 else:
7046 7044 ui.write(_(b' (no revision checked out)'))
7047 7045 if p.obsolete():
7048 7046 ui.write(_(b' (obsolete)'))
7049 7047 if p.isunstable():
7050 7048 instabilities = (
7051 7049 ui.label(instability, b'trouble.%s' % instability)
7052 7050 for instability in p.instabilities()
7053 7051 )
7054 7052 ui.write(b' (' + b', '.join(instabilities) + b')')
7055 7053 ui.write(b'\n')
7056 7054 if p.description():
7057 7055 ui.status(
7058 7056 b' ' + p.description().splitlines()[0].strip() + b'\n',
7059 7057 label=b'log.summary',
7060 7058 )
7061 7059
7062 7060 branch = ctx.branch()
7063 7061 bheads = repo.branchheads(branch)
7064 7062 # i18n: column positioning for "hg summary"
7065 7063 m = _(b'branch: %s\n') % branch
7066 7064 if branch != b'default':
7067 7065 ui.write(m, label=b'log.branch')
7068 7066 else:
7069 7067 ui.status(m, label=b'log.branch')
7070 7068
7071 7069 if marks:
7072 7070 active = repo._activebookmark
7073 7071 # i18n: column positioning for "hg summary"
7074 7072 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7075 7073 if active is not None:
7076 7074 if active in marks:
7077 7075 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7078 7076 marks.remove(active)
7079 7077 else:
7080 7078 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7081 7079 for m in marks:
7082 7080 ui.write(b' ' + m, label=b'log.bookmark')
7083 7081 ui.write(b'\n', label=b'log.bookmark')
7084 7082
7085 7083 status = repo.status(unknown=True)
7086 7084
7087 7085 c = repo.dirstate.copies()
7088 7086 copied, renamed = [], []
7089 7087 for d, s in c.items():
7090 7088 if s in status.removed:
7091 7089 status.removed.remove(s)
7092 7090 renamed.append(d)
7093 7091 else:
7094 7092 copied.append(d)
7095 7093 if d in status.added:
7096 7094 status.added.remove(d)
7097 7095
7098 7096 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7099 7097
7100 7098 labels = [
7101 7099 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7102 7100 (ui.label(_(b'%d added'), b'status.added'), status.added),
7103 7101 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7104 7102 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7105 7103 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7106 7104 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7107 7105 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7108 7106 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7109 7107 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7110 7108 ]
7111 7109 t = []
7112 7110 for l, s in labels:
7113 7111 if s:
7114 7112 t.append(l % len(s))
7115 7113
7116 7114 t = b', '.join(t)
7117 7115 cleanworkdir = False
7118 7116
7119 7117 if repo.vfs.exists(b'graftstate'):
7120 7118 t += _(b' (graft in progress)')
7121 7119 if repo.vfs.exists(b'updatestate'):
7122 7120 t += _(b' (interrupted update)')
7123 7121 elif len(parents) > 1:
7124 7122 t += _(b' (merge)')
7125 7123 elif branch != parents[0].branch():
7126 7124 t += _(b' (new branch)')
7127 7125 elif parents[0].closesbranch() and pnode in repo.branchheads(
7128 7126 branch, closed=True
7129 7127 ):
7130 7128 t += _(b' (head closed)')
7131 7129 elif not (
7132 7130 status.modified
7133 7131 or status.added
7134 7132 or status.removed
7135 7133 or renamed
7136 7134 or copied
7137 7135 or subs
7138 7136 ):
7139 7137 t += _(b' (clean)')
7140 7138 cleanworkdir = True
7141 7139 elif pnode not in bheads:
7142 7140 t += _(b' (new branch head)')
7143 7141
7144 7142 if parents:
7145 7143 pendingphase = max(p.phase() for p in parents)
7146 7144 else:
7147 7145 pendingphase = phases.public
7148 7146
7149 7147 if pendingphase > phases.newcommitphase(ui):
7150 7148 t += b' (%s)' % phases.phasenames[pendingphase]
7151 7149
7152 7150 if cleanworkdir:
7153 7151 # i18n: column positioning for "hg summary"
7154 7152 ui.status(_(b'commit: %s\n') % t.strip())
7155 7153 else:
7156 7154 # i18n: column positioning for "hg summary"
7157 7155 ui.write(_(b'commit: %s\n') % t.strip())
7158 7156
7159 7157 # all ancestors of branch heads - all ancestors of parent = new csets
7160 7158 new = len(
7161 7159 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7162 7160 )
7163 7161
7164 7162 if new == 0:
7165 7163 # i18n: column positioning for "hg summary"
7166 7164 ui.status(_(b'update: (current)\n'))
7167 7165 elif pnode not in bheads:
7168 7166 # i18n: column positioning for "hg summary"
7169 7167 ui.write(_(b'update: %d new changesets (update)\n') % new)
7170 7168 else:
7171 7169 # i18n: column positioning for "hg summary"
7172 7170 ui.write(
7173 7171 _(b'update: %d new changesets, %d branch heads (merge)\n')
7174 7172 % (new, len(bheads))
7175 7173 )
7176 7174
7177 7175 t = []
7178 7176 draft = len(repo.revs(b'draft()'))
7179 7177 if draft:
7180 7178 t.append(_(b'%d draft') % draft)
7181 7179 secret = len(repo.revs(b'secret()'))
7182 7180 if secret:
7183 7181 t.append(_(b'%d secret') % secret)
7184 7182
7185 7183 if draft or secret:
7186 7184 ui.status(_(b'phases: %s\n') % b', '.join(t))
7187 7185
7188 7186 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7189 7187 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7190 7188 numtrouble = len(repo.revs(trouble + b"()"))
7191 7189 # We write all the possibilities to ease translation
7192 7190 troublemsg = {
7193 7191 b"orphan": _(b"orphan: %d changesets"),
7194 7192 b"contentdivergent": _(b"content-divergent: %d changesets"),
7195 7193 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7196 7194 }
7197 7195 if numtrouble > 0:
7198 7196 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7199 7197
7200 7198 cmdutil.summaryhooks(ui, repo)
7201 7199
7202 7200 if opts.get(b'remote'):
7203 7201 needsincoming, needsoutgoing = True, True
7204 7202 else:
7205 7203 needsincoming, needsoutgoing = False, False
7206 7204 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7207 7205 if i:
7208 7206 needsincoming = True
7209 7207 if o:
7210 7208 needsoutgoing = True
7211 7209 if not needsincoming and not needsoutgoing:
7212 7210 return
7213 7211
7214 7212 def getincoming():
7215 7213 # XXX We should actually skip this if no default is specified, instead
7216 7214 # of passing "default" which will resolve as "./default/" if no default
7217 7215 # path is defined.
7218 7216 source, branches = urlutil.get_unique_pull_path(
7219 7217 b'summary', repo, ui, b'default'
7220 7218 )
7221 7219 sbranch = branches[0]
7222 7220 try:
7223 7221 other = hg.peer(repo, {}, source)
7224 7222 except error.RepoError:
7225 7223 if opts.get(b'remote'):
7226 7224 raise
7227 7225 return source, sbranch, None, None, None
7228 7226 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7229 7227 if revs:
7230 7228 revs = [other.lookup(rev) for rev in revs]
7231 7229 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source))
7232 7230 with repo.ui.silent():
7233 7231 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7234 7232 return source, sbranch, other, commoninc, commoninc[1]
7235 7233
7236 7234 if needsincoming:
7237 7235 source, sbranch, sother, commoninc, incoming = getincoming()
7238 7236 else:
7239 7237 source = sbranch = sother = commoninc = incoming = None
7240 7238
7241 7239 def getoutgoing():
7242 7240 # XXX We should actually skip this if no default is specified, instead
7243 7241 # of passing "default" which will resolve as "./default/" if no default
7244 7242 # path is defined.
7245 7243 d = None
7246 7244 if b'default-push' in ui.paths:
7247 7245 d = b'default-push'
7248 7246 elif b'default' in ui.paths:
7249 7247 d = b'default'
7250 7248 if d is not None:
7251 7249 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7252 7250 dest = path.pushloc or path.loc
7253 7251 dbranch = path.branch
7254 7252 else:
7255 7253 dest = b'default'
7256 7254 dbranch = None
7257 7255 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7258 7256 if source != dest:
7259 7257 try:
7260 7258 dother = hg.peer(repo, {}, dest)
7261 7259 except error.RepoError:
7262 7260 if opts.get(b'remote'):
7263 7261 raise
7264 7262 return dest, dbranch, None, None
7265 7263 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7266 7264 elif sother is None:
7267 7265 # there is no explicit destination peer, but source one is invalid
7268 7266 return dest, dbranch, None, None
7269 7267 else:
7270 7268 dother = sother
7271 7269 if source != dest or (sbranch is not None and sbranch != dbranch):
7272 7270 common = None
7273 7271 else:
7274 7272 common = commoninc
7275 7273 if revs:
7276 7274 revs = [repo.lookup(rev) for rev in revs]
7277 7275 with repo.ui.silent():
7278 7276 outgoing = discovery.findcommonoutgoing(
7279 7277 repo, dother, onlyheads=revs, commoninc=common
7280 7278 )
7281 7279 return dest, dbranch, dother, outgoing
7282 7280
7283 7281 if needsoutgoing:
7284 7282 dest, dbranch, dother, outgoing = getoutgoing()
7285 7283 else:
7286 7284 dest = dbranch = dother = outgoing = None
7287 7285
7288 7286 if opts.get(b'remote'):
7289 7287 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7290 7288 # The former always sets `sother` (or raises an exception if it can't);
7291 7289 # the latter always sets `outgoing`.
7292 7290 assert sother is not None
7293 7291 assert outgoing is not None
7294 7292
7295 7293 t = []
7296 7294 if incoming:
7297 7295 t.append(_(b'1 or more incoming'))
7298 7296 o = outgoing.missing
7299 7297 if o:
7300 7298 t.append(_(b'%d outgoing') % len(o))
7301 7299 other = dother or sother
7302 7300 if b'bookmarks' in other.listkeys(b'namespaces'):
7303 7301 counts = bookmarks.summary(repo, other)
7304 7302 if counts[0] > 0:
7305 7303 t.append(_(b'%d incoming bookmarks') % counts[0])
7306 7304 if counts[1] > 0:
7307 7305 t.append(_(b'%d outgoing bookmarks') % counts[1])
7308 7306
7309 7307 if t:
7310 7308 # i18n: column positioning for "hg summary"
7311 7309 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7312 7310 else:
7313 7311 # i18n: column positioning for "hg summary"
7314 7312 ui.status(_(b'remote: (synced)\n'))
7315 7313
7316 7314 cmdutil.summaryremotehooks(
7317 7315 ui,
7318 7316 repo,
7319 7317 opts,
7320 7318 (
7321 7319 (source, sbranch, sother, commoninc),
7322 7320 (dest, dbranch, dother, outgoing),
7323 7321 ),
7324 7322 )
7325 7323
7326 7324
7327 7325 @command(
7328 7326 b'tag',
7329 7327 [
7330 7328 (b'f', b'force', None, _(b'force tag')),
7331 7329 (b'l', b'local', None, _(b'make the tag local')),
7332 7330 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7333 7331 (b'', b'remove', None, _(b'remove a tag')),
7334 7332 # -l/--local is already there, commitopts cannot be used
7335 7333 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7336 7334 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7337 7335 ]
7338 7336 + commitopts2,
7339 7337 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7340 7338 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7341 7339 )
7342 7340 def tag(ui, repo, name1, *names, **opts):
7343 7341 """add one or more tags for the current or given revision
7344 7342
7345 7343 Name a particular revision using <name>.
7346 7344
7347 7345 Tags are used to name particular revisions of the repository and are
7348 7346 very useful to compare different revisions, to go back to significant
7349 7347 earlier versions or to mark branch points as releases, etc. Changing
7350 7348 an existing tag is normally disallowed; use -f/--force to override.
7351 7349
7352 7350 If no revision is given, the parent of the working directory is
7353 7351 used.
7354 7352
7355 7353 To facilitate version control, distribution, and merging of tags,
7356 7354 they are stored as a file named ".hgtags" which is managed similarly
7357 7355 to other project files and can be hand-edited if necessary. This
7358 7356 also means that tagging creates a new commit. The file
7359 7357 ".hg/localtags" is used for local tags (not shared among
7360 7358 repositories).
7361 7359
7362 7360 Tag commits are usually made at the head of a branch. If the parent
7363 7361 of the working directory is not a branch head, :hg:`tag` aborts; use
7364 7362 -f/--force to force the tag commit to be based on a non-head
7365 7363 changeset.
7366 7364
7367 7365 See :hg:`help dates` for a list of formats valid for -d/--date.
7368 7366
7369 7367 Since tag names have priority over branch names during revision
7370 7368 lookup, using an existing branch name as a tag name is discouraged.
7371 7369
7372 7370 Returns 0 on success.
7373 7371 """
7374 7372 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7375 7373 opts = pycompat.byteskwargs(opts)
7376 7374 with repo.wlock(), repo.lock():
7377 7375 rev_ = b"."
7378 7376 names = [t.strip() for t in (name1,) + names]
7379 7377 if len(names) != len(set(names)):
7380 7378 raise error.InputError(_(b'tag names must be unique'))
7381 7379 for n in names:
7382 7380 scmutil.checknewlabel(repo, n, b'tag')
7383 7381 if not n:
7384 7382 raise error.InputError(
7385 7383 _(b'tag names cannot consist entirely of whitespace')
7386 7384 )
7387 7385 if opts.get(b'rev'):
7388 7386 rev_ = opts[b'rev']
7389 7387 message = opts.get(b'message')
7390 7388 if opts.get(b'remove'):
7391 7389 if opts.get(b'local'):
7392 7390 expectedtype = b'local'
7393 7391 else:
7394 7392 expectedtype = b'global'
7395 7393
7396 7394 for n in names:
7397 7395 if repo.tagtype(n) == b'global':
7398 7396 alltags = tagsmod.findglobaltags(ui, repo)
7399 7397 if alltags[n][0] == repo.nullid:
7400 7398 raise error.InputError(
7401 7399 _(b"tag '%s' is already removed") % n
7402 7400 )
7403 7401 if not repo.tagtype(n):
7404 7402 raise error.InputError(_(b"tag '%s' does not exist") % n)
7405 7403 if repo.tagtype(n) != expectedtype:
7406 7404 if expectedtype == b'global':
7407 7405 raise error.InputError(
7408 7406 _(b"tag '%s' is not a global tag") % n
7409 7407 )
7410 7408 else:
7411 7409 raise error.InputError(
7412 7410 _(b"tag '%s' is not a local tag") % n
7413 7411 )
7414 7412 rev_ = b'null'
7415 7413 if not message:
7416 7414 # we don't translate commit messages
7417 7415 message = b'Removed tag %s' % b', '.join(names)
7418 7416 elif not opts.get(b'force'):
7419 7417 for n in names:
7420 7418 if n in repo.tags():
7421 7419 raise error.InputError(
7422 7420 _(b"tag '%s' already exists (use -f to force)") % n
7423 7421 )
7424 7422 if not opts.get(b'local'):
7425 7423 p1, p2 = repo.dirstate.parents()
7426 7424 if p2 != repo.nullid:
7427 7425 raise error.StateError(_(b'uncommitted merge'))
7428 7426 bheads = repo.branchheads()
7429 7427 if not opts.get(b'force') and bheads and p1 not in bheads:
7430 7428 raise error.InputError(
7431 7429 _(
7432 7430 b'working directory is not at a branch head '
7433 7431 b'(use -f to force)'
7434 7432 )
7435 7433 )
7436 7434 node = logcmdutil.revsingle(repo, rev_).node()
7437 7435
7438 7436 if not message:
7439 7437 # we don't translate commit messages
7440 7438 message = b'Added tag %s for changeset %s' % (
7441 7439 b', '.join(names),
7442 7440 short(node),
7443 7441 )
7444 7442
7445 7443 date = opts.get(b'date')
7446 7444 if date:
7447 7445 date = dateutil.parsedate(date)
7448 7446
7449 7447 if opts.get(b'remove'):
7450 7448 editform = b'tag.remove'
7451 7449 else:
7452 7450 editform = b'tag.add'
7453 7451 editor = cmdutil.getcommiteditor(
7454 7452 editform=editform, **pycompat.strkwargs(opts)
7455 7453 )
7456 7454
7457 7455 # don't allow tagging the null rev
7458 7456 if (
7459 7457 not opts.get(b'remove')
7460 7458 and logcmdutil.revsingle(repo, rev_).rev() == nullrev
7461 7459 ):
7462 7460 raise error.InputError(_(b"cannot tag null revision"))
7463 7461
7464 7462 tagsmod.tag(
7465 7463 repo,
7466 7464 names,
7467 7465 node,
7468 7466 message,
7469 7467 opts.get(b'local'),
7470 7468 opts.get(b'user'),
7471 7469 date,
7472 7470 editor=editor,
7473 7471 )
7474 7472
7475 7473
7476 7474 @command(
7477 7475 b'tags',
7478 7476 formatteropts,
7479 7477 b'',
7480 7478 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7481 7479 intents={INTENT_READONLY},
7482 7480 )
7483 7481 def tags(ui, repo, **opts):
7484 7482 """list repository tags
7485 7483
7486 7484 This lists both regular and local tags. When the -v/--verbose
7487 7485 switch is used, a third column "local" is printed for local tags.
7488 7486 When the -q/--quiet switch is used, only the tag name is printed.
7489 7487
7490 7488 .. container:: verbose
7491 7489
7492 7490 Template:
7493 7491
7494 7492 The following keywords are supported in addition to the common template
7495 7493 keywords and functions such as ``{tag}``. See also
7496 7494 :hg:`help templates`.
7497 7495
7498 7496 :type: String. ``local`` for local tags.
7499 7497
7500 7498 Returns 0 on success.
7501 7499 """
7502 7500
7503 7501 opts = pycompat.byteskwargs(opts)
7504 7502 ui.pager(b'tags')
7505 7503 fm = ui.formatter(b'tags', opts)
7506 7504 hexfunc = fm.hexfunc
7507 7505
7508 7506 for t, n in reversed(repo.tagslist()):
7509 7507 hn = hexfunc(n)
7510 7508 label = b'tags.normal'
7511 7509 tagtype = repo.tagtype(t)
7512 7510 if not tagtype or tagtype == b'global':
7513 7511 tagtype = b''
7514 7512 else:
7515 7513 label = b'tags.' + tagtype
7516 7514
7517 7515 fm.startitem()
7518 7516 fm.context(repo=repo)
7519 7517 fm.write(b'tag', b'%s', t, label=label)
7520 7518 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7521 7519 fm.condwrite(
7522 7520 not ui.quiet,
7523 7521 b'rev node',
7524 7522 fmt,
7525 7523 repo.changelog.rev(n),
7526 7524 hn,
7527 7525 label=label,
7528 7526 )
7529 7527 fm.condwrite(
7530 7528 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7531 7529 )
7532 7530 fm.plain(b'\n')
7533 7531 fm.end()
7534 7532
7535 7533
7536 7534 @command(
7537 7535 b'tip',
7538 7536 [
7539 7537 (b'p', b'patch', None, _(b'show patch')),
7540 7538 (b'g', b'git', None, _(b'use git extended diff format')),
7541 7539 ]
7542 7540 + templateopts,
7543 7541 _(b'[-p] [-g]'),
7544 7542 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7545 7543 )
7546 7544 def tip(ui, repo, **opts):
7547 7545 """show the tip revision (DEPRECATED)
7548 7546
7549 7547 The tip revision (usually just called the tip) is the changeset
7550 7548 most recently added to the repository (and therefore the most
7551 7549 recently changed head).
7552 7550
7553 7551 If you have just made a commit, that commit will be the tip. If
7554 7552 you have just pulled changes from another repository, the tip of
7555 7553 that repository becomes the current tip. The "tip" tag is special
7556 7554 and cannot be renamed or assigned to a different changeset.
7557 7555
7558 7556 This command is deprecated, please use :hg:`heads` instead.
7559 7557
7560 7558 Returns 0 on success.
7561 7559 """
7562 7560 opts = pycompat.byteskwargs(opts)
7563 7561 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7564 7562 displayer.show(repo[b'tip'])
7565 7563 displayer.close()
7566 7564
7567 7565
7568 7566 @command(
7569 7567 b'unbundle',
7570 7568 [
7571 7569 (
7572 7570 b'u',
7573 7571 b'update',
7574 7572 None,
7575 7573 _(b'update to new branch head if changesets were unbundled'),
7576 7574 )
7577 7575 ],
7578 7576 _(b'[-u] FILE...'),
7579 7577 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7580 7578 )
7581 7579 def unbundle(ui, repo, fname1, *fnames, **opts):
7582 7580 """apply one or more bundle files
7583 7581
7584 7582 Apply one or more bundle files generated by :hg:`bundle`.
7585 7583
7586 7584 Returns 0 on success, 1 if an update has unresolved files.
7587 7585 """
7588 7586 fnames = (fname1,) + fnames
7589 7587
7590 7588 with repo.lock():
7591 7589 for fname in fnames:
7592 7590 f = hg.openpath(ui, fname)
7593 7591 gen = exchange.readbundle(ui, f, fname)
7594 7592 if isinstance(gen, streamclone.streamcloneapplier):
7595 7593 raise error.InputError(
7596 7594 _(
7597 7595 b'packed bundles cannot be applied with '
7598 7596 b'"hg unbundle"'
7599 7597 ),
7600 7598 hint=_(b'use "hg debugapplystreamclonebundle"'),
7601 7599 )
7602 7600 url = b'bundle:' + fname
7603 7601 try:
7604 7602 txnname = b'unbundle'
7605 7603 if not isinstance(gen, bundle2.unbundle20):
7606 7604 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7607 7605 with repo.transaction(txnname) as tr:
7608 7606 op = bundle2.applybundle(
7609 7607 repo, gen, tr, source=b'unbundle', url=url
7610 7608 )
7611 7609 except error.BundleUnknownFeatureError as exc:
7612 7610 raise error.Abort(
7613 7611 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7614 7612 hint=_(
7615 7613 b"see https://mercurial-scm.org/"
7616 7614 b"wiki/BundleFeature for more "
7617 7615 b"information"
7618 7616 ),
7619 7617 )
7620 7618 modheads = bundle2.combinechangegroupresults(op)
7621 7619
7622 7620 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7623 7621 return 1
7624 7622 else:
7625 7623 return 0
7626 7624
7627 7625
7628 7626 @command(
7629 7627 b'unshelve',
7630 7628 [
7631 7629 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7632 7630 (
7633 7631 b'c',
7634 7632 b'continue',
7635 7633 None,
7636 7634 _(b'continue an incomplete unshelve operation'),
7637 7635 ),
7638 7636 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7639 7637 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7640 7638 (
7641 7639 b'n',
7642 7640 b'name',
7643 7641 b'',
7644 7642 _(b'restore shelved change with given name'),
7645 7643 _(b'NAME'),
7646 7644 ),
7647 7645 (b't', b'tool', b'', _(b'specify merge tool')),
7648 7646 (
7649 7647 b'',
7650 7648 b'date',
7651 7649 b'',
7652 7650 _(b'set date for temporary commits (DEPRECATED)'),
7653 7651 _(b'DATE'),
7654 7652 ),
7655 7653 ],
7656 7654 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7657 7655 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7658 7656 )
7659 7657 def unshelve(ui, repo, *shelved, **opts):
7660 7658 """restore a shelved change to the working directory
7661 7659
7662 7660 This command accepts an optional name of a shelved change to
7663 7661 restore. If none is given, the most recent shelved change is used.
7664 7662
7665 7663 If a shelved change is applied successfully, the bundle that
7666 7664 contains the shelved changes is moved to a backup location
7667 7665 (.hg/shelve-backup).
7668 7666
7669 7667 Since you can restore a shelved change on top of an arbitrary
7670 7668 commit, it is possible that unshelving will result in a conflict
7671 7669 between your changes and the commits you are unshelving onto. If
7672 7670 this occurs, you must resolve the conflict, then use
7673 7671 ``--continue`` to complete the unshelve operation. (The bundle
7674 7672 will not be moved until you successfully complete the unshelve.)
7675 7673
7676 7674 (Alternatively, you can use ``--abort`` to abandon an unshelve
7677 7675 that causes a conflict. This reverts the unshelved changes, and
7678 7676 leaves the bundle in place.)
7679 7677
7680 7678 If bare shelved change (without interactive, include and exclude
7681 7679 option) was done on newly created branch it would restore branch
7682 7680 information to the working directory.
7683 7681
7684 7682 After a successful unshelve, the shelved changes are stored in a
7685 7683 backup directory. Only the N most recent backups are kept. N
7686 7684 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7687 7685 configuration option.
7688 7686
7689 7687 .. container:: verbose
7690 7688
7691 7689 Timestamp in seconds is used to decide order of backups. More
7692 7690 than ``maxbackups`` backups are kept, if same timestamp
7693 7691 prevents from deciding exact order of them, for safety.
7694 7692
7695 7693 Selected changes can be unshelved with ``--interactive`` flag.
7696 7694 The working directory is updated with the selected changes, and
7697 7695 only the unselected changes remain shelved.
7698 7696 Note: The whole shelve is applied to working directory first before
7699 7697 running interactively. So, this will bring up all the conflicts between
7700 7698 working directory and the shelve, irrespective of which changes will be
7701 7699 unshelved.
7702 7700 """
7703 7701 with repo.wlock():
7704 7702 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7705 7703
7706 7704
7707 7705 statemod.addunfinished(
7708 7706 b'unshelve',
7709 7707 fname=b'shelvedstate',
7710 7708 continueflag=True,
7711 7709 abortfunc=shelvemod.hgabortunshelve,
7712 7710 continuefunc=shelvemod.hgcontinueunshelve,
7713 7711 cmdmsg=_(b'unshelve already in progress'),
7714 7712 )
7715 7713
7716 7714
7717 7715 @command(
7718 7716 b'update|up|checkout|co',
7719 7717 [
7720 7718 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7721 7719 (b'c', b'check', None, _(b'require clean working directory')),
7722 7720 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7723 7721 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7724 7722 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7725 7723 ]
7726 7724 + mergetoolopts,
7727 7725 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7728 7726 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7729 7727 helpbasic=True,
7730 7728 )
7731 7729 def update(ui, repo, node=None, **opts):
7732 7730 """update working directory (or switch revisions)
7733 7731
7734 7732 Update the repository's working directory to the specified
7735 7733 changeset. If no changeset is specified, update to the tip of the
7736 7734 current named branch and move the active bookmark (see :hg:`help
7737 7735 bookmarks`).
7738 7736
7739 7737 Update sets the working directory's parent revision to the specified
7740 7738 changeset (see :hg:`help parents`).
7741 7739
7742 7740 If the changeset is not a descendant or ancestor of the working
7743 7741 directory's parent and there are uncommitted changes, the update is
7744 7742 aborted. With the -c/--check option, the working directory is checked
7745 7743 for uncommitted changes; if none are found, the working directory is
7746 7744 updated to the specified changeset.
7747 7745
7748 7746 .. container:: verbose
7749 7747
7750 7748 The -C/--clean, -c/--check, and -m/--merge options control what
7751 7749 happens if the working directory contains uncommitted changes.
7752 7750 At most of one of them can be specified.
7753 7751
7754 7752 1. If no option is specified, and if
7755 7753 the requested changeset is an ancestor or descendant of
7756 7754 the working directory's parent, the uncommitted changes
7757 7755 are merged into the requested changeset and the merged
7758 7756 result is left uncommitted. If the requested changeset is
7759 7757 not an ancestor or descendant (that is, it is on another
7760 7758 branch), the update is aborted and the uncommitted changes
7761 7759 are preserved.
7762 7760
7763 7761 2. With the -m/--merge option, the update is allowed even if the
7764 7762 requested changeset is not an ancestor or descendant of
7765 7763 the working directory's parent.
7766 7764
7767 7765 3. With the -c/--check option, the update is aborted and the
7768 7766 uncommitted changes are preserved.
7769 7767
7770 7768 4. With the -C/--clean option, uncommitted changes are discarded and
7771 7769 the working directory is updated to the requested changeset.
7772 7770
7773 7771 To cancel an uncommitted merge (and lose your changes), use
7774 7772 :hg:`merge --abort`.
7775 7773
7776 7774 Use null as the changeset to remove the working directory (like
7777 7775 :hg:`clone -U`).
7778 7776
7779 7777 If you want to revert just one file to an older revision, use
7780 7778 :hg:`revert [-r REV] NAME`.
7781 7779
7782 7780 See :hg:`help dates` for a list of formats valid for -d/--date.
7783 7781
7784 7782 Returns 0 on success, 1 if there are unresolved files.
7785 7783 """
7786 7784 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7787 7785 rev = opts.get('rev')
7788 7786 date = opts.get('date')
7789 7787 clean = opts.get('clean')
7790 7788 check = opts.get('check')
7791 7789 merge = opts.get('merge')
7792 7790 if rev and node:
7793 7791 raise error.InputError(_(b"please specify just one revision"))
7794 7792
7795 7793 if ui.configbool(b'commands', b'update.requiredest'):
7796 7794 if not node and not rev and not date:
7797 7795 raise error.InputError(
7798 7796 _(b'you must specify a destination'),
7799 7797 hint=_(b'for example: hg update ".::"'),
7800 7798 )
7801 7799
7802 7800 if rev is None or rev == b'':
7803 7801 rev = node
7804 7802
7805 7803 if date and rev is not None:
7806 7804 raise error.InputError(_(b"you can't specify a revision and a date"))
7807 7805
7808 7806 updatecheck = None
7809 7807 if check or merge is not None and not merge:
7810 7808 updatecheck = b'abort'
7811 7809 elif merge or check is not None and not check:
7812 7810 updatecheck = b'none'
7813 7811
7814 7812 with repo.wlock():
7815 7813 cmdutil.clearunfinished(repo)
7816 7814 if date:
7817 7815 rev = cmdutil.finddate(ui, repo, date)
7818 7816
7819 7817 # if we defined a bookmark, we have to remember the original name
7820 7818 brev = rev
7821 7819 if rev:
7822 7820 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7823 7821 ctx = logcmdutil.revsingle(repo, rev, default=None)
7824 7822 rev = ctx.rev()
7825 7823 hidden = ctx.hidden()
7826 7824 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7827 7825 with ui.configoverride(overrides, b'update'):
7828 7826 ret = hg.updatetotally(
7829 7827 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7830 7828 )
7831 7829 if hidden:
7832 7830 ctxstr = ctx.hex()[:12]
7833 7831 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7834 7832
7835 7833 if ctx.obsolete():
7836 7834 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7837 7835 ui.warn(b"(%s)\n" % obsfatemsg)
7838 7836 return ret
7839 7837
7840 7838
7841 7839 @command(
7842 7840 b'verify',
7843 7841 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7844 7842 helpcategory=command.CATEGORY_MAINTENANCE,
7845 7843 )
7846 7844 def verify(ui, repo, **opts):
7847 7845 """verify the integrity of the repository
7848 7846
7849 7847 Verify the integrity of the current repository.
7850 7848
7851 7849 This will perform an extensive check of the repository's
7852 7850 integrity, validating the hashes and checksums of each entry in
7853 7851 the changelog, manifest, and tracked files, as well as the
7854 7852 integrity of their crosslinks and indices.
7855 7853
7856 7854 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7857 7855 for more information about recovery from corruption of the
7858 7856 repository.
7859 7857
7860 7858 Returns 0 on success, 1 if errors are encountered.
7861 7859 """
7862 7860 opts = pycompat.byteskwargs(opts)
7863 7861
7864 7862 level = None
7865 7863 if opts[b'full']:
7866 7864 level = verifymod.VERIFY_FULL
7867 7865 return hg.verify(repo, level)
7868 7866
7869 7867
7870 7868 @command(
7871 7869 b'version',
7872 7870 [] + formatteropts,
7873 7871 helpcategory=command.CATEGORY_HELP,
7874 7872 norepo=True,
7875 7873 intents={INTENT_READONLY},
7876 7874 )
7877 7875 def version_(ui, **opts):
7878 7876 """output version and copyright information
7879 7877
7880 7878 .. container:: verbose
7881 7879
7882 7880 Template:
7883 7881
7884 7882 The following keywords are supported. See also :hg:`help templates`.
7885 7883
7886 7884 :extensions: List of extensions.
7887 7885 :ver: String. Version number.
7888 7886
7889 7887 And each entry of ``{extensions}`` provides the following sub-keywords
7890 7888 in addition to ``{ver}``.
7891 7889
7892 7890 :bundled: Boolean. True if included in the release.
7893 7891 :name: String. Extension name.
7894 7892 """
7895 7893 opts = pycompat.byteskwargs(opts)
7896 7894 if ui.verbose:
7897 7895 ui.pager(b'version')
7898 7896 fm = ui.formatter(b"version", opts)
7899 7897 fm.startitem()
7900 7898 fm.write(
7901 7899 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7902 7900 )
7903 7901 license = _(
7904 7902 b"(see https://mercurial-scm.org for more information)\n"
7905 7903 b"\nCopyright (C) 2005-2022 Olivia Mackall and others\n"
7906 7904 b"This is free software; see the source for copying conditions. "
7907 7905 b"There is NO\nwarranty; "
7908 7906 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7909 7907 )
7910 7908 if not ui.quiet:
7911 7909 fm.plain(license)
7912 7910
7913 7911 if ui.verbose:
7914 7912 fm.plain(_(b"\nEnabled extensions:\n\n"))
7915 7913 # format names and versions into columns
7916 7914 names = []
7917 7915 vers = []
7918 7916 isinternals = []
7919 7917 for name, module in sorted(extensions.extensions()):
7920 7918 names.append(name)
7921 7919 vers.append(extensions.moduleversion(module) or None)
7922 7920 isinternals.append(extensions.ismoduleinternal(module))
7923 7921 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7924 7922 if names:
7925 7923 namefmt = b" %%-%ds " % max(len(n) for n in names)
7926 7924 places = [_(b"external"), _(b"internal")]
7927 7925 for n, v, p in zip(names, vers, isinternals):
7928 7926 fn.startitem()
7929 7927 fn.condwrite(ui.verbose, b"name", namefmt, n)
7930 7928 if ui.verbose:
7931 7929 fn.plain(b"%s " % places[p])
7932 7930 fn.data(bundled=p)
7933 7931 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7934 7932 if ui.verbose:
7935 7933 fn.plain(b"\n")
7936 7934 fn.end()
7937 7935 fm.end()
7938 7936
7939 7937
7940 7938 def loadcmdtable(ui, name, cmdtable):
7941 7939 """Load command functions from specified cmdtable"""
7942 7940 overrides = [cmd for cmd in cmdtable if cmd in table]
7943 7941 if overrides:
7944 7942 ui.warn(
7945 7943 _(b"extension '%s' overrides commands: %s\n")
7946 7944 % (name, b" ".join(overrides))
7947 7945 )
7948 7946 table.update(cmdtable)
General Comments 0
You need to be logged in to leave comments. Login now