##// END OF EJS Templates
branch: migrate `opts` to native kwargs
Matt Harbison -
r51721:13ad1b2a default
parent child Browse files
Show More
@@ -1,4126 +1,4126 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8
8
9 import copy as copymod
9 import copy as copymod
10 import errno
10 import errno
11 import functools
11 import functools
12 import os
12 import os
13 import re
13 import re
14
14
15 from typing import (
15 from typing import (
16 Any,
16 Any,
17 AnyStr,
17 AnyStr,
18 Dict,
18 Dict,
19 Iterable,
19 Iterable,
20 Optional,
20 Optional,
21 cast,
21 cast,
22 )
22 )
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import (
25 from .node import (
26 hex,
26 hex,
27 nullrev,
27 nullrev,
28 short,
28 short,
29 )
29 )
30 from .pycompat import (
30 from .pycompat import (
31 getattr,
31 getattr,
32 open,
32 open,
33 setattr,
33 setattr,
34 )
34 )
35 from .thirdparty import attr
35 from .thirdparty import attr
36
36
37 from . import (
37 from . import (
38 bookmarks,
38 bookmarks,
39 changelog,
39 changelog,
40 copies,
40 copies,
41 crecord as crecordmod,
41 crecord as crecordmod,
42 encoding,
42 encoding,
43 error,
43 error,
44 formatter,
44 formatter,
45 logcmdutil,
45 logcmdutil,
46 match as matchmod,
46 match as matchmod,
47 merge as mergemod,
47 merge as mergemod,
48 mergestate as mergestatemod,
48 mergestate as mergestatemod,
49 mergeutil,
49 mergeutil,
50 obsolete,
50 obsolete,
51 patch,
51 patch,
52 pathutil,
52 pathutil,
53 phases,
53 phases,
54 pycompat,
54 pycompat,
55 repair,
55 repair,
56 revlog,
56 revlog,
57 rewriteutil,
57 rewriteutil,
58 scmutil,
58 scmutil,
59 state as statemod,
59 state as statemod,
60 subrepoutil,
60 subrepoutil,
61 templatekw,
61 templatekw,
62 templater,
62 templater,
63 util,
63 util,
64 vfs as vfsmod,
64 vfs as vfsmod,
65 )
65 )
66
66
67 from .utils import (
67 from .utils import (
68 dateutil,
68 dateutil,
69 stringutil,
69 stringutil,
70 )
70 )
71
71
72 from .revlogutils import (
72 from .revlogutils import (
73 constants as revlog_constants,
73 constants as revlog_constants,
74 )
74 )
75
75
76 if pycompat.TYPE_CHECKING:
76 if pycompat.TYPE_CHECKING:
77 from . import (
77 from . import (
78 ui as uimod,
78 ui as uimod,
79 )
79 )
80
80
81 stringio = util.stringio
81 stringio = util.stringio
82
82
83 # templates of common command options
83 # templates of common command options
84
84
85 dryrunopts = [
85 dryrunopts = [
86 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
86 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
87 ]
87 ]
88
88
89 confirmopts = [
89 confirmopts = [
90 (b'', b'confirm', None, _(b'ask before applying actions')),
90 (b'', b'confirm', None, _(b'ask before applying actions')),
91 ]
91 ]
92
92
93 remoteopts = [
93 remoteopts = [
94 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
94 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
95 (
95 (
96 b'',
96 b'',
97 b'remotecmd',
97 b'remotecmd',
98 b'',
98 b'',
99 _(b'specify hg command to run on the remote side'),
99 _(b'specify hg command to run on the remote side'),
100 _(b'CMD'),
100 _(b'CMD'),
101 ),
101 ),
102 (
102 (
103 b'',
103 b'',
104 b'insecure',
104 b'insecure',
105 None,
105 None,
106 _(b'do not verify server certificate (ignoring web.cacerts config)'),
106 _(b'do not verify server certificate (ignoring web.cacerts config)'),
107 ),
107 ),
108 ]
108 ]
109
109
110 walkopts = [
110 walkopts = [
111 (
111 (
112 b'I',
112 b'I',
113 b'include',
113 b'include',
114 [],
114 [],
115 _(b'include names matching the given patterns'),
115 _(b'include names matching the given patterns'),
116 _(b'PATTERN'),
116 _(b'PATTERN'),
117 ),
117 ),
118 (
118 (
119 b'X',
119 b'X',
120 b'exclude',
120 b'exclude',
121 [],
121 [],
122 _(b'exclude names matching the given patterns'),
122 _(b'exclude names matching the given patterns'),
123 _(b'PATTERN'),
123 _(b'PATTERN'),
124 ),
124 ),
125 ]
125 ]
126
126
127 commitopts = [
127 commitopts = [
128 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
128 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
129 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
129 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
130 ]
130 ]
131
131
132 commitopts2 = [
132 commitopts2 = [
133 (
133 (
134 b'd',
134 b'd',
135 b'date',
135 b'date',
136 b'',
136 b'',
137 _(b'record the specified date as commit date'),
137 _(b'record the specified date as commit date'),
138 _(b'DATE'),
138 _(b'DATE'),
139 ),
139 ),
140 (
140 (
141 b'u',
141 b'u',
142 b'user',
142 b'user',
143 b'',
143 b'',
144 _(b'record the specified user as committer'),
144 _(b'record the specified user as committer'),
145 _(b'USER'),
145 _(b'USER'),
146 ),
146 ),
147 ]
147 ]
148
148
149 commitopts3 = [
149 commitopts3 = [
150 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
150 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
151 (b'U', b'currentuser', None, _(b'record the current user as committer')),
151 (b'U', b'currentuser', None, _(b'record the current user as committer')),
152 ]
152 ]
153
153
154 formatteropts = [
154 formatteropts = [
155 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
155 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
156 ]
156 ]
157
157
158 templateopts = [
158 templateopts = [
159 (
159 (
160 b'',
160 b'',
161 b'style',
161 b'style',
162 b'',
162 b'',
163 _(b'display using template map file (DEPRECATED)'),
163 _(b'display using template map file (DEPRECATED)'),
164 _(b'STYLE'),
164 _(b'STYLE'),
165 ),
165 ),
166 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
166 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
167 ]
167 ]
168
168
169 logopts = [
169 logopts = [
170 (b'p', b'patch', None, _(b'show patch')),
170 (b'p', b'patch', None, _(b'show patch')),
171 (b'g', b'git', None, _(b'use git extended diff format')),
171 (b'g', b'git', None, _(b'use git extended diff format')),
172 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
172 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
173 (b'M', b'no-merges', None, _(b'do not show merges')),
173 (b'M', b'no-merges', None, _(b'do not show merges')),
174 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
174 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
175 (b'G', b'graph', None, _(b"show the revision DAG")),
175 (b'G', b'graph', None, _(b"show the revision DAG")),
176 ] + templateopts
176 ] + templateopts
177
177
178 diffopts = [
178 diffopts = [
179 (b'a', b'text', None, _(b'treat all files as text')),
179 (b'a', b'text', None, _(b'treat all files as text')),
180 (
180 (
181 b'g',
181 b'g',
182 b'git',
182 b'git',
183 None,
183 None,
184 _(b'use git extended diff format (DEFAULT: diff.git)'),
184 _(b'use git extended diff format (DEFAULT: diff.git)'),
185 ),
185 ),
186 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
186 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
187 (b'', b'nodates', None, _(b'omit dates from diff headers')),
187 (b'', b'nodates', None, _(b'omit dates from diff headers')),
188 ]
188 ]
189
189
190 diffwsopts = [
190 diffwsopts = [
191 (
191 (
192 b'w',
192 b'w',
193 b'ignore-all-space',
193 b'ignore-all-space',
194 None,
194 None,
195 _(b'ignore white space when comparing lines'),
195 _(b'ignore white space when comparing lines'),
196 ),
196 ),
197 (
197 (
198 b'b',
198 b'b',
199 b'ignore-space-change',
199 b'ignore-space-change',
200 None,
200 None,
201 _(b'ignore changes in the amount of white space'),
201 _(b'ignore changes in the amount of white space'),
202 ),
202 ),
203 (
203 (
204 b'B',
204 b'B',
205 b'ignore-blank-lines',
205 b'ignore-blank-lines',
206 None,
206 None,
207 _(b'ignore changes whose lines are all blank'),
207 _(b'ignore changes whose lines are all blank'),
208 ),
208 ),
209 (
209 (
210 b'Z',
210 b'Z',
211 b'ignore-space-at-eol',
211 b'ignore-space-at-eol',
212 None,
212 None,
213 _(b'ignore changes in whitespace at EOL'),
213 _(b'ignore changes in whitespace at EOL'),
214 ),
214 ),
215 ]
215 ]
216
216
217 diffopts2 = (
217 diffopts2 = (
218 [
218 [
219 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
219 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
220 (
220 (
221 b'p',
221 b'p',
222 b'show-function',
222 b'show-function',
223 None,
223 None,
224 _(
224 _(
225 b'show which function each change is in (DEFAULT: diff.showfunc)'
225 b'show which function each change is in (DEFAULT: diff.showfunc)'
226 ),
226 ),
227 ),
227 ),
228 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
228 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
229 ]
229 ]
230 + diffwsopts
230 + diffwsopts
231 + [
231 + [
232 (
232 (
233 b'U',
233 b'U',
234 b'unified',
234 b'unified',
235 b'',
235 b'',
236 _(b'number of lines of context to show'),
236 _(b'number of lines of context to show'),
237 _(b'NUM'),
237 _(b'NUM'),
238 ),
238 ),
239 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
239 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
240 (
240 (
241 b'',
241 b'',
242 b'root',
242 b'root',
243 b'',
243 b'',
244 _(b'produce diffs relative to subdirectory'),
244 _(b'produce diffs relative to subdirectory'),
245 _(b'DIR'),
245 _(b'DIR'),
246 ),
246 ),
247 ]
247 ]
248 )
248 )
249
249
250 mergetoolopts = [
250 mergetoolopts = [
251 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
251 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
252 ]
252 ]
253
253
254 similarityopts = [
254 similarityopts = [
255 (
255 (
256 b's',
256 b's',
257 b'similarity',
257 b'similarity',
258 b'',
258 b'',
259 _(b'guess renamed files by similarity (0<=s<=100)'),
259 _(b'guess renamed files by similarity (0<=s<=100)'),
260 _(b'SIMILARITY'),
260 _(b'SIMILARITY'),
261 )
261 )
262 ]
262 ]
263
263
264 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
264 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
265
265
266 debugrevlogopts = [
266 debugrevlogopts = [
267 (b'c', b'changelog', False, _(b'open changelog')),
267 (b'c', b'changelog', False, _(b'open changelog')),
268 (b'm', b'manifest', False, _(b'open manifest')),
268 (b'm', b'manifest', False, _(b'open manifest')),
269 (b'', b'dir', b'', _(b'open directory manifest')),
269 (b'', b'dir', b'', _(b'open directory manifest')),
270 ]
270 ]
271
271
272 # special string such that everything below this line will be ingored in the
272 # special string such that everything below this line will be ingored in the
273 # editor text
273 # editor text
274 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
274 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
275
275
276
276
277 def check_at_most_one_arg(
277 def check_at_most_one_arg(
278 opts: Dict[AnyStr, Any],
278 opts: Dict[AnyStr, Any],
279 *args: AnyStr,
279 *args: AnyStr,
280 ) -> Optional[AnyStr]:
280 ) -> Optional[AnyStr]:
281 """abort if more than one of the arguments are in opts
281 """abort if more than one of the arguments are in opts
282
282
283 Returns the unique argument or None if none of them were specified.
283 Returns the unique argument or None if none of them were specified.
284 """
284 """
285
285
286 def to_display(name: AnyStr) -> bytes:
286 def to_display(name: AnyStr) -> bytes:
287 return pycompat.sysbytes(name).replace(b'_', b'-')
287 return pycompat.sysbytes(name).replace(b'_', b'-')
288
288
289 previous = None
289 previous = None
290 for x in args:
290 for x in args:
291 if opts.get(x):
291 if opts.get(x):
292 if previous:
292 if previous:
293 raise error.InputError(
293 raise error.InputError(
294 _(b'cannot specify both --%s and --%s')
294 _(b'cannot specify both --%s and --%s')
295 % (to_display(previous), to_display(x))
295 % (to_display(previous), to_display(x))
296 )
296 )
297 previous = x
297 previous = x
298 return previous
298 return previous
299
299
300
300
301 def check_incompatible_arguments(
301 def check_incompatible_arguments(
302 opts: Dict[AnyStr, Any],
302 opts: Dict[AnyStr, Any],
303 first: AnyStr,
303 first: AnyStr,
304 others: Iterable[AnyStr],
304 others: Iterable[AnyStr],
305 ) -> None:
305 ) -> None:
306 """abort if the first argument is given along with any of the others
306 """abort if the first argument is given along with any of the others
307
307
308 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
308 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
309 among themselves, and they're passed as a single collection.
309 among themselves, and they're passed as a single collection.
310 """
310 """
311 for other in others:
311 for other in others:
312 check_at_most_one_arg(opts, first, other)
312 check_at_most_one_arg(opts, first, other)
313
313
314
314
315 def resolve_commit_options(ui: "uimod.ui", opts: Dict[str, Any]) -> bool:
315 def resolve_commit_options(ui: "uimod.ui", opts: Dict[str, Any]) -> bool:
316 """modify commit options dict to handle related options
316 """modify commit options dict to handle related options
317
317
318 The return value indicates that ``rewrite.update-timestamp`` is the reason
318 The return value indicates that ``rewrite.update-timestamp`` is the reason
319 the ``date`` option is set.
319 the ``date`` option is set.
320 """
320 """
321 check_at_most_one_arg(opts, 'date', 'currentdate')
321 check_at_most_one_arg(opts, 'date', 'currentdate')
322 check_at_most_one_arg(opts, 'user', 'currentuser')
322 check_at_most_one_arg(opts, 'user', 'currentuser')
323
323
324 datemaydiffer = False # date-only change should be ignored?
324 datemaydiffer = False # date-only change should be ignored?
325
325
326 if opts.get('currentdate'):
326 if opts.get('currentdate'):
327 opts['date'] = b'%d %d' % dateutil.makedate()
327 opts['date'] = b'%d %d' % dateutil.makedate()
328 elif (
328 elif (
329 not opts.get('date')
329 not opts.get('date')
330 and ui.configbool(b'rewrite', b'update-timestamp')
330 and ui.configbool(b'rewrite', b'update-timestamp')
331 and opts.get('currentdate') is None
331 and opts.get('currentdate') is None
332 ):
332 ):
333 opts['date'] = b'%d %d' % dateutil.makedate()
333 opts['date'] = b'%d %d' % dateutil.makedate()
334 datemaydiffer = True
334 datemaydiffer = True
335
335
336 if opts.get('currentuser'):
336 if opts.get('currentuser'):
337 opts['user'] = ui.username()
337 opts['user'] = ui.username()
338
338
339 return datemaydiffer
339 return datemaydiffer
340
340
341
341
342 def check_note_size(opts: Dict[str, Any]) -> None:
342 def check_note_size(opts: Dict[str, Any]) -> None:
343 """make sure note is of valid format"""
343 """make sure note is of valid format"""
344
344
345 note = opts.get('note')
345 note = opts.get('note')
346 if not note:
346 if not note:
347 return
347 return
348
348
349 if len(note) > 255:
349 if len(note) > 255:
350 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
350 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
351 if b'\n' in note:
351 if b'\n' in note:
352 raise error.InputError(_(b"note cannot contain a newline"))
352 raise error.InputError(_(b"note cannot contain a newline"))
353
353
354
354
355 def ishunk(x):
355 def ishunk(x):
356 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
356 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
357 return isinstance(x, hunkclasses)
357 return isinstance(x, hunkclasses)
358
358
359
359
360 def isheader(x):
360 def isheader(x):
361 headerclasses = (crecordmod.uiheader, patch.header)
361 headerclasses = (crecordmod.uiheader, patch.header)
362 return isinstance(x, headerclasses)
362 return isinstance(x, headerclasses)
363
363
364
364
365 def newandmodified(chunks):
365 def newandmodified(chunks):
366 newlyaddedandmodifiedfiles = set()
366 newlyaddedandmodifiedfiles = set()
367 alsorestore = set()
367 alsorestore = set()
368 for chunk in chunks:
368 for chunk in chunks:
369 if isheader(chunk) and chunk.isnewfile():
369 if isheader(chunk) and chunk.isnewfile():
370 newlyaddedandmodifiedfiles.add(chunk.filename())
370 newlyaddedandmodifiedfiles.add(chunk.filename())
371 alsorestore.update(set(chunk.files()) - {chunk.filename()})
371 alsorestore.update(set(chunk.files()) - {chunk.filename()})
372 return newlyaddedandmodifiedfiles, alsorestore
372 return newlyaddedandmodifiedfiles, alsorestore
373
373
374
374
375 def parsealiases(cmd):
375 def parsealiases(cmd):
376 base_aliases = cmd.split(b"|")
376 base_aliases = cmd.split(b"|")
377 all_aliases = set(base_aliases)
377 all_aliases = set(base_aliases)
378 extra_aliases = []
378 extra_aliases = []
379 for alias in base_aliases:
379 for alias in base_aliases:
380 if b'-' in alias:
380 if b'-' in alias:
381 folded_alias = alias.replace(b'-', b'')
381 folded_alias = alias.replace(b'-', b'')
382 if folded_alias not in all_aliases:
382 if folded_alias not in all_aliases:
383 all_aliases.add(folded_alias)
383 all_aliases.add(folded_alias)
384 extra_aliases.append(folded_alias)
384 extra_aliases.append(folded_alias)
385 base_aliases.extend(extra_aliases)
385 base_aliases.extend(extra_aliases)
386 return base_aliases
386 return base_aliases
387
387
388
388
389 def setupwrapcolorwrite(ui):
389 def setupwrapcolorwrite(ui):
390 # wrap ui.write so diff output can be labeled/colorized
390 # wrap ui.write so diff output can be labeled/colorized
391 def wrapwrite(orig, *args, **kw):
391 def wrapwrite(orig, *args, **kw):
392 label = kw.pop('label', b'')
392 label = kw.pop('label', b'')
393 for chunk, l in patch.difflabel(lambda: args):
393 for chunk, l in patch.difflabel(lambda: args):
394 orig(chunk, label=label + l)
394 orig(chunk, label=label + l)
395
395
396 oldwrite = ui.write
396 oldwrite = ui.write
397
397
398 def wrap(*args, **kwargs):
398 def wrap(*args, **kwargs):
399 return wrapwrite(oldwrite, *args, **kwargs)
399 return wrapwrite(oldwrite, *args, **kwargs)
400
400
401 setattr(ui, 'write', wrap)
401 setattr(ui, 'write', wrap)
402 return oldwrite
402 return oldwrite
403
403
404
404
405 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
405 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
406 try:
406 try:
407 if usecurses:
407 if usecurses:
408 if testfile:
408 if testfile:
409 recordfn = crecordmod.testdecorator(
409 recordfn = crecordmod.testdecorator(
410 testfile, crecordmod.testchunkselector
410 testfile, crecordmod.testchunkselector
411 )
411 )
412 else:
412 else:
413 recordfn = crecordmod.chunkselector
413 recordfn = crecordmod.chunkselector
414
414
415 return crecordmod.filterpatch(
415 return crecordmod.filterpatch(
416 ui, originalhunks, recordfn, operation
416 ui, originalhunks, recordfn, operation
417 )
417 )
418 except crecordmod.fallbackerror as e:
418 except crecordmod.fallbackerror as e:
419 ui.warn(b'%s\n' % e)
419 ui.warn(b'%s\n' % e)
420 ui.warn(_(b'falling back to text mode\n'))
420 ui.warn(_(b'falling back to text mode\n'))
421
421
422 return patch.filterpatch(ui, originalhunks, match, operation)
422 return patch.filterpatch(ui, originalhunks, match, operation)
423
423
424
424
425 def recordfilter(ui, originalhunks, match, operation=None):
425 def recordfilter(ui, originalhunks, match, operation=None):
426 """Prompts the user to filter the originalhunks and return a list of
426 """Prompts the user to filter the originalhunks and return a list of
427 selected hunks.
427 selected hunks.
428 *operation* is used for to build ui messages to indicate the user what
428 *operation* is used for to build ui messages to indicate the user what
429 kind of filtering they are doing: reverting, committing, shelving, etc.
429 kind of filtering they are doing: reverting, committing, shelving, etc.
430 (see patch.filterpatch).
430 (see patch.filterpatch).
431 """
431 """
432 usecurses = crecordmod.checkcurses(ui)
432 usecurses = crecordmod.checkcurses(ui)
433 testfile = ui.config(b'experimental', b'crecordtest')
433 testfile = ui.config(b'experimental', b'crecordtest')
434 oldwrite = setupwrapcolorwrite(ui)
434 oldwrite = setupwrapcolorwrite(ui)
435 try:
435 try:
436 newchunks, newopts = filterchunks(
436 newchunks, newopts = filterchunks(
437 ui, originalhunks, usecurses, testfile, match, operation
437 ui, originalhunks, usecurses, testfile, match, operation
438 )
438 )
439 finally:
439 finally:
440 ui.write = oldwrite
440 ui.write = oldwrite
441 return newchunks, newopts
441 return newchunks, newopts
442
442
443
443
444 def _record(
444 def _record(
445 ui,
445 ui,
446 repo,
446 repo,
447 message,
447 message,
448 match,
448 match,
449 opts,
449 opts,
450 commitfunc,
450 commitfunc,
451 backupall,
451 backupall,
452 filterfn,
452 filterfn,
453 pats,
453 pats,
454 ):
454 ):
455 """This is generic record driver.
455 """This is generic record driver.
456
456
457 Its job is to interactively filter local changes, and
457 Its job is to interactively filter local changes, and
458 accordingly prepare working directory into a state in which the
458 accordingly prepare working directory into a state in which the
459 job can be delegated to a non-interactive commit command such as
459 job can be delegated to a non-interactive commit command such as
460 'commit' or 'qrefresh'.
460 'commit' or 'qrefresh'.
461
461
462 After the actual job is done by non-interactive command, the
462 After the actual job is done by non-interactive command, the
463 working directory is restored to its original state.
463 working directory is restored to its original state.
464
464
465 In the end we'll record interesting changes, and everything else
465 In the end we'll record interesting changes, and everything else
466 will be left in place, so the user can continue working.
466 will be left in place, so the user can continue working.
467 """
467 """
468 assert repo.currentwlock() is not None
468 assert repo.currentwlock() is not None
469 if not opts.get(b'interactive-unshelve'):
469 if not opts.get(b'interactive-unshelve'):
470 checkunfinished(repo, commit=True)
470 checkunfinished(repo, commit=True)
471 wctx = repo[None]
471 wctx = repo[None]
472 merge = len(wctx.parents()) > 1
472 merge = len(wctx.parents()) > 1
473 if merge:
473 if merge:
474 raise error.InputError(
474 raise error.InputError(
475 _(b'cannot partially commit a merge ' b'(use "hg commit" instead)')
475 _(b'cannot partially commit a merge ' b'(use "hg commit" instead)')
476 )
476 )
477
477
478 def fail(f, msg):
478 def fail(f, msg):
479 raise error.InputError(b'%s: %s' % (f, msg))
479 raise error.InputError(b'%s: %s' % (f, msg))
480
480
481 force = opts.get(b'force')
481 force = opts.get(b'force')
482 if not force:
482 if not force:
483 match = matchmod.badmatch(match, fail)
483 match = matchmod.badmatch(match, fail)
484
484
485 status = repo.status(match=match)
485 status = repo.status(match=match)
486
486
487 overrides = {(b'ui', b'commitsubrepos'): True}
487 overrides = {(b'ui', b'commitsubrepos'): True}
488
488
489 with repo.ui.configoverride(overrides, b'record'):
489 with repo.ui.configoverride(overrides, b'record'):
490 # subrepoutil.precommit() modifies the status
490 # subrepoutil.precommit() modifies the status
491 tmpstatus = scmutil.status(
491 tmpstatus = scmutil.status(
492 copymod.copy(status.modified),
492 copymod.copy(status.modified),
493 copymod.copy(status.added),
493 copymod.copy(status.added),
494 copymod.copy(status.removed),
494 copymod.copy(status.removed),
495 copymod.copy(status.deleted),
495 copymod.copy(status.deleted),
496 copymod.copy(status.unknown),
496 copymod.copy(status.unknown),
497 copymod.copy(status.ignored),
497 copymod.copy(status.ignored),
498 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
498 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
499 )
499 )
500
500
501 # Force allows -X subrepo to skip the subrepo.
501 # Force allows -X subrepo to skip the subrepo.
502 subs, commitsubs, newstate = subrepoutil.precommit(
502 subs, commitsubs, newstate = subrepoutil.precommit(
503 repo.ui, wctx, tmpstatus, match, force=True
503 repo.ui, wctx, tmpstatus, match, force=True
504 )
504 )
505 for s in subs:
505 for s in subs:
506 if s in commitsubs:
506 if s in commitsubs:
507 dirtyreason = wctx.sub(s).dirtyreason(True)
507 dirtyreason = wctx.sub(s).dirtyreason(True)
508 raise error.Abort(dirtyreason)
508 raise error.Abort(dirtyreason)
509
509
510 if not force:
510 if not force:
511 repo.checkcommitpatterns(wctx, match, status, fail)
511 repo.checkcommitpatterns(wctx, match, status, fail)
512 diffopts = patch.difffeatureopts(
512 diffopts = patch.difffeatureopts(
513 ui,
513 ui,
514 opts=opts,
514 opts=opts,
515 whitespace=True,
515 whitespace=True,
516 section=b'commands',
516 section=b'commands',
517 configprefix=b'commit.interactive.',
517 configprefix=b'commit.interactive.',
518 )
518 )
519 diffopts.nodates = True
519 diffopts.nodates = True
520 diffopts.git = True
520 diffopts.git = True
521 diffopts.showfunc = True
521 diffopts.showfunc = True
522 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
522 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
523 original_headers = patch.parsepatch(originaldiff)
523 original_headers = patch.parsepatch(originaldiff)
524 match = scmutil.match(repo[None], pats)
524 match = scmutil.match(repo[None], pats)
525
525
526 # 1. filter patch, since we are intending to apply subset of it
526 # 1. filter patch, since we are intending to apply subset of it
527 try:
527 try:
528 chunks, newopts = filterfn(ui, original_headers, match)
528 chunks, newopts = filterfn(ui, original_headers, match)
529 except error.PatchParseError as err:
529 except error.PatchParseError as err:
530 raise error.InputError(_(b'error parsing patch: %s') % err)
530 raise error.InputError(_(b'error parsing patch: %s') % err)
531 except error.PatchApplicationError as err:
531 except error.PatchApplicationError as err:
532 raise error.StateError(_(b'error applying patch: %s') % err)
532 raise error.StateError(_(b'error applying patch: %s') % err)
533 opts.update(newopts)
533 opts.update(newopts)
534
534
535 # We need to keep a backup of files that have been newly added and
535 # We need to keep a backup of files that have been newly added and
536 # modified during the recording process because there is a previous
536 # modified during the recording process because there is a previous
537 # version without the edit in the workdir. We also will need to restore
537 # version without the edit in the workdir. We also will need to restore
538 # files that were the sources of renames so that the patch application
538 # files that were the sources of renames so that the patch application
539 # works.
539 # works.
540 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks)
540 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks)
541 contenders = set()
541 contenders = set()
542 for h in chunks:
542 for h in chunks:
543 if isheader(h):
543 if isheader(h):
544 contenders.update(set(h.files()))
544 contenders.update(set(h.files()))
545
545
546 changed = status.modified + status.added + status.removed
546 changed = status.modified + status.added + status.removed
547 newfiles = [f for f in changed if f in contenders]
547 newfiles = [f for f in changed if f in contenders]
548 if not newfiles:
548 if not newfiles:
549 ui.status(_(b'no changes to record\n'))
549 ui.status(_(b'no changes to record\n'))
550 return 0
550 return 0
551
551
552 modified = set(status.modified)
552 modified = set(status.modified)
553
553
554 # 2. backup changed files, so we can restore them in the end
554 # 2. backup changed files, so we can restore them in the end
555
555
556 if backupall:
556 if backupall:
557 tobackup = changed
557 tobackup = changed
558 else:
558 else:
559 tobackup = [
559 tobackup = [
560 f
560 f
561 for f in newfiles
561 for f in newfiles
562 if f in modified or f in newlyaddedandmodifiedfiles
562 if f in modified or f in newlyaddedandmodifiedfiles
563 ]
563 ]
564 backups = {}
564 backups = {}
565 if tobackup:
565 if tobackup:
566 backupdir = repo.vfs.join(b'record-backups')
566 backupdir = repo.vfs.join(b'record-backups')
567 try:
567 try:
568 os.mkdir(backupdir)
568 os.mkdir(backupdir)
569 except FileExistsError:
569 except FileExistsError:
570 pass
570 pass
571 try:
571 try:
572 # backup continues
572 # backup continues
573 for f in tobackup:
573 for f in tobackup:
574 fd, tmpname = pycompat.mkstemp(
574 fd, tmpname = pycompat.mkstemp(
575 prefix=os.path.basename(f) + b'.', dir=backupdir
575 prefix=os.path.basename(f) + b'.', dir=backupdir
576 )
576 )
577 os.close(fd)
577 os.close(fd)
578 ui.debug(b'backup %r as %r\n' % (f, tmpname))
578 ui.debug(b'backup %r as %r\n' % (f, tmpname))
579 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
579 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
580 backups[f] = tmpname
580 backups[f] = tmpname
581
581
582 fp = stringio()
582 fp = stringio()
583 for c in chunks:
583 for c in chunks:
584 fname = c.filename()
584 fname = c.filename()
585 if fname in backups:
585 if fname in backups:
586 c.write(fp)
586 c.write(fp)
587 dopatch = fp.tell()
587 dopatch = fp.tell()
588 fp.seek(0)
588 fp.seek(0)
589
589
590 # 2.5 optionally review / modify patch in text editor
590 # 2.5 optionally review / modify patch in text editor
591 if opts.get(b'review', False):
591 if opts.get(b'review', False):
592 patchtext = (
592 patchtext = (
593 crecordmod.diffhelptext + crecordmod.patchhelptext + fp.read()
593 crecordmod.diffhelptext + crecordmod.patchhelptext + fp.read()
594 )
594 )
595 reviewedpatch = ui.edit(
595 reviewedpatch = ui.edit(
596 patchtext, b"", action=b"diff", repopath=repo.path
596 patchtext, b"", action=b"diff", repopath=repo.path
597 )
597 )
598 fp.truncate(0)
598 fp.truncate(0)
599 fp.write(reviewedpatch)
599 fp.write(reviewedpatch)
600 fp.seek(0)
600 fp.seek(0)
601
601
602 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
602 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
603 # 3a. apply filtered patch to clean repo (clean)
603 # 3a. apply filtered patch to clean repo (clean)
604 if backups:
604 if backups:
605 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
605 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
606 mergemod.revert_to(repo[b'.'], matcher=m)
606 mergemod.revert_to(repo[b'.'], matcher=m)
607
607
608 # 3b. (apply)
608 # 3b. (apply)
609 if dopatch:
609 if dopatch:
610 try:
610 try:
611 ui.debug(b'applying patch\n')
611 ui.debug(b'applying patch\n')
612 ui.debug(fp.getvalue())
612 ui.debug(fp.getvalue())
613 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
613 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
614 except error.PatchParseError as err:
614 except error.PatchParseError as err:
615 raise error.InputError(pycompat.bytestr(err))
615 raise error.InputError(pycompat.bytestr(err))
616 except error.PatchApplicationError as err:
616 except error.PatchApplicationError as err:
617 raise error.StateError(pycompat.bytestr(err))
617 raise error.StateError(pycompat.bytestr(err))
618 del fp
618 del fp
619
619
620 # 4. We prepared working directory according to filtered
620 # 4. We prepared working directory according to filtered
621 # patch. Now is the time to delegate the job to
621 # patch. Now is the time to delegate the job to
622 # commit/qrefresh or the like!
622 # commit/qrefresh or the like!
623
623
624 # Make all of the pathnames absolute.
624 # Make all of the pathnames absolute.
625 newfiles = [repo.wjoin(nf) for nf in newfiles]
625 newfiles = [repo.wjoin(nf) for nf in newfiles]
626 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
626 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
627 finally:
627 finally:
628 # 5. finally restore backed-up files
628 # 5. finally restore backed-up files
629 try:
629 try:
630 dirstate = repo.dirstate
630 dirstate = repo.dirstate
631 for realname, tmpname in backups.items():
631 for realname, tmpname in backups.items():
632 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
632 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
633
633
634 if dirstate.get_entry(realname).maybe_clean:
634 if dirstate.get_entry(realname).maybe_clean:
635 # without normallookup, restoring timestamp
635 # without normallookup, restoring timestamp
636 # may cause partially committed files
636 # may cause partially committed files
637 # to be treated as unmodified
637 # to be treated as unmodified
638
638
639 # XXX-PENDINGCHANGE: We should clarify the context in
639 # XXX-PENDINGCHANGE: We should clarify the context in
640 # which this function is called to make sure it
640 # which this function is called to make sure it
641 # already called within a `pendingchange`, However we
641 # already called within a `pendingchange`, However we
642 # are taking a shortcut here in order to be able to
642 # are taking a shortcut here in order to be able to
643 # quickly deprecated the older API.
643 # quickly deprecated the older API.
644 with dirstate.changing_parents(repo):
644 with dirstate.changing_parents(repo):
645 dirstate.update_file(
645 dirstate.update_file(
646 realname,
646 realname,
647 p1_tracked=True,
647 p1_tracked=True,
648 wc_tracked=True,
648 wc_tracked=True,
649 possibly_dirty=True,
649 possibly_dirty=True,
650 )
650 )
651
651
652 # copystat=True here and above are a hack to trick any
652 # copystat=True here and above are a hack to trick any
653 # editors that have f open that we haven't modified them.
653 # editors that have f open that we haven't modified them.
654 #
654 #
655 # Also note that this racy as an editor could notice the
655 # Also note that this racy as an editor could notice the
656 # file's mtime before we've finished writing it.
656 # file's mtime before we've finished writing it.
657 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
657 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
658 os.unlink(tmpname)
658 os.unlink(tmpname)
659 if tobackup:
659 if tobackup:
660 os.rmdir(backupdir)
660 os.rmdir(backupdir)
661 except OSError:
661 except OSError:
662 pass
662 pass
663
663
664
664
665 def dorecord(
665 def dorecord(
666 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
666 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
667 ):
667 ):
668 opts = pycompat.byteskwargs(opts)
668 opts = pycompat.byteskwargs(opts)
669 if not ui.interactive():
669 if not ui.interactive():
670 if cmdsuggest:
670 if cmdsuggest:
671 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
671 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
672 else:
672 else:
673 msg = _(b'running non-interactively')
673 msg = _(b'running non-interactively')
674 raise error.InputError(msg)
674 raise error.InputError(msg)
675
675
676 # make sure username is set before going interactive
676 # make sure username is set before going interactive
677 if not opts.get(b'user'):
677 if not opts.get(b'user'):
678 ui.username() # raise exception, username not provided
678 ui.username() # raise exception, username not provided
679
679
680 func = functools.partial(
680 func = functools.partial(
681 _record,
681 _record,
682 commitfunc=commitfunc,
682 commitfunc=commitfunc,
683 backupall=backupall,
683 backupall=backupall,
684 filterfn=filterfn,
684 filterfn=filterfn,
685 pats=pats,
685 pats=pats,
686 )
686 )
687
687
688 return commit(ui, repo, func, pats, opts)
688 return commit(ui, repo, func, pats, opts)
689
689
690
690
691 class dirnode:
691 class dirnode:
692 """
692 """
693 Represent a directory in user working copy with information required for
693 Represent a directory in user working copy with information required for
694 the purpose of tersing its status.
694 the purpose of tersing its status.
695
695
696 path is the path to the directory, without a trailing '/'
696 path is the path to the directory, without a trailing '/'
697
697
698 statuses is a set of statuses of all files in this directory (this includes
698 statuses is a set of statuses of all files in this directory (this includes
699 all the files in all the subdirectories too)
699 all the files in all the subdirectories too)
700
700
701 files is a list of files which are direct child of this directory
701 files is a list of files which are direct child of this directory
702
702
703 subdirs is a dictionary of sub-directory name as the key and it's own
703 subdirs is a dictionary of sub-directory name as the key and it's own
704 dirnode object as the value
704 dirnode object as the value
705 """
705 """
706
706
707 def __init__(self, dirpath):
707 def __init__(self, dirpath):
708 self.path = dirpath
708 self.path = dirpath
709 self.statuses = set()
709 self.statuses = set()
710 self.files = []
710 self.files = []
711 self.subdirs = {}
711 self.subdirs = {}
712
712
713 def _addfileindir(self, filename, status):
713 def _addfileindir(self, filename, status):
714 """Add a file in this directory as a direct child."""
714 """Add a file in this directory as a direct child."""
715 self.files.append((filename, status))
715 self.files.append((filename, status))
716
716
717 def addfile(self, filename, status):
717 def addfile(self, filename, status):
718 """
718 """
719 Add a file to this directory or to its direct parent directory.
719 Add a file to this directory or to its direct parent directory.
720
720
721 If the file is not direct child of this directory, we traverse to the
721 If the file is not direct child of this directory, we traverse to the
722 directory of which this file is a direct child of and add the file
722 directory of which this file is a direct child of and add the file
723 there.
723 there.
724 """
724 """
725
725
726 # the filename contains a path separator, it means it's not the direct
726 # the filename contains a path separator, it means it's not the direct
727 # child of this directory
727 # child of this directory
728 if b'/' in filename:
728 if b'/' in filename:
729 subdir, filep = filename.split(b'/', 1)
729 subdir, filep = filename.split(b'/', 1)
730
730
731 # does the dirnode object for subdir exists
731 # does the dirnode object for subdir exists
732 if subdir not in self.subdirs:
732 if subdir not in self.subdirs:
733 subdirpath = pathutil.join(self.path, subdir)
733 subdirpath = pathutil.join(self.path, subdir)
734 self.subdirs[subdir] = dirnode(subdirpath)
734 self.subdirs[subdir] = dirnode(subdirpath)
735
735
736 # try adding the file in subdir
736 # try adding the file in subdir
737 self.subdirs[subdir].addfile(filep, status)
737 self.subdirs[subdir].addfile(filep, status)
738
738
739 else:
739 else:
740 self._addfileindir(filename, status)
740 self._addfileindir(filename, status)
741
741
742 if status not in self.statuses:
742 if status not in self.statuses:
743 self.statuses.add(status)
743 self.statuses.add(status)
744
744
745 def iterfilepaths(self):
745 def iterfilepaths(self):
746 """Yield (status, path) for files directly under this directory."""
746 """Yield (status, path) for files directly under this directory."""
747 for f, st in self.files:
747 for f, st in self.files:
748 yield st, pathutil.join(self.path, f)
748 yield st, pathutil.join(self.path, f)
749
749
750 def tersewalk(self, terseargs):
750 def tersewalk(self, terseargs):
751 """
751 """
752 Yield (status, path) obtained by processing the status of this
752 Yield (status, path) obtained by processing the status of this
753 dirnode.
753 dirnode.
754
754
755 terseargs is the string of arguments passed by the user with `--terse`
755 terseargs is the string of arguments passed by the user with `--terse`
756 flag.
756 flag.
757
757
758 Following are the cases which can happen:
758 Following are the cases which can happen:
759
759
760 1) All the files in the directory (including all the files in its
760 1) All the files in the directory (including all the files in its
761 subdirectories) share the same status and the user has asked us to terse
761 subdirectories) share the same status and the user has asked us to terse
762 that status. -> yield (status, dirpath). dirpath will end in '/'.
762 that status. -> yield (status, dirpath). dirpath will end in '/'.
763
763
764 2) Otherwise, we do following:
764 2) Otherwise, we do following:
765
765
766 a) Yield (status, filepath) for all the files which are in this
766 a) Yield (status, filepath) for all the files which are in this
767 directory (only the ones in this directory, not the subdirs)
767 directory (only the ones in this directory, not the subdirs)
768
768
769 b) Recurse the function on all the subdirectories of this
769 b) Recurse the function on all the subdirectories of this
770 directory
770 directory
771 """
771 """
772
772
773 if len(self.statuses) == 1:
773 if len(self.statuses) == 1:
774 onlyst = self.statuses.pop()
774 onlyst = self.statuses.pop()
775
775
776 # Making sure we terse only when the status abbreviation is
776 # Making sure we terse only when the status abbreviation is
777 # passed as terse argument
777 # passed as terse argument
778 if onlyst in terseargs:
778 if onlyst in terseargs:
779 yield onlyst, self.path + b'/'
779 yield onlyst, self.path + b'/'
780 return
780 return
781
781
782 # add the files to status list
782 # add the files to status list
783 for st, fpath in self.iterfilepaths():
783 for st, fpath in self.iterfilepaths():
784 yield st, fpath
784 yield st, fpath
785
785
786 # recurse on the subdirs
786 # recurse on the subdirs
787 for dirobj in self.subdirs.values():
787 for dirobj in self.subdirs.values():
788 for st, fpath in dirobj.tersewalk(terseargs):
788 for st, fpath in dirobj.tersewalk(terseargs):
789 yield st, fpath
789 yield st, fpath
790
790
791
791
792 def tersedir(statuslist, terseargs):
792 def tersedir(statuslist, terseargs):
793 """
793 """
794 Terse the status if all the files in a directory shares the same status.
794 Terse the status if all the files in a directory shares the same status.
795
795
796 statuslist is scmutil.status() object which contains a list of files for
796 statuslist is scmutil.status() object which contains a list of files for
797 each status.
797 each status.
798 terseargs is string which is passed by the user as the argument to `--terse`
798 terseargs is string which is passed by the user as the argument to `--terse`
799 flag.
799 flag.
800
800
801 The function makes a tree of objects of dirnode class, and at each node it
801 The function makes a tree of objects of dirnode class, and at each node it
802 stores the information required to know whether we can terse a certain
802 stores the information required to know whether we can terse a certain
803 directory or not.
803 directory or not.
804 """
804 """
805 # the order matters here as that is used to produce final list
805 # the order matters here as that is used to produce final list
806 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
806 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
807
807
808 # checking the argument validity
808 # checking the argument validity
809 for s in pycompat.bytestr(terseargs):
809 for s in pycompat.bytestr(terseargs):
810 if s not in allst:
810 if s not in allst:
811 raise error.InputError(_(b"'%s' not recognized") % s)
811 raise error.InputError(_(b"'%s' not recognized") % s)
812
812
813 # creating a dirnode object for the root of the repo
813 # creating a dirnode object for the root of the repo
814 rootobj = dirnode(b'')
814 rootobj = dirnode(b'')
815 pstatus = (
815 pstatus = (
816 b'modified',
816 b'modified',
817 b'added',
817 b'added',
818 b'deleted',
818 b'deleted',
819 b'clean',
819 b'clean',
820 b'unknown',
820 b'unknown',
821 b'ignored',
821 b'ignored',
822 b'removed',
822 b'removed',
823 )
823 )
824
824
825 tersedict = {}
825 tersedict = {}
826 for attrname in pstatus:
826 for attrname in pstatus:
827 statuschar = attrname[0:1]
827 statuschar = attrname[0:1]
828 for f in getattr(statuslist, attrname):
828 for f in getattr(statuslist, attrname):
829 rootobj.addfile(f, statuschar)
829 rootobj.addfile(f, statuschar)
830 tersedict[statuschar] = []
830 tersedict[statuschar] = []
831
831
832 # we won't be tersing the root dir, so add files in it
832 # we won't be tersing the root dir, so add files in it
833 for st, fpath in rootobj.iterfilepaths():
833 for st, fpath in rootobj.iterfilepaths():
834 tersedict[st].append(fpath)
834 tersedict[st].append(fpath)
835
835
836 # process each sub-directory and build tersedict
836 # process each sub-directory and build tersedict
837 for subdir in rootobj.subdirs.values():
837 for subdir in rootobj.subdirs.values():
838 for st, f in subdir.tersewalk(terseargs):
838 for st, f in subdir.tersewalk(terseargs):
839 tersedict[st].append(f)
839 tersedict[st].append(f)
840
840
841 tersedlist = []
841 tersedlist = []
842 for st in allst:
842 for st in allst:
843 tersedict[st].sort()
843 tersedict[st].sort()
844 tersedlist.append(tersedict[st])
844 tersedlist.append(tersedict[st])
845
845
846 return scmutil.status(*tersedlist)
846 return scmutil.status(*tersedlist)
847
847
848
848
849 def _commentlines(raw):
849 def _commentlines(raw):
850 '''Surround lineswith a comment char and a new line'''
850 '''Surround lineswith a comment char and a new line'''
851 lines = raw.splitlines()
851 lines = raw.splitlines()
852 commentedlines = [b'# %s' % line for line in lines]
852 commentedlines = [b'# %s' % line for line in lines]
853 return b'\n'.join(commentedlines) + b'\n'
853 return b'\n'.join(commentedlines) + b'\n'
854
854
855
855
856 @attr.s(frozen=True)
856 @attr.s(frozen=True)
857 class morestatus:
857 class morestatus:
858 repo = attr.ib()
858 repo = attr.ib()
859 unfinishedop = attr.ib()
859 unfinishedop = attr.ib()
860 unfinishedmsg = attr.ib()
860 unfinishedmsg = attr.ib()
861 activemerge = attr.ib()
861 activemerge = attr.ib()
862 unresolvedpaths = attr.ib()
862 unresolvedpaths = attr.ib()
863 _formattedpaths = attr.ib(init=False, default=set())
863 _formattedpaths = attr.ib(init=False, default=set())
864 _label = b'status.morestatus'
864 _label = b'status.morestatus'
865
865
866 def formatfile(self, path, fm):
866 def formatfile(self, path, fm):
867 self._formattedpaths.add(path)
867 self._formattedpaths.add(path)
868 if self.activemerge and path in self.unresolvedpaths:
868 if self.activemerge and path in self.unresolvedpaths:
869 fm.data(unresolved=True)
869 fm.data(unresolved=True)
870
870
871 def formatfooter(self, fm):
871 def formatfooter(self, fm):
872 if self.unfinishedop or self.unfinishedmsg:
872 if self.unfinishedop or self.unfinishedmsg:
873 fm.startitem()
873 fm.startitem()
874 fm.data(itemtype=b'morestatus')
874 fm.data(itemtype=b'morestatus')
875
875
876 if self.unfinishedop:
876 if self.unfinishedop:
877 fm.data(unfinished=self.unfinishedop)
877 fm.data(unfinished=self.unfinishedop)
878 statemsg = (
878 statemsg = (
879 _(b'The repository is in an unfinished *%s* state.')
879 _(b'The repository is in an unfinished *%s* state.')
880 % self.unfinishedop
880 % self.unfinishedop
881 )
881 )
882 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
882 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
883 if self.unfinishedmsg:
883 if self.unfinishedmsg:
884 fm.data(unfinishedmsg=self.unfinishedmsg)
884 fm.data(unfinishedmsg=self.unfinishedmsg)
885
885
886 # May also start new data items.
886 # May also start new data items.
887 self._formatconflicts(fm)
887 self._formatconflicts(fm)
888
888
889 if self.unfinishedmsg:
889 if self.unfinishedmsg:
890 fm.plain(
890 fm.plain(
891 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
891 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
892 )
892 )
893
893
894 def _formatconflicts(self, fm):
894 def _formatconflicts(self, fm):
895 if not self.activemerge:
895 if not self.activemerge:
896 return
896 return
897
897
898 if self.unresolvedpaths:
898 if self.unresolvedpaths:
899 mergeliststr = b'\n'.join(
899 mergeliststr = b'\n'.join(
900 [
900 [
901 b' %s'
901 b' %s'
902 % util.pathto(self.repo.root, encoding.getcwd(), path)
902 % util.pathto(self.repo.root, encoding.getcwd(), path)
903 for path in self.unresolvedpaths
903 for path in self.unresolvedpaths
904 ]
904 ]
905 )
905 )
906 msg = (
906 msg = (
907 _(
907 _(
908 b'''Unresolved merge conflicts:
908 b'''Unresolved merge conflicts:
909
909
910 %s
910 %s
911
911
912 To mark files as resolved: hg resolve --mark FILE'''
912 To mark files as resolved: hg resolve --mark FILE'''
913 )
913 )
914 % mergeliststr
914 % mergeliststr
915 )
915 )
916
916
917 # If any paths with unresolved conflicts were not previously
917 # If any paths with unresolved conflicts were not previously
918 # formatted, output them now.
918 # formatted, output them now.
919 for f in self.unresolvedpaths:
919 for f in self.unresolvedpaths:
920 if f in self._formattedpaths:
920 if f in self._formattedpaths:
921 # Already output.
921 # Already output.
922 continue
922 continue
923 fm.startitem()
923 fm.startitem()
924 fm.context(repo=self.repo)
924 fm.context(repo=self.repo)
925 # We can't claim to know the status of the file - it may just
925 # We can't claim to know the status of the file - it may just
926 # have been in one of the states that were not requested for
926 # have been in one of the states that were not requested for
927 # display, so it could be anything.
927 # display, so it could be anything.
928 fm.data(itemtype=b'file', path=f, unresolved=True)
928 fm.data(itemtype=b'file', path=f, unresolved=True)
929
929
930 else:
930 else:
931 msg = _(b'No unresolved merge conflicts.')
931 msg = _(b'No unresolved merge conflicts.')
932
932
933 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
933 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
934
934
935
935
936 def readmorestatus(repo):
936 def readmorestatus(repo):
937 """Returns a morestatus object if the repo has unfinished state."""
937 """Returns a morestatus object if the repo has unfinished state."""
938 statetuple = statemod.getrepostate(repo)
938 statetuple = statemod.getrepostate(repo)
939 mergestate = mergestatemod.mergestate.read(repo)
939 mergestate = mergestatemod.mergestate.read(repo)
940 activemerge = mergestate.active()
940 activemerge = mergestate.active()
941 if not statetuple and not activemerge:
941 if not statetuple and not activemerge:
942 return None
942 return None
943
943
944 unfinishedop = unfinishedmsg = unresolved = None
944 unfinishedop = unfinishedmsg = unresolved = None
945 if statetuple:
945 if statetuple:
946 unfinishedop, unfinishedmsg = statetuple
946 unfinishedop, unfinishedmsg = statetuple
947 if activemerge:
947 if activemerge:
948 unresolved = sorted(mergestate.unresolved())
948 unresolved = sorted(mergestate.unresolved())
949 return morestatus(
949 return morestatus(
950 repo, unfinishedop, unfinishedmsg, activemerge, unresolved
950 repo, unfinishedop, unfinishedmsg, activemerge, unresolved
951 )
951 )
952
952
953
953
954 def findpossible(cmd, table, strict=False):
954 def findpossible(cmd, table, strict=False):
955 """
955 """
956 Return cmd -> (aliases, command table entry)
956 Return cmd -> (aliases, command table entry)
957 for each matching command.
957 for each matching command.
958 Return debug commands (or their aliases) only if no normal command matches.
958 Return debug commands (or their aliases) only if no normal command matches.
959 """
959 """
960 choice = {}
960 choice = {}
961 debugchoice = {}
961 debugchoice = {}
962
962
963 if cmd in table:
963 if cmd in table:
964 # short-circuit exact matches, "log" alias beats "log|history"
964 # short-circuit exact matches, "log" alias beats "log|history"
965 keys = [cmd]
965 keys = [cmd]
966 else:
966 else:
967 keys = table.keys()
967 keys = table.keys()
968
968
969 allcmds = []
969 allcmds = []
970 for e in keys:
970 for e in keys:
971 aliases = parsealiases(e)
971 aliases = parsealiases(e)
972 allcmds.extend(aliases)
972 allcmds.extend(aliases)
973 found = None
973 found = None
974 if cmd in aliases:
974 if cmd in aliases:
975 found = cmd
975 found = cmd
976 elif not strict:
976 elif not strict:
977 for a in aliases:
977 for a in aliases:
978 if a.startswith(cmd):
978 if a.startswith(cmd):
979 found = a
979 found = a
980 break
980 break
981 if found is not None:
981 if found is not None:
982 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
982 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
983 debugchoice[found] = (aliases, table[e])
983 debugchoice[found] = (aliases, table[e])
984 else:
984 else:
985 choice[found] = (aliases, table[e])
985 choice[found] = (aliases, table[e])
986
986
987 if not choice and debugchoice:
987 if not choice and debugchoice:
988 choice = debugchoice
988 choice = debugchoice
989
989
990 return choice, allcmds
990 return choice, allcmds
991
991
992
992
993 def findcmd(cmd, table, strict=True):
993 def findcmd(cmd, table, strict=True):
994 """Return (aliases, command table entry) for command string."""
994 """Return (aliases, command table entry) for command string."""
995 choice, allcmds = findpossible(cmd, table, strict)
995 choice, allcmds = findpossible(cmd, table, strict)
996
996
997 if cmd in choice:
997 if cmd in choice:
998 return choice[cmd]
998 return choice[cmd]
999
999
1000 if len(choice) > 1:
1000 if len(choice) > 1:
1001 clist = sorted(choice)
1001 clist = sorted(choice)
1002 raise error.AmbiguousCommand(cmd, clist)
1002 raise error.AmbiguousCommand(cmd, clist)
1003
1003
1004 if choice:
1004 if choice:
1005 return list(choice.values())[0]
1005 return list(choice.values())[0]
1006
1006
1007 raise error.UnknownCommand(cmd, allcmds)
1007 raise error.UnknownCommand(cmd, allcmds)
1008
1008
1009
1009
1010 def changebranch(ui, repo, revs, label, opts):
1010 def changebranch(ui, repo, revs, label, **opts):
1011 """Change the branch name of given revs to label"""
1011 """Change the branch name of given revs to label"""
1012
1012
1013 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
1013 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
1014 # abort in case of uncommitted merge or dirty wdir
1014 # abort in case of uncommitted merge or dirty wdir
1015 bailifchanged(repo)
1015 bailifchanged(repo)
1016 revs = logcmdutil.revrange(repo, revs)
1016 revs = logcmdutil.revrange(repo, revs)
1017 if not revs:
1017 if not revs:
1018 raise error.InputError(b"empty revision set")
1018 raise error.InputError(b"empty revision set")
1019 roots = repo.revs(b'roots(%ld)', revs)
1019 roots = repo.revs(b'roots(%ld)', revs)
1020 if len(roots) > 1:
1020 if len(roots) > 1:
1021 raise error.InputError(
1021 raise error.InputError(
1022 _(b"cannot change branch of non-linear revisions")
1022 _(b"cannot change branch of non-linear revisions")
1023 )
1023 )
1024 rewriteutil.precheck(repo, revs, b'change branch of')
1024 rewriteutil.precheck(repo, revs, b'change branch of')
1025
1025
1026 root = repo[roots.first()]
1026 root = repo[roots.first()]
1027 rpb = {parent.branch() for parent in root.parents()}
1027 rpb = {parent.branch() for parent in root.parents()}
1028 if (
1028 if (
1029 not opts.get(b'force')
1029 not opts.get('force')
1030 and label not in rpb
1030 and label not in rpb
1031 and label in repo.branchmap()
1031 and label in repo.branchmap()
1032 ):
1032 ):
1033 raise error.InputError(
1033 raise error.InputError(
1034 _(b"a branch of the same name already exists")
1034 _(b"a branch of the same name already exists")
1035 )
1035 )
1036
1036
1037 # make sure only topological heads
1037 # make sure only topological heads
1038 if repo.revs(b'heads(%ld) - head()', revs):
1038 if repo.revs(b'heads(%ld) - head()', revs):
1039 raise error.InputError(
1039 raise error.InputError(
1040 _(b"cannot change branch in middle of a stack")
1040 _(b"cannot change branch in middle of a stack")
1041 )
1041 )
1042
1042
1043 replacements = {}
1043 replacements = {}
1044 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1044 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1045 # mercurial.subrepo -> mercurial.cmdutil
1045 # mercurial.subrepo -> mercurial.cmdutil
1046 from . import context
1046 from . import context
1047
1047
1048 for rev in revs:
1048 for rev in revs:
1049 ctx = repo[rev]
1049 ctx = repo[rev]
1050 oldbranch = ctx.branch()
1050 oldbranch = ctx.branch()
1051 # check if ctx has same branch
1051 # check if ctx has same branch
1052 if oldbranch == label:
1052 if oldbranch == label:
1053 continue
1053 continue
1054
1054
1055 def filectxfn(repo, newctx, path):
1055 def filectxfn(repo, newctx, path):
1056 try:
1056 try:
1057 return ctx[path]
1057 return ctx[path]
1058 except error.ManifestLookupError:
1058 except error.ManifestLookupError:
1059 return None
1059 return None
1060
1060
1061 ui.debug(
1061 ui.debug(
1062 b"changing branch of '%s' from '%s' to '%s'\n"
1062 b"changing branch of '%s' from '%s' to '%s'\n"
1063 % (hex(ctx.node()), oldbranch, label)
1063 % (hex(ctx.node()), oldbranch, label)
1064 )
1064 )
1065 extra = ctx.extra()
1065 extra = ctx.extra()
1066 extra[b'branch_change'] = hex(ctx.node())
1066 extra[b'branch_change'] = hex(ctx.node())
1067 # While changing branch of set of linear commits, make sure that
1067 # While changing branch of set of linear commits, make sure that
1068 # we base our commits on new parent rather than old parent which
1068 # we base our commits on new parent rather than old parent which
1069 # was obsoleted while changing the branch
1069 # was obsoleted while changing the branch
1070 p1 = ctx.p1().node()
1070 p1 = ctx.p1().node()
1071 p2 = ctx.p2().node()
1071 p2 = ctx.p2().node()
1072 if p1 in replacements:
1072 if p1 in replacements:
1073 p1 = replacements[p1][0]
1073 p1 = replacements[p1][0]
1074 if p2 in replacements:
1074 if p2 in replacements:
1075 p2 = replacements[p2][0]
1075 p2 = replacements[p2][0]
1076
1076
1077 mc = context.memctx(
1077 mc = context.memctx(
1078 repo,
1078 repo,
1079 (p1, p2),
1079 (p1, p2),
1080 ctx.description(),
1080 ctx.description(),
1081 ctx.files(),
1081 ctx.files(),
1082 filectxfn,
1082 filectxfn,
1083 user=ctx.user(),
1083 user=ctx.user(),
1084 date=ctx.date(),
1084 date=ctx.date(),
1085 extra=extra,
1085 extra=extra,
1086 branch=label,
1086 branch=label,
1087 )
1087 )
1088
1088
1089 newnode = repo.commitctx(mc)
1089 newnode = repo.commitctx(mc)
1090 replacements[ctx.node()] = (newnode,)
1090 replacements[ctx.node()] = (newnode,)
1091 ui.debug(b'new node id is %s\n' % hex(newnode))
1091 ui.debug(b'new node id is %s\n' % hex(newnode))
1092
1092
1093 # create obsmarkers and move bookmarks
1093 # create obsmarkers and move bookmarks
1094 scmutil.cleanupnodes(
1094 scmutil.cleanupnodes(
1095 repo, replacements, b'branch-change', fixphase=True
1095 repo, replacements, b'branch-change', fixphase=True
1096 )
1096 )
1097
1097
1098 # move the working copy too
1098 # move the working copy too
1099 wctx = repo[None]
1099 wctx = repo[None]
1100 # in-progress merge is a bit too complex for now.
1100 # in-progress merge is a bit too complex for now.
1101 if len(wctx.parents()) == 1:
1101 if len(wctx.parents()) == 1:
1102 newid = replacements.get(wctx.p1().node())
1102 newid = replacements.get(wctx.p1().node())
1103 if newid is not None:
1103 if newid is not None:
1104 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1104 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1105 # mercurial.cmdutil
1105 # mercurial.cmdutil
1106 from . import hg
1106 from . import hg
1107
1107
1108 hg.update(repo, newid[0], quietempty=True)
1108 hg.update(repo, newid[0], quietempty=True)
1109
1109
1110 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1110 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1111
1111
1112
1112
1113 def findrepo(p):
1113 def findrepo(p):
1114 while not os.path.isdir(os.path.join(p, b".hg")):
1114 while not os.path.isdir(os.path.join(p, b".hg")):
1115 oldp, p = p, os.path.dirname(p)
1115 oldp, p = p, os.path.dirname(p)
1116 if p == oldp:
1116 if p == oldp:
1117 return None
1117 return None
1118
1118
1119 return p
1119 return p
1120
1120
1121
1121
1122 def bailifchanged(repo, merge=True, hint=None):
1122 def bailifchanged(repo, merge=True, hint=None):
1123 """enforce the precondition that working directory must be clean.
1123 """enforce the precondition that working directory must be clean.
1124
1124
1125 'merge' can be set to false if a pending uncommitted merge should be
1125 'merge' can be set to false if a pending uncommitted merge should be
1126 ignored (such as when 'update --check' runs).
1126 ignored (such as when 'update --check' runs).
1127
1127
1128 'hint' is the usual hint given to Abort exception.
1128 'hint' is the usual hint given to Abort exception.
1129 """
1129 """
1130
1130
1131 if merge and repo.dirstate.p2() != repo.nullid:
1131 if merge and repo.dirstate.p2() != repo.nullid:
1132 raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
1132 raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
1133 st = repo.status()
1133 st = repo.status()
1134 if st.modified or st.added or st.removed or st.deleted:
1134 if st.modified or st.added or st.removed or st.deleted:
1135 raise error.StateError(_(b'uncommitted changes'), hint=hint)
1135 raise error.StateError(_(b'uncommitted changes'), hint=hint)
1136 ctx = repo[None]
1136 ctx = repo[None]
1137 for s in sorted(ctx.substate):
1137 for s in sorted(ctx.substate):
1138 ctx.sub(s).bailifchanged(hint=hint)
1138 ctx.sub(s).bailifchanged(hint=hint)
1139
1139
1140
1140
1141 def logmessage(ui: "uimod.ui", opts: Dict[bytes, Any]) -> Optional[bytes]:
1141 def logmessage(ui: "uimod.ui", opts: Dict[bytes, Any]) -> Optional[bytes]:
1142 """get the log message according to -m and -l option"""
1142 """get the log message according to -m and -l option"""
1143
1143
1144 check_at_most_one_arg(opts, b'message', b'logfile')
1144 check_at_most_one_arg(opts, b'message', b'logfile')
1145
1145
1146 message = cast(Optional[bytes], opts.get(b'message'))
1146 message = cast(Optional[bytes], opts.get(b'message'))
1147 logfile = opts.get(b'logfile')
1147 logfile = opts.get(b'logfile')
1148
1148
1149 if not message and logfile:
1149 if not message and logfile:
1150 try:
1150 try:
1151 if isstdiofilename(logfile):
1151 if isstdiofilename(logfile):
1152 message = ui.fin.read()
1152 message = ui.fin.read()
1153 else:
1153 else:
1154 message = b'\n'.join(util.readfile(logfile).splitlines())
1154 message = b'\n'.join(util.readfile(logfile).splitlines())
1155 except IOError as inst:
1155 except IOError as inst:
1156 raise error.Abort(
1156 raise error.Abort(
1157 _(b"can't read commit message '%s': %s")
1157 _(b"can't read commit message '%s': %s")
1158 % (logfile, encoding.strtolocal(inst.strerror))
1158 % (logfile, encoding.strtolocal(inst.strerror))
1159 )
1159 )
1160 return message
1160 return message
1161
1161
1162
1162
1163 def mergeeditform(ctxorbool, baseformname):
1163 def mergeeditform(ctxorbool, baseformname):
1164 """return appropriate editform name (referencing a committemplate)
1164 """return appropriate editform name (referencing a committemplate)
1165
1165
1166 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1166 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1167 merging is committed.
1167 merging is committed.
1168
1168
1169 This returns baseformname with '.merge' appended if it is a merge,
1169 This returns baseformname with '.merge' appended if it is a merge,
1170 otherwise '.normal' is appended.
1170 otherwise '.normal' is appended.
1171 """
1171 """
1172 if isinstance(ctxorbool, bool):
1172 if isinstance(ctxorbool, bool):
1173 if ctxorbool:
1173 if ctxorbool:
1174 return baseformname + b".merge"
1174 return baseformname + b".merge"
1175 elif len(ctxorbool.parents()) > 1:
1175 elif len(ctxorbool.parents()) > 1:
1176 return baseformname + b".merge"
1176 return baseformname + b".merge"
1177
1177
1178 return baseformname + b".normal"
1178 return baseformname + b".normal"
1179
1179
1180
1180
1181 def getcommiteditor(
1181 def getcommiteditor(
1182 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1182 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1183 ):
1183 ):
1184 """get appropriate commit message editor according to '--edit' option
1184 """get appropriate commit message editor according to '--edit' option
1185
1185
1186 'finishdesc' is a function to be called with edited commit message
1186 'finishdesc' is a function to be called with edited commit message
1187 (= 'description' of the new changeset) just after editing, but
1187 (= 'description' of the new changeset) just after editing, but
1188 before checking empty-ness. It should return actual text to be
1188 before checking empty-ness. It should return actual text to be
1189 stored into history. This allows to change description before
1189 stored into history. This allows to change description before
1190 storing.
1190 storing.
1191
1191
1192 'extramsg' is a extra message to be shown in the editor instead of
1192 'extramsg' is a extra message to be shown in the editor instead of
1193 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1193 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1194 is automatically added.
1194 is automatically added.
1195
1195
1196 'editform' is a dot-separated list of names, to distinguish
1196 'editform' is a dot-separated list of names, to distinguish
1197 the purpose of commit text editing.
1197 the purpose of commit text editing.
1198
1198
1199 'getcommiteditor' returns 'commitforceeditor' regardless of
1199 'getcommiteditor' returns 'commitforceeditor' regardless of
1200 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1200 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1201 they are specific for usage in MQ.
1201 they are specific for usage in MQ.
1202 """
1202 """
1203 if edit or finishdesc or extramsg:
1203 if edit or finishdesc or extramsg:
1204 return lambda r, c, s: commitforceeditor(
1204 return lambda r, c, s: commitforceeditor(
1205 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1205 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1206 )
1206 )
1207 elif editform:
1207 elif editform:
1208 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1208 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1209 else:
1209 else:
1210 return commiteditor
1210 return commiteditor
1211
1211
1212
1212
1213 def _escapecommandtemplate(tmpl):
1213 def _escapecommandtemplate(tmpl):
1214 parts = []
1214 parts = []
1215 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1215 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1216 if typ == b'string':
1216 if typ == b'string':
1217 parts.append(stringutil.escapestr(tmpl[start:end]))
1217 parts.append(stringutil.escapestr(tmpl[start:end]))
1218 else:
1218 else:
1219 parts.append(tmpl[start:end])
1219 parts.append(tmpl[start:end])
1220 return b''.join(parts)
1220 return b''.join(parts)
1221
1221
1222
1222
1223 def rendercommandtemplate(ui, tmpl, props):
1223 def rendercommandtemplate(ui, tmpl, props):
1224 r"""Expand a literal template 'tmpl' in a way suitable for command line
1224 r"""Expand a literal template 'tmpl' in a way suitable for command line
1225
1225
1226 '\' in outermost string is not taken as an escape character because it
1226 '\' in outermost string is not taken as an escape character because it
1227 is a directory separator on Windows.
1227 is a directory separator on Windows.
1228
1228
1229 >>> from . import ui as uimod
1229 >>> from . import ui as uimod
1230 >>> ui = uimod.ui()
1230 >>> ui = uimod.ui()
1231 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1231 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1232 'c:\\foo'
1232 'c:\\foo'
1233 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1233 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1234 'c:{path}'
1234 'c:{path}'
1235 """
1235 """
1236 if not tmpl:
1236 if not tmpl:
1237 return tmpl
1237 return tmpl
1238 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1238 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1239 return t.renderdefault(props)
1239 return t.renderdefault(props)
1240
1240
1241
1241
1242 def rendertemplate(ctx, tmpl, props=None):
1242 def rendertemplate(ctx, tmpl, props=None):
1243 """Expand a literal template 'tmpl' byte-string against one changeset
1243 """Expand a literal template 'tmpl' byte-string against one changeset
1244
1244
1245 Each props item must be a stringify-able value or a callable returning
1245 Each props item must be a stringify-able value or a callable returning
1246 such value, i.e. no bare list nor dict should be passed.
1246 such value, i.e. no bare list nor dict should be passed.
1247 """
1247 """
1248 repo = ctx.repo()
1248 repo = ctx.repo()
1249 tres = formatter.templateresources(repo.ui, repo)
1249 tres = formatter.templateresources(repo.ui, repo)
1250 t = formatter.maketemplater(
1250 t = formatter.maketemplater(
1251 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1251 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1252 )
1252 )
1253 mapping = {b'ctx': ctx}
1253 mapping = {b'ctx': ctx}
1254 if props:
1254 if props:
1255 mapping.update(props)
1255 mapping.update(props)
1256 return t.renderdefault(mapping)
1256 return t.renderdefault(mapping)
1257
1257
1258
1258
1259 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1259 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1260 """Format a changeset summary (one line)."""
1260 """Format a changeset summary (one line)."""
1261 spec = None
1261 spec = None
1262 if command:
1262 if command:
1263 spec = ui.config(
1263 spec = ui.config(
1264 b'command-templates', b'oneline-summary.%s' % command, None
1264 b'command-templates', b'oneline-summary.%s' % command, None
1265 )
1265 )
1266 if not spec:
1266 if not spec:
1267 spec = ui.config(b'command-templates', b'oneline-summary')
1267 spec = ui.config(b'command-templates', b'oneline-summary')
1268 if not spec:
1268 if not spec:
1269 spec = default_spec
1269 spec = default_spec
1270 if not spec:
1270 if not spec:
1271 spec = (
1271 spec = (
1272 b'{separate(" ", '
1272 b'{separate(" ", '
1273 b'label("oneline-summary.changeset", "{rev}:{node|short}")'
1273 b'label("oneline-summary.changeset", "{rev}:{node|short}")'
1274 b', '
1274 b', '
1275 b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names % "{label("oneline-summary.{namespace}", name)}", " "))}"), " ")'
1275 b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names % "{label("oneline-summary.{namespace}", name)}", " "))}"), " ")'
1276 b')} '
1276 b')} '
1277 b'"{label("oneline-summary.desc", desc|firstline)}"'
1277 b'"{label("oneline-summary.desc", desc|firstline)}"'
1278 )
1278 )
1279 text = rendertemplate(ctx, spec)
1279 text = rendertemplate(ctx, spec)
1280 return text.split(b'\n')[0]
1280 return text.split(b'\n')[0]
1281
1281
1282
1282
1283 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1283 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1284 r"""Convert old-style filename format string to template string
1284 r"""Convert old-style filename format string to template string
1285
1285
1286 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1286 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1287 'foo-{reporoot|basename}-{seqno}.patch'
1287 'foo-{reporoot|basename}-{seqno}.patch'
1288 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1288 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1289 '{rev}{tags % "{tag}"}{node}'
1289 '{rev}{tags % "{tag}"}{node}'
1290
1290
1291 '\' in outermost strings has to be escaped because it is a directory
1291 '\' in outermost strings has to be escaped because it is a directory
1292 separator on Windows:
1292 separator on Windows:
1293
1293
1294 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1294 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1295 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1295 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1296 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1296 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1297 '\\\\\\\\foo\\\\bar.patch'
1297 '\\\\\\\\foo\\\\bar.patch'
1298 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1298 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1299 '\\\\{tags % "{tag}"}'
1299 '\\\\{tags % "{tag}"}'
1300
1300
1301 but inner strings follow the template rules (i.e. '\' is taken as an
1301 but inner strings follow the template rules (i.e. '\' is taken as an
1302 escape character):
1302 escape character):
1303
1303
1304 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1304 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1305 '{"c:\\tmp"}'
1305 '{"c:\\tmp"}'
1306 """
1306 """
1307 expander = {
1307 expander = {
1308 b'H': b'{node}',
1308 b'H': b'{node}',
1309 b'R': b'{rev}',
1309 b'R': b'{rev}',
1310 b'h': b'{node|short}',
1310 b'h': b'{node|short}',
1311 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1311 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1312 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1312 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1313 b'%': b'%',
1313 b'%': b'%',
1314 b'b': b'{reporoot|basename}',
1314 b'b': b'{reporoot|basename}',
1315 }
1315 }
1316 if total is not None:
1316 if total is not None:
1317 expander[b'N'] = b'{total}'
1317 expander[b'N'] = b'{total}'
1318 if seqno is not None:
1318 if seqno is not None:
1319 expander[b'n'] = b'{seqno}'
1319 expander[b'n'] = b'{seqno}'
1320 if total is not None and seqno is not None:
1320 if total is not None and seqno is not None:
1321 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1321 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1322 if pathname is not None:
1322 if pathname is not None:
1323 expander[b's'] = b'{pathname|basename}'
1323 expander[b's'] = b'{pathname|basename}'
1324 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1324 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1325 expander[b'p'] = b'{pathname}'
1325 expander[b'p'] = b'{pathname}'
1326
1326
1327 newname = []
1327 newname = []
1328 for typ, start, end in templater.scantemplate(pat, raw=True):
1328 for typ, start, end in templater.scantemplate(pat, raw=True):
1329 if typ != b'string':
1329 if typ != b'string':
1330 newname.append(pat[start:end])
1330 newname.append(pat[start:end])
1331 continue
1331 continue
1332 i = start
1332 i = start
1333 while i < end:
1333 while i < end:
1334 n = pat.find(b'%', i, end)
1334 n = pat.find(b'%', i, end)
1335 if n < 0:
1335 if n < 0:
1336 newname.append(stringutil.escapestr(pat[i:end]))
1336 newname.append(stringutil.escapestr(pat[i:end]))
1337 break
1337 break
1338 newname.append(stringutil.escapestr(pat[i:n]))
1338 newname.append(stringutil.escapestr(pat[i:n]))
1339 if n + 2 > end:
1339 if n + 2 > end:
1340 raise error.Abort(
1340 raise error.Abort(
1341 _(b"incomplete format spec in output filename")
1341 _(b"incomplete format spec in output filename")
1342 )
1342 )
1343 c = pat[n + 1 : n + 2]
1343 c = pat[n + 1 : n + 2]
1344 i = n + 2
1344 i = n + 2
1345 try:
1345 try:
1346 newname.append(expander[c])
1346 newname.append(expander[c])
1347 except KeyError:
1347 except KeyError:
1348 raise error.Abort(
1348 raise error.Abort(
1349 _(b"invalid format spec '%%%s' in output filename") % c
1349 _(b"invalid format spec '%%%s' in output filename") % c
1350 )
1350 )
1351 return b''.join(newname)
1351 return b''.join(newname)
1352
1352
1353
1353
1354 def makefilename(ctx, pat, **props):
1354 def makefilename(ctx, pat, **props):
1355 if not pat:
1355 if not pat:
1356 return pat
1356 return pat
1357 tmpl = _buildfntemplate(pat, **props)
1357 tmpl = _buildfntemplate(pat, **props)
1358 # BUG: alias expansion shouldn't be made against template fragments
1358 # BUG: alias expansion shouldn't be made against template fragments
1359 # rewritten from %-format strings, but we have no easy way to partially
1359 # rewritten from %-format strings, but we have no easy way to partially
1360 # disable the expansion.
1360 # disable the expansion.
1361 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1361 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1362
1362
1363
1363
1364 def isstdiofilename(pat):
1364 def isstdiofilename(pat):
1365 """True if the given pat looks like a filename denoting stdin/stdout"""
1365 """True if the given pat looks like a filename denoting stdin/stdout"""
1366 return not pat or pat == b'-'
1366 return not pat or pat == b'-'
1367
1367
1368
1368
1369 class _unclosablefile:
1369 class _unclosablefile:
1370 def __init__(self, fp):
1370 def __init__(self, fp):
1371 self._fp = fp
1371 self._fp = fp
1372
1372
1373 def close(self):
1373 def close(self):
1374 pass
1374 pass
1375
1375
1376 def __iter__(self):
1376 def __iter__(self):
1377 return iter(self._fp)
1377 return iter(self._fp)
1378
1378
1379 def __getattr__(self, attr):
1379 def __getattr__(self, attr):
1380 return getattr(self._fp, attr)
1380 return getattr(self._fp, attr)
1381
1381
1382 def __enter__(self):
1382 def __enter__(self):
1383 return self
1383 return self
1384
1384
1385 def __exit__(self, exc_type, exc_value, exc_tb):
1385 def __exit__(self, exc_type, exc_value, exc_tb):
1386 pass
1386 pass
1387
1387
1388
1388
1389 def makefileobj(ctx, pat, mode=b'wb', **props):
1389 def makefileobj(ctx, pat, mode=b'wb', **props):
1390 writable = mode not in (b'r', b'rb')
1390 writable = mode not in (b'r', b'rb')
1391
1391
1392 if isstdiofilename(pat):
1392 if isstdiofilename(pat):
1393 repo = ctx.repo()
1393 repo = ctx.repo()
1394 if writable:
1394 if writable:
1395 fp = repo.ui.fout
1395 fp = repo.ui.fout
1396 else:
1396 else:
1397 fp = repo.ui.fin
1397 fp = repo.ui.fin
1398 return _unclosablefile(fp)
1398 return _unclosablefile(fp)
1399 fn = makefilename(ctx, pat, **props)
1399 fn = makefilename(ctx, pat, **props)
1400 return open(fn, mode)
1400 return open(fn, mode)
1401
1401
1402
1402
1403 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1403 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1404 """opens the changelog, manifest, a filelog or a given revlog"""
1404 """opens the changelog, manifest, a filelog or a given revlog"""
1405 cl = opts[b'changelog']
1405 cl = opts[b'changelog']
1406 mf = opts[b'manifest']
1406 mf = opts[b'manifest']
1407 dir = opts[b'dir']
1407 dir = opts[b'dir']
1408 msg = None
1408 msg = None
1409 if cl and mf:
1409 if cl and mf:
1410 msg = _(b'cannot specify --changelog and --manifest at the same time')
1410 msg = _(b'cannot specify --changelog and --manifest at the same time')
1411 elif cl and dir:
1411 elif cl and dir:
1412 msg = _(b'cannot specify --changelog and --dir at the same time')
1412 msg = _(b'cannot specify --changelog and --dir at the same time')
1413 elif cl or mf or dir:
1413 elif cl or mf or dir:
1414 if file_:
1414 if file_:
1415 msg = _(b'cannot specify filename with --changelog or --manifest')
1415 msg = _(b'cannot specify filename with --changelog or --manifest')
1416 elif not repo:
1416 elif not repo:
1417 msg = _(
1417 msg = _(
1418 b'cannot specify --changelog or --manifest or --dir '
1418 b'cannot specify --changelog or --manifest or --dir '
1419 b'without a repository'
1419 b'without a repository'
1420 )
1420 )
1421 if msg:
1421 if msg:
1422 raise error.InputError(msg)
1422 raise error.InputError(msg)
1423
1423
1424 r = None
1424 r = None
1425 if repo:
1425 if repo:
1426 if cl:
1426 if cl:
1427 r = repo.unfiltered().changelog
1427 r = repo.unfiltered().changelog
1428 elif dir:
1428 elif dir:
1429 if not scmutil.istreemanifest(repo):
1429 if not scmutil.istreemanifest(repo):
1430 raise error.InputError(
1430 raise error.InputError(
1431 _(
1431 _(
1432 b"--dir can only be used on repos with "
1432 b"--dir can only be used on repos with "
1433 b"treemanifest enabled"
1433 b"treemanifest enabled"
1434 )
1434 )
1435 )
1435 )
1436 if not dir.endswith(b'/'):
1436 if not dir.endswith(b'/'):
1437 dir = dir + b'/'
1437 dir = dir + b'/'
1438 dirlog = repo.manifestlog.getstorage(dir)
1438 dirlog = repo.manifestlog.getstorage(dir)
1439 if len(dirlog):
1439 if len(dirlog):
1440 r = dirlog
1440 r = dirlog
1441 elif mf:
1441 elif mf:
1442 r = repo.manifestlog.getstorage(b'')
1442 r = repo.manifestlog.getstorage(b'')
1443 elif file_:
1443 elif file_:
1444 filelog = repo.file(file_)
1444 filelog = repo.file(file_)
1445 if len(filelog):
1445 if len(filelog):
1446 r = filelog
1446 r = filelog
1447
1447
1448 # Not all storage may be revlogs. If requested, try to return an actual
1448 # Not all storage may be revlogs. If requested, try to return an actual
1449 # revlog instance.
1449 # revlog instance.
1450 if returnrevlog:
1450 if returnrevlog:
1451 if isinstance(r, revlog.revlog):
1451 if isinstance(r, revlog.revlog):
1452 pass
1452 pass
1453 elif util.safehasattr(r, '_revlog'):
1453 elif util.safehasattr(r, '_revlog'):
1454 r = r._revlog # pytype: disable=attribute-error
1454 r = r._revlog # pytype: disable=attribute-error
1455 elif r is not None:
1455 elif r is not None:
1456 raise error.InputError(
1456 raise error.InputError(
1457 _(b'%r does not appear to be a revlog') % r
1457 _(b'%r does not appear to be a revlog') % r
1458 )
1458 )
1459
1459
1460 if not r:
1460 if not r:
1461 if not returnrevlog:
1461 if not returnrevlog:
1462 raise error.InputError(_(b'cannot give path to non-revlog'))
1462 raise error.InputError(_(b'cannot give path to non-revlog'))
1463
1463
1464 if not file_:
1464 if not file_:
1465 raise error.CommandError(cmd, _(b'invalid arguments'))
1465 raise error.CommandError(cmd, _(b'invalid arguments'))
1466 if not os.path.isfile(file_):
1466 if not os.path.isfile(file_):
1467 raise error.InputError(_(b"revlog '%s' not found") % file_)
1467 raise error.InputError(_(b"revlog '%s' not found") % file_)
1468
1468
1469 target = (revlog_constants.KIND_OTHER, b'free-form:%s' % file_)
1469 target = (revlog_constants.KIND_OTHER, b'free-form:%s' % file_)
1470 r = revlog.revlog(
1470 r = revlog.revlog(
1471 vfsmod.vfs(encoding.getcwd(), audit=False),
1471 vfsmod.vfs(encoding.getcwd(), audit=False),
1472 target=target,
1472 target=target,
1473 radix=file_[:-2],
1473 radix=file_[:-2],
1474 )
1474 )
1475 return r
1475 return r
1476
1476
1477
1477
1478 def openrevlog(repo, cmd, file_, opts):
1478 def openrevlog(repo, cmd, file_, opts):
1479 """Obtain a revlog backing storage of an item.
1479 """Obtain a revlog backing storage of an item.
1480
1480
1481 This is similar to ``openstorage()`` except it always returns a revlog.
1481 This is similar to ``openstorage()`` except it always returns a revlog.
1482
1482
1483 In most cases, a caller cares about the main storage object - not the
1483 In most cases, a caller cares about the main storage object - not the
1484 revlog backing it. Therefore, this function should only be used by code
1484 revlog backing it. Therefore, this function should only be used by code
1485 that needs to examine low-level revlog implementation details. e.g. debug
1485 that needs to examine low-level revlog implementation details. e.g. debug
1486 commands.
1486 commands.
1487 """
1487 """
1488 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1488 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1489
1489
1490
1490
1491 def copy(ui, repo, pats, opts: Dict[bytes, Any], rename=False):
1491 def copy(ui, repo, pats, opts: Dict[bytes, Any], rename=False):
1492 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1492 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1493
1493
1494 # called with the repo lock held
1494 # called with the repo lock held
1495 #
1495 #
1496 # hgsep => pathname that uses "/" to separate directories
1496 # hgsep => pathname that uses "/" to separate directories
1497 # ossep => pathname that uses os.sep to separate directories
1497 # ossep => pathname that uses os.sep to separate directories
1498 cwd = repo.getcwd()
1498 cwd = repo.getcwd()
1499 targets = {}
1499 targets = {}
1500 forget = opts.get(b"forget")
1500 forget = opts.get(b"forget")
1501 after = opts.get(b"after")
1501 after = opts.get(b"after")
1502 dryrun = opts.get(b"dry_run")
1502 dryrun = opts.get(b"dry_run")
1503 rev = opts.get(b'at_rev')
1503 rev = opts.get(b'at_rev')
1504 if rev:
1504 if rev:
1505 if not forget and not after:
1505 if not forget and not after:
1506 # TODO: Remove this restriction and make it also create the copy
1506 # TODO: Remove this restriction and make it also create the copy
1507 # targets (and remove the rename source if rename==True).
1507 # targets (and remove the rename source if rename==True).
1508 raise error.InputError(_(b'--at-rev requires --after'))
1508 raise error.InputError(_(b'--at-rev requires --after'))
1509 ctx = logcmdutil.revsingle(repo, rev)
1509 ctx = logcmdutil.revsingle(repo, rev)
1510 if len(ctx.parents()) > 1:
1510 if len(ctx.parents()) > 1:
1511 raise error.InputError(
1511 raise error.InputError(
1512 _(b'cannot mark/unmark copy in merge commit')
1512 _(b'cannot mark/unmark copy in merge commit')
1513 )
1513 )
1514 else:
1514 else:
1515 ctx = repo[None]
1515 ctx = repo[None]
1516
1516
1517 pctx = ctx.p1()
1517 pctx = ctx.p1()
1518
1518
1519 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1519 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1520
1520
1521 if forget:
1521 if forget:
1522 if ctx.rev() is None:
1522 if ctx.rev() is None:
1523 new_ctx = ctx
1523 new_ctx = ctx
1524 else:
1524 else:
1525 if len(ctx.parents()) > 1:
1525 if len(ctx.parents()) > 1:
1526 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1526 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1527 # avoid cycle context -> subrepo -> cmdutil
1527 # avoid cycle context -> subrepo -> cmdutil
1528 from . import context
1528 from . import context
1529
1529
1530 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1530 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1531 new_ctx = context.overlayworkingctx(repo)
1531 new_ctx = context.overlayworkingctx(repo)
1532 new_ctx.setbase(ctx.p1())
1532 new_ctx.setbase(ctx.p1())
1533 mergemod.graft(repo, ctx, wctx=new_ctx)
1533 mergemod.graft(repo, ctx, wctx=new_ctx)
1534
1534
1535 match = scmutil.match(ctx, pats, opts)
1535 match = scmutil.match(ctx, pats, opts)
1536
1536
1537 current_copies = ctx.p1copies()
1537 current_copies = ctx.p1copies()
1538 current_copies.update(ctx.p2copies())
1538 current_copies.update(ctx.p2copies())
1539
1539
1540 uipathfn = scmutil.getuipathfn(repo)
1540 uipathfn = scmutil.getuipathfn(repo)
1541 for f in ctx.walk(match):
1541 for f in ctx.walk(match):
1542 if f in current_copies:
1542 if f in current_copies:
1543 new_ctx[f].markcopied(None)
1543 new_ctx[f].markcopied(None)
1544 elif match.exact(f):
1544 elif match.exact(f):
1545 ui.warn(
1545 ui.warn(
1546 _(
1546 _(
1547 b'%s: not unmarking as copy - file is not marked as copied\n'
1547 b'%s: not unmarking as copy - file is not marked as copied\n'
1548 )
1548 )
1549 % uipathfn(f)
1549 % uipathfn(f)
1550 )
1550 )
1551
1551
1552 if ctx.rev() is not None:
1552 if ctx.rev() is not None:
1553 with repo.lock():
1553 with repo.lock():
1554 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1554 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1555 new_node = mem_ctx.commit()
1555 new_node = mem_ctx.commit()
1556
1556
1557 if repo.dirstate.p1() == ctx.node():
1557 if repo.dirstate.p1() == ctx.node():
1558 with repo.dirstate.changing_parents(repo):
1558 with repo.dirstate.changing_parents(repo):
1559 scmutil.movedirstate(repo, repo[new_node])
1559 scmutil.movedirstate(repo, repo[new_node])
1560 replacements = {ctx.node(): [new_node]}
1560 replacements = {ctx.node(): [new_node]}
1561 scmutil.cleanupnodes(
1561 scmutil.cleanupnodes(
1562 repo, replacements, b'uncopy', fixphase=True
1562 repo, replacements, b'uncopy', fixphase=True
1563 )
1563 )
1564
1564
1565 return
1565 return
1566
1566
1567 pats = scmutil.expandpats(pats)
1567 pats = scmutil.expandpats(pats)
1568 if not pats:
1568 if not pats:
1569 raise error.InputError(_(b'no source or destination specified'))
1569 raise error.InputError(_(b'no source or destination specified'))
1570 if len(pats) == 1:
1570 if len(pats) == 1:
1571 raise error.InputError(_(b'no destination specified'))
1571 raise error.InputError(_(b'no destination specified'))
1572 dest = pats.pop()
1572 dest = pats.pop()
1573
1573
1574 def walkpat(pat):
1574 def walkpat(pat):
1575 srcs = []
1575 srcs = []
1576 # TODO: Inline and simplify the non-working-copy version of this code
1576 # TODO: Inline and simplify the non-working-copy version of this code
1577 # since it shares very little with the working-copy version of it.
1577 # since it shares very little with the working-copy version of it.
1578 ctx_to_walk = ctx if ctx.rev() is None else pctx
1578 ctx_to_walk = ctx if ctx.rev() is None else pctx
1579 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1579 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1580 for abs in ctx_to_walk.walk(m):
1580 for abs in ctx_to_walk.walk(m):
1581 rel = uipathfn(abs)
1581 rel = uipathfn(abs)
1582 exact = m.exact(abs)
1582 exact = m.exact(abs)
1583 if abs not in ctx:
1583 if abs not in ctx:
1584 if abs in pctx:
1584 if abs in pctx:
1585 if not after:
1585 if not after:
1586 if exact:
1586 if exact:
1587 ui.warn(
1587 ui.warn(
1588 _(
1588 _(
1589 b'%s: not copying - file has been marked '
1589 b'%s: not copying - file has been marked '
1590 b'for remove\n'
1590 b'for remove\n'
1591 )
1591 )
1592 % rel
1592 % rel
1593 )
1593 )
1594 continue
1594 continue
1595 else:
1595 else:
1596 if exact:
1596 if exact:
1597 ui.warn(
1597 ui.warn(
1598 _(b'%s: not copying - file is not managed\n') % rel
1598 _(b'%s: not copying - file is not managed\n') % rel
1599 )
1599 )
1600 continue
1600 continue
1601
1601
1602 # abs: hgsep
1602 # abs: hgsep
1603 # rel: ossep
1603 # rel: ossep
1604 srcs.append((abs, rel, exact))
1604 srcs.append((abs, rel, exact))
1605 return srcs
1605 return srcs
1606
1606
1607 if ctx.rev() is not None:
1607 if ctx.rev() is not None:
1608 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1608 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1609 absdest = pathutil.canonpath(repo.root, cwd, dest)
1609 absdest = pathutil.canonpath(repo.root, cwd, dest)
1610 if ctx.hasdir(absdest):
1610 if ctx.hasdir(absdest):
1611 raise error.InputError(
1611 raise error.InputError(
1612 _(b'%s: --at-rev does not support a directory as destination')
1612 _(b'%s: --at-rev does not support a directory as destination')
1613 % uipathfn(absdest)
1613 % uipathfn(absdest)
1614 )
1614 )
1615 if absdest not in ctx:
1615 if absdest not in ctx:
1616 raise error.InputError(
1616 raise error.InputError(
1617 _(b'%s: copy destination does not exist in %s')
1617 _(b'%s: copy destination does not exist in %s')
1618 % (uipathfn(absdest), ctx)
1618 % (uipathfn(absdest), ctx)
1619 )
1619 )
1620
1620
1621 # avoid cycle context -> subrepo -> cmdutil
1621 # avoid cycle context -> subrepo -> cmdutil
1622 from . import context
1622 from . import context
1623
1623
1624 copylist = []
1624 copylist = []
1625 for pat in pats:
1625 for pat in pats:
1626 srcs = walkpat(pat)
1626 srcs = walkpat(pat)
1627 if not srcs:
1627 if not srcs:
1628 continue
1628 continue
1629 for abs, rel, exact in srcs:
1629 for abs, rel, exact in srcs:
1630 copylist.append(abs)
1630 copylist.append(abs)
1631
1631
1632 if not copylist:
1632 if not copylist:
1633 raise error.InputError(_(b'no files to copy'))
1633 raise error.InputError(_(b'no files to copy'))
1634 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1634 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1635 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1635 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1636 # existing functions below.
1636 # existing functions below.
1637 if len(copylist) != 1:
1637 if len(copylist) != 1:
1638 raise error.InputError(_(b'--at-rev requires a single source'))
1638 raise error.InputError(_(b'--at-rev requires a single source'))
1639
1639
1640 new_ctx = context.overlayworkingctx(repo)
1640 new_ctx = context.overlayworkingctx(repo)
1641 new_ctx.setbase(ctx.p1())
1641 new_ctx.setbase(ctx.p1())
1642 mergemod.graft(repo, ctx, wctx=new_ctx)
1642 mergemod.graft(repo, ctx, wctx=new_ctx)
1643
1643
1644 new_ctx.markcopied(absdest, copylist[0])
1644 new_ctx.markcopied(absdest, copylist[0])
1645
1645
1646 with repo.lock():
1646 with repo.lock():
1647 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1647 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1648 new_node = mem_ctx.commit()
1648 new_node = mem_ctx.commit()
1649
1649
1650 if repo.dirstate.p1() == ctx.node():
1650 if repo.dirstate.p1() == ctx.node():
1651 with repo.dirstate.changing_parents(repo):
1651 with repo.dirstate.changing_parents(repo):
1652 scmutil.movedirstate(repo, repo[new_node])
1652 scmutil.movedirstate(repo, repo[new_node])
1653 replacements = {ctx.node(): [new_node]}
1653 replacements = {ctx.node(): [new_node]}
1654 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1654 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1655
1655
1656 return
1656 return
1657
1657
1658 # abssrc: hgsep
1658 # abssrc: hgsep
1659 # relsrc: ossep
1659 # relsrc: ossep
1660 # otarget: ossep
1660 # otarget: ossep
1661 def copyfile(abssrc, relsrc, otarget, exact):
1661 def copyfile(abssrc, relsrc, otarget, exact):
1662 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1662 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1663 if b'/' in abstarget:
1663 if b'/' in abstarget:
1664 # We cannot normalize abstarget itself, this would prevent
1664 # We cannot normalize abstarget itself, this would prevent
1665 # case only renames, like a => A.
1665 # case only renames, like a => A.
1666 abspath, absname = abstarget.rsplit(b'/', 1)
1666 abspath, absname = abstarget.rsplit(b'/', 1)
1667 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1667 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1668 reltarget = repo.pathto(abstarget, cwd)
1668 reltarget = repo.pathto(abstarget, cwd)
1669 target = repo.wjoin(abstarget)
1669 target = repo.wjoin(abstarget)
1670 src = repo.wjoin(abssrc)
1670 src = repo.wjoin(abssrc)
1671 entry = repo.dirstate.get_entry(abstarget)
1671 entry = repo.dirstate.get_entry(abstarget)
1672
1672
1673 already_commited = entry.tracked and not entry.added
1673 already_commited = entry.tracked and not entry.added
1674
1674
1675 scmutil.checkportable(ui, abstarget)
1675 scmutil.checkportable(ui, abstarget)
1676
1676
1677 # check for collisions
1677 # check for collisions
1678 prevsrc = targets.get(abstarget)
1678 prevsrc = targets.get(abstarget)
1679 if prevsrc is not None:
1679 if prevsrc is not None:
1680 ui.warn(
1680 ui.warn(
1681 _(b'%s: not overwriting - %s collides with %s\n')
1681 _(b'%s: not overwriting - %s collides with %s\n')
1682 % (
1682 % (
1683 reltarget,
1683 reltarget,
1684 repo.pathto(abssrc, cwd),
1684 repo.pathto(abssrc, cwd),
1685 repo.pathto(prevsrc, cwd),
1685 repo.pathto(prevsrc, cwd),
1686 )
1686 )
1687 )
1687 )
1688 return True # report a failure
1688 return True # report a failure
1689
1689
1690 # check for overwrites
1690 # check for overwrites
1691 exists = os.path.lexists(target)
1691 exists = os.path.lexists(target)
1692 samefile = False
1692 samefile = False
1693 if exists and abssrc != abstarget:
1693 if exists and abssrc != abstarget:
1694 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1694 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1695 abstarget
1695 abstarget
1696 ):
1696 ):
1697 if not rename:
1697 if not rename:
1698 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1698 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1699 return True # report a failure
1699 return True # report a failure
1700 exists = False
1700 exists = False
1701 samefile = True
1701 samefile = True
1702
1702
1703 if not after and exists or after and already_commited:
1703 if not after and exists or after and already_commited:
1704 if not opts[b'force']:
1704 if not opts[b'force']:
1705 if already_commited:
1705 if already_commited:
1706 msg = _(b'%s: not overwriting - file already committed\n')
1706 msg = _(b'%s: not overwriting - file already committed\n')
1707 # Check if if the target was added in the parent and the
1707 # Check if if the target was added in the parent and the
1708 # source already existed in the grandparent.
1708 # source already existed in the grandparent.
1709 looks_like_copy_in_pctx = abstarget in pctx and any(
1709 looks_like_copy_in_pctx = abstarget in pctx and any(
1710 abssrc in gpctx and abstarget not in gpctx
1710 abssrc in gpctx and abstarget not in gpctx
1711 for gpctx in pctx.parents()
1711 for gpctx in pctx.parents()
1712 )
1712 )
1713 if looks_like_copy_in_pctx:
1713 if looks_like_copy_in_pctx:
1714 if rename:
1714 if rename:
1715 hint = _(
1715 hint = _(
1716 b"('hg rename --at-rev .' to record the rename "
1716 b"('hg rename --at-rev .' to record the rename "
1717 b"in the parent of the working copy)\n"
1717 b"in the parent of the working copy)\n"
1718 )
1718 )
1719 else:
1719 else:
1720 hint = _(
1720 hint = _(
1721 b"('hg copy --at-rev .' to record the copy in "
1721 b"('hg copy --at-rev .' to record the copy in "
1722 b"the parent of the working copy)\n"
1722 b"the parent of the working copy)\n"
1723 )
1723 )
1724 else:
1724 else:
1725 if after:
1725 if after:
1726 flags = b'--after --force'
1726 flags = b'--after --force'
1727 else:
1727 else:
1728 flags = b'--force'
1728 flags = b'--force'
1729 if rename:
1729 if rename:
1730 hint = (
1730 hint = (
1731 _(
1731 _(
1732 b"('hg rename %s' to replace the file by "
1732 b"('hg rename %s' to replace the file by "
1733 b'recording a rename)\n'
1733 b'recording a rename)\n'
1734 )
1734 )
1735 % flags
1735 % flags
1736 )
1736 )
1737 else:
1737 else:
1738 hint = (
1738 hint = (
1739 _(
1739 _(
1740 b"('hg copy %s' to replace the file by "
1740 b"('hg copy %s' to replace the file by "
1741 b'recording a copy)\n'
1741 b'recording a copy)\n'
1742 )
1742 )
1743 % flags
1743 % flags
1744 )
1744 )
1745 else:
1745 else:
1746 msg = _(b'%s: not overwriting - file exists\n')
1746 msg = _(b'%s: not overwriting - file exists\n')
1747 if rename:
1747 if rename:
1748 hint = _(
1748 hint = _(
1749 b"('hg rename --after' to record the rename)\n"
1749 b"('hg rename --after' to record the rename)\n"
1750 )
1750 )
1751 else:
1751 else:
1752 hint = _(b"('hg copy --after' to record the copy)\n")
1752 hint = _(b"('hg copy --after' to record the copy)\n")
1753 ui.warn(msg % reltarget)
1753 ui.warn(msg % reltarget)
1754 ui.warn(hint)
1754 ui.warn(hint)
1755 return True # report a failure
1755 return True # report a failure
1756
1756
1757 if after:
1757 if after:
1758 if not exists:
1758 if not exists:
1759 if rename:
1759 if rename:
1760 ui.warn(
1760 ui.warn(
1761 _(b'%s: not recording move - %s does not exist\n')
1761 _(b'%s: not recording move - %s does not exist\n')
1762 % (relsrc, reltarget)
1762 % (relsrc, reltarget)
1763 )
1763 )
1764 else:
1764 else:
1765 ui.warn(
1765 ui.warn(
1766 _(b'%s: not recording copy - %s does not exist\n')
1766 _(b'%s: not recording copy - %s does not exist\n')
1767 % (relsrc, reltarget)
1767 % (relsrc, reltarget)
1768 )
1768 )
1769 return True # report a failure
1769 return True # report a failure
1770 elif not dryrun:
1770 elif not dryrun:
1771 try:
1771 try:
1772 if exists:
1772 if exists:
1773 os.unlink(target)
1773 os.unlink(target)
1774 targetdir = os.path.dirname(target) or b'.'
1774 targetdir = os.path.dirname(target) or b'.'
1775 if not os.path.isdir(targetdir):
1775 if not os.path.isdir(targetdir):
1776 os.makedirs(targetdir)
1776 os.makedirs(targetdir)
1777 if samefile:
1777 if samefile:
1778 tmp = target + b"~hgrename"
1778 tmp = target + b"~hgrename"
1779 os.rename(src, tmp)
1779 os.rename(src, tmp)
1780 os.rename(tmp, target)
1780 os.rename(tmp, target)
1781 else:
1781 else:
1782 # Preserve stat info on renames, not on copies; this matches
1782 # Preserve stat info on renames, not on copies; this matches
1783 # Linux CLI behavior.
1783 # Linux CLI behavior.
1784 util.copyfile(src, target, copystat=rename)
1784 util.copyfile(src, target, copystat=rename)
1785 srcexists = True
1785 srcexists = True
1786 except IOError as inst:
1786 except IOError as inst:
1787 if inst.errno == errno.ENOENT:
1787 if inst.errno == errno.ENOENT:
1788 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1788 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1789 srcexists = False
1789 srcexists = False
1790 else:
1790 else:
1791 ui.warn(
1791 ui.warn(
1792 _(b'%s: cannot copy - %s\n')
1792 _(b'%s: cannot copy - %s\n')
1793 % (relsrc, encoding.strtolocal(inst.strerror))
1793 % (relsrc, encoding.strtolocal(inst.strerror))
1794 )
1794 )
1795 return True # report a failure
1795 return True # report a failure
1796
1796
1797 if ui.verbose or not exact:
1797 if ui.verbose or not exact:
1798 if rename:
1798 if rename:
1799 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1799 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1800 else:
1800 else:
1801 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1801 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1802
1802
1803 targets[abstarget] = abssrc
1803 targets[abstarget] = abssrc
1804
1804
1805 # fix up dirstate
1805 # fix up dirstate
1806 scmutil.dirstatecopy(
1806 scmutil.dirstatecopy(
1807 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1807 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1808 )
1808 )
1809 if rename and not dryrun:
1809 if rename and not dryrun:
1810 if not after and srcexists and not samefile:
1810 if not after and srcexists and not samefile:
1811 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1811 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1812 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1812 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1813 ctx.forget([abssrc])
1813 ctx.forget([abssrc])
1814
1814
1815 # pat: ossep
1815 # pat: ossep
1816 # dest ossep
1816 # dest ossep
1817 # srcs: list of (hgsep, hgsep, ossep, bool)
1817 # srcs: list of (hgsep, hgsep, ossep, bool)
1818 # return: function that takes hgsep and returns ossep
1818 # return: function that takes hgsep and returns ossep
1819 def targetpathfn(pat, dest, srcs):
1819 def targetpathfn(pat, dest, srcs):
1820 if os.path.isdir(pat):
1820 if os.path.isdir(pat):
1821 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1821 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1822 abspfx = util.localpath(abspfx)
1822 abspfx = util.localpath(abspfx)
1823 if destdirexists:
1823 if destdirexists:
1824 striplen = len(os.path.split(abspfx)[0])
1824 striplen = len(os.path.split(abspfx)[0])
1825 else:
1825 else:
1826 striplen = len(abspfx)
1826 striplen = len(abspfx)
1827 if striplen:
1827 if striplen:
1828 striplen += len(pycompat.ossep)
1828 striplen += len(pycompat.ossep)
1829 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1829 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1830 elif destdirexists:
1830 elif destdirexists:
1831 res = lambda p: os.path.join(
1831 res = lambda p: os.path.join(
1832 dest, os.path.basename(util.localpath(p))
1832 dest, os.path.basename(util.localpath(p))
1833 )
1833 )
1834 else:
1834 else:
1835 res = lambda p: dest
1835 res = lambda p: dest
1836 return res
1836 return res
1837
1837
1838 # pat: ossep
1838 # pat: ossep
1839 # dest ossep
1839 # dest ossep
1840 # srcs: list of (hgsep, hgsep, ossep, bool)
1840 # srcs: list of (hgsep, hgsep, ossep, bool)
1841 # return: function that takes hgsep and returns ossep
1841 # return: function that takes hgsep and returns ossep
1842 def targetpathafterfn(pat, dest, srcs):
1842 def targetpathafterfn(pat, dest, srcs):
1843 if matchmod.patkind(pat):
1843 if matchmod.patkind(pat):
1844 # a mercurial pattern
1844 # a mercurial pattern
1845 res = lambda p: os.path.join(
1845 res = lambda p: os.path.join(
1846 dest, os.path.basename(util.localpath(p))
1846 dest, os.path.basename(util.localpath(p))
1847 )
1847 )
1848 else:
1848 else:
1849 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1849 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1850 if len(abspfx) < len(srcs[0][0]):
1850 if len(abspfx) < len(srcs[0][0]):
1851 # A directory. Either the target path contains the last
1851 # A directory. Either the target path contains the last
1852 # component of the source path or it does not.
1852 # component of the source path or it does not.
1853 def evalpath(striplen):
1853 def evalpath(striplen):
1854 score = 0
1854 score = 0
1855 for s in srcs:
1855 for s in srcs:
1856 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1856 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1857 if os.path.lexists(t):
1857 if os.path.lexists(t):
1858 score += 1
1858 score += 1
1859 return score
1859 return score
1860
1860
1861 abspfx = util.localpath(abspfx)
1861 abspfx = util.localpath(abspfx)
1862 striplen = len(abspfx)
1862 striplen = len(abspfx)
1863 if striplen:
1863 if striplen:
1864 striplen += len(pycompat.ossep)
1864 striplen += len(pycompat.ossep)
1865 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1865 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1866 score = evalpath(striplen)
1866 score = evalpath(striplen)
1867 striplen1 = len(os.path.split(abspfx)[0])
1867 striplen1 = len(os.path.split(abspfx)[0])
1868 if striplen1:
1868 if striplen1:
1869 striplen1 += len(pycompat.ossep)
1869 striplen1 += len(pycompat.ossep)
1870 if evalpath(striplen1) > score:
1870 if evalpath(striplen1) > score:
1871 striplen = striplen1
1871 striplen = striplen1
1872 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1872 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1873 else:
1873 else:
1874 # a file
1874 # a file
1875 if destdirexists:
1875 if destdirexists:
1876 res = lambda p: os.path.join(
1876 res = lambda p: os.path.join(
1877 dest, os.path.basename(util.localpath(p))
1877 dest, os.path.basename(util.localpath(p))
1878 )
1878 )
1879 else:
1879 else:
1880 res = lambda p: dest
1880 res = lambda p: dest
1881 return res
1881 return res
1882
1882
1883 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1883 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1884 if not destdirexists:
1884 if not destdirexists:
1885 if len(pats) > 1 or matchmod.patkind(pats[0]):
1885 if len(pats) > 1 or matchmod.patkind(pats[0]):
1886 raise error.InputError(
1886 raise error.InputError(
1887 _(
1887 _(
1888 b'with multiple sources, destination must be an '
1888 b'with multiple sources, destination must be an '
1889 b'existing directory'
1889 b'existing directory'
1890 )
1890 )
1891 )
1891 )
1892 if util.endswithsep(dest):
1892 if util.endswithsep(dest):
1893 raise error.InputError(
1893 raise error.InputError(
1894 _(b'destination %s is not a directory') % dest
1894 _(b'destination %s is not a directory') % dest
1895 )
1895 )
1896
1896
1897 tfn = targetpathfn
1897 tfn = targetpathfn
1898 if after:
1898 if after:
1899 tfn = targetpathafterfn
1899 tfn = targetpathafterfn
1900 copylist = []
1900 copylist = []
1901 for pat in pats:
1901 for pat in pats:
1902 srcs = walkpat(pat)
1902 srcs = walkpat(pat)
1903 if not srcs:
1903 if not srcs:
1904 continue
1904 continue
1905 copylist.append((tfn(pat, dest, srcs), srcs))
1905 copylist.append((tfn(pat, dest, srcs), srcs))
1906 if not copylist:
1906 if not copylist:
1907 hint = None
1907 hint = None
1908 if rename:
1908 if rename:
1909 hint = _(b'maybe you meant to use --after --at-rev=.')
1909 hint = _(b'maybe you meant to use --after --at-rev=.')
1910 raise error.InputError(_(b'no files to copy'), hint=hint)
1910 raise error.InputError(_(b'no files to copy'), hint=hint)
1911
1911
1912 errors = 0
1912 errors = 0
1913 for targetpath, srcs in copylist:
1913 for targetpath, srcs in copylist:
1914 for abssrc, relsrc, exact in srcs:
1914 for abssrc, relsrc, exact in srcs:
1915 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1915 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1916 errors += 1
1916 errors += 1
1917
1917
1918 return errors != 0
1918 return errors != 0
1919
1919
1920
1920
1921 ## facility to let extension process additional data into an import patch
1921 ## facility to let extension process additional data into an import patch
1922 # list of identifier to be executed in order
1922 # list of identifier to be executed in order
1923 extrapreimport = [] # run before commit
1923 extrapreimport = [] # run before commit
1924 extrapostimport = [] # run after commit
1924 extrapostimport = [] # run after commit
1925 # mapping from identifier to actual import function
1925 # mapping from identifier to actual import function
1926 #
1926 #
1927 # 'preimport' are run before the commit is made and are provided the following
1927 # 'preimport' are run before the commit is made and are provided the following
1928 # arguments:
1928 # arguments:
1929 # - repo: the localrepository instance,
1929 # - repo: the localrepository instance,
1930 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1930 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1931 # - extra: the future extra dictionary of the changeset, please mutate it,
1931 # - extra: the future extra dictionary of the changeset, please mutate it,
1932 # - opts: the import options.
1932 # - opts: the import options.
1933 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1933 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1934 # mutation of in memory commit and more. Feel free to rework the code to get
1934 # mutation of in memory commit and more. Feel free to rework the code to get
1935 # there.
1935 # there.
1936 extrapreimportmap = {}
1936 extrapreimportmap = {}
1937 # 'postimport' are run after the commit is made and are provided the following
1937 # 'postimport' are run after the commit is made and are provided the following
1938 # argument:
1938 # argument:
1939 # - ctx: the changectx created by import.
1939 # - ctx: the changectx created by import.
1940 extrapostimportmap = {}
1940 extrapostimportmap = {}
1941
1941
1942
1942
1943 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1943 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1944 """Utility function used by commands.import to import a single patch
1944 """Utility function used by commands.import to import a single patch
1945
1945
1946 This function is explicitly defined here to help the evolve extension to
1946 This function is explicitly defined here to help the evolve extension to
1947 wrap this part of the import logic.
1947 wrap this part of the import logic.
1948
1948
1949 The API is currently a bit ugly because it a simple code translation from
1949 The API is currently a bit ugly because it a simple code translation from
1950 the import command. Feel free to make it better.
1950 the import command. Feel free to make it better.
1951
1951
1952 :patchdata: a dictionary containing parsed patch data (such as from
1952 :patchdata: a dictionary containing parsed patch data (such as from
1953 ``patch.extract()``)
1953 ``patch.extract()``)
1954 :parents: nodes that will be parent of the created commit
1954 :parents: nodes that will be parent of the created commit
1955 :opts: the full dict of option passed to the import command
1955 :opts: the full dict of option passed to the import command
1956 :msgs: list to save commit message to.
1956 :msgs: list to save commit message to.
1957 (used in case we need to save it when failing)
1957 (used in case we need to save it when failing)
1958 :updatefunc: a function that update a repo to a given node
1958 :updatefunc: a function that update a repo to a given node
1959 updatefunc(<repo>, <node>)
1959 updatefunc(<repo>, <node>)
1960 """
1960 """
1961 # avoid cycle context -> subrepo -> cmdutil
1961 # avoid cycle context -> subrepo -> cmdutil
1962 from . import context
1962 from . import context
1963
1963
1964 tmpname = patchdata.get(b'filename')
1964 tmpname = patchdata.get(b'filename')
1965 message = patchdata.get(b'message')
1965 message = patchdata.get(b'message')
1966 user = opts.get(b'user') or patchdata.get(b'user')
1966 user = opts.get(b'user') or patchdata.get(b'user')
1967 date = opts.get(b'date') or patchdata.get(b'date')
1967 date = opts.get(b'date') or patchdata.get(b'date')
1968 branch = patchdata.get(b'branch')
1968 branch = patchdata.get(b'branch')
1969 nodeid = patchdata.get(b'nodeid')
1969 nodeid = patchdata.get(b'nodeid')
1970 p1 = patchdata.get(b'p1')
1970 p1 = patchdata.get(b'p1')
1971 p2 = patchdata.get(b'p2')
1971 p2 = patchdata.get(b'p2')
1972
1972
1973 nocommit = opts.get(b'no_commit')
1973 nocommit = opts.get(b'no_commit')
1974 importbranch = opts.get(b'import_branch')
1974 importbranch = opts.get(b'import_branch')
1975 update = not opts.get(b'bypass')
1975 update = not opts.get(b'bypass')
1976 strip = opts[b"strip"]
1976 strip = opts[b"strip"]
1977 prefix = opts[b"prefix"]
1977 prefix = opts[b"prefix"]
1978 sim = float(opts.get(b'similarity') or 0)
1978 sim = float(opts.get(b'similarity') or 0)
1979
1979
1980 if not tmpname:
1980 if not tmpname:
1981 return None, None, False
1981 return None, None, False
1982
1982
1983 rejects = False
1983 rejects = False
1984
1984
1985 cmdline_message = logmessage(ui, opts)
1985 cmdline_message = logmessage(ui, opts)
1986 if cmdline_message:
1986 if cmdline_message:
1987 # pickup the cmdline msg
1987 # pickup the cmdline msg
1988 message = cmdline_message
1988 message = cmdline_message
1989 elif message:
1989 elif message:
1990 # pickup the patch msg
1990 # pickup the patch msg
1991 message = message.strip()
1991 message = message.strip()
1992 else:
1992 else:
1993 # launch the editor
1993 # launch the editor
1994 message = None
1994 message = None
1995 ui.debug(b'message:\n%s\n' % (message or b''))
1995 ui.debug(b'message:\n%s\n' % (message or b''))
1996
1996
1997 if len(parents) == 1:
1997 if len(parents) == 1:
1998 parents.append(repo[nullrev])
1998 parents.append(repo[nullrev])
1999 if opts.get(b'exact'):
1999 if opts.get(b'exact'):
2000 if not nodeid or not p1:
2000 if not nodeid or not p1:
2001 raise error.InputError(_(b'not a Mercurial patch'))
2001 raise error.InputError(_(b'not a Mercurial patch'))
2002 p1 = repo[p1]
2002 p1 = repo[p1]
2003 p2 = repo[p2 or nullrev]
2003 p2 = repo[p2 or nullrev]
2004 elif p2:
2004 elif p2:
2005 try:
2005 try:
2006 p1 = repo[p1]
2006 p1 = repo[p1]
2007 p2 = repo[p2]
2007 p2 = repo[p2]
2008 # Without any options, consider p2 only if the
2008 # Without any options, consider p2 only if the
2009 # patch is being applied on top of the recorded
2009 # patch is being applied on top of the recorded
2010 # first parent.
2010 # first parent.
2011 if p1 != parents[0]:
2011 if p1 != parents[0]:
2012 p1 = parents[0]
2012 p1 = parents[0]
2013 p2 = repo[nullrev]
2013 p2 = repo[nullrev]
2014 except error.RepoError:
2014 except error.RepoError:
2015 p1, p2 = parents
2015 p1, p2 = parents
2016 if p2.rev() == nullrev:
2016 if p2.rev() == nullrev:
2017 ui.warn(
2017 ui.warn(
2018 _(
2018 _(
2019 b"warning: import the patch as a normal revision\n"
2019 b"warning: import the patch as a normal revision\n"
2020 b"(use --exact to import the patch as a merge)\n"
2020 b"(use --exact to import the patch as a merge)\n"
2021 )
2021 )
2022 )
2022 )
2023 else:
2023 else:
2024 p1, p2 = parents
2024 p1, p2 = parents
2025
2025
2026 n = None
2026 n = None
2027 if update:
2027 if update:
2028 if p1 != parents[0]:
2028 if p1 != parents[0]:
2029 updatefunc(repo, p1.node())
2029 updatefunc(repo, p1.node())
2030 if p2 != parents[1]:
2030 if p2 != parents[1]:
2031 repo.setparents(p1.node(), p2.node())
2031 repo.setparents(p1.node(), p2.node())
2032
2032
2033 if opts.get(b'exact') or importbranch:
2033 if opts.get(b'exact') or importbranch:
2034 repo.dirstate.setbranch(
2034 repo.dirstate.setbranch(
2035 branch or b'default', repo.currenttransaction()
2035 branch or b'default', repo.currenttransaction()
2036 )
2036 )
2037
2037
2038 partial = opts.get(b'partial', False)
2038 partial = opts.get(b'partial', False)
2039 files = set()
2039 files = set()
2040 try:
2040 try:
2041 patch.patch(
2041 patch.patch(
2042 ui,
2042 ui,
2043 repo,
2043 repo,
2044 tmpname,
2044 tmpname,
2045 strip=strip,
2045 strip=strip,
2046 prefix=prefix,
2046 prefix=prefix,
2047 files=files,
2047 files=files,
2048 eolmode=None,
2048 eolmode=None,
2049 similarity=sim / 100.0,
2049 similarity=sim / 100.0,
2050 )
2050 )
2051 except error.PatchParseError as e:
2051 except error.PatchParseError as e:
2052 raise error.InputError(
2052 raise error.InputError(
2053 pycompat.bytestr(e),
2053 pycompat.bytestr(e),
2054 hint=_(
2054 hint=_(
2055 b'check that whitespace in the patch has not been mangled'
2055 b'check that whitespace in the patch has not been mangled'
2056 ),
2056 ),
2057 )
2057 )
2058 except error.PatchApplicationError as e:
2058 except error.PatchApplicationError as e:
2059 if not partial:
2059 if not partial:
2060 raise error.StateError(pycompat.bytestr(e))
2060 raise error.StateError(pycompat.bytestr(e))
2061 if partial:
2061 if partial:
2062 rejects = True
2062 rejects = True
2063
2063
2064 files = list(files)
2064 files = list(files)
2065 if nocommit:
2065 if nocommit:
2066 if message:
2066 if message:
2067 msgs.append(message)
2067 msgs.append(message)
2068 else:
2068 else:
2069 if opts.get(b'exact') or p2:
2069 if opts.get(b'exact') or p2:
2070 # If you got here, you either use --force and know what
2070 # If you got here, you either use --force and know what
2071 # you are doing or used --exact or a merge patch while
2071 # you are doing or used --exact or a merge patch while
2072 # being updated to its first parent.
2072 # being updated to its first parent.
2073 m = None
2073 m = None
2074 else:
2074 else:
2075 m = scmutil.matchfiles(repo, files or [])
2075 m = scmutil.matchfiles(repo, files or [])
2076 editform = mergeeditform(repo[None], b'import.normal')
2076 editform = mergeeditform(repo[None], b'import.normal')
2077 if opts.get(b'exact'):
2077 if opts.get(b'exact'):
2078 editor = None
2078 editor = None
2079 else:
2079 else:
2080 editor = getcommiteditor(
2080 editor = getcommiteditor(
2081 editform=editform, **pycompat.strkwargs(opts)
2081 editform=editform, **pycompat.strkwargs(opts)
2082 )
2082 )
2083 extra = {}
2083 extra = {}
2084 for idfunc in extrapreimport:
2084 for idfunc in extrapreimport:
2085 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
2085 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
2086 overrides = {}
2086 overrides = {}
2087 if partial:
2087 if partial:
2088 overrides[(b'ui', b'allowemptycommit')] = True
2088 overrides[(b'ui', b'allowemptycommit')] = True
2089 if opts.get(b'secret'):
2089 if opts.get(b'secret'):
2090 overrides[(b'phases', b'new-commit')] = b'secret'
2090 overrides[(b'phases', b'new-commit')] = b'secret'
2091 with repo.ui.configoverride(overrides, b'import'):
2091 with repo.ui.configoverride(overrides, b'import'):
2092 n = repo.commit(
2092 n = repo.commit(
2093 message, user, date, match=m, editor=editor, extra=extra
2093 message, user, date, match=m, editor=editor, extra=extra
2094 )
2094 )
2095 for idfunc in extrapostimport:
2095 for idfunc in extrapostimport:
2096 extrapostimportmap[idfunc](repo[n])
2096 extrapostimportmap[idfunc](repo[n])
2097 else:
2097 else:
2098 if opts.get(b'exact') or importbranch:
2098 if opts.get(b'exact') or importbranch:
2099 branch = branch or b'default'
2099 branch = branch or b'default'
2100 else:
2100 else:
2101 branch = p1.branch()
2101 branch = p1.branch()
2102 store = patch.filestore()
2102 store = patch.filestore()
2103 try:
2103 try:
2104 files = set()
2104 files = set()
2105 try:
2105 try:
2106 patch.patchrepo(
2106 patch.patchrepo(
2107 ui,
2107 ui,
2108 repo,
2108 repo,
2109 p1,
2109 p1,
2110 store,
2110 store,
2111 tmpname,
2111 tmpname,
2112 strip,
2112 strip,
2113 prefix,
2113 prefix,
2114 files,
2114 files,
2115 eolmode=None,
2115 eolmode=None,
2116 )
2116 )
2117 except error.PatchParseError as e:
2117 except error.PatchParseError as e:
2118 raise error.InputError(
2118 raise error.InputError(
2119 stringutil.forcebytestr(e),
2119 stringutil.forcebytestr(e),
2120 hint=_(
2120 hint=_(
2121 b'check that whitespace in the patch has not been mangled'
2121 b'check that whitespace in the patch has not been mangled'
2122 ),
2122 ),
2123 )
2123 )
2124 except error.PatchApplicationError as e:
2124 except error.PatchApplicationError as e:
2125 raise error.StateError(stringutil.forcebytestr(e))
2125 raise error.StateError(stringutil.forcebytestr(e))
2126 if opts.get(b'exact'):
2126 if opts.get(b'exact'):
2127 editor = None
2127 editor = None
2128 else:
2128 else:
2129 editor = getcommiteditor(editform=b'import.bypass')
2129 editor = getcommiteditor(editform=b'import.bypass')
2130 memctx = context.memctx(
2130 memctx = context.memctx(
2131 repo,
2131 repo,
2132 (p1.node(), p2.node()),
2132 (p1.node(), p2.node()),
2133 message,
2133 message,
2134 files=files,
2134 files=files,
2135 filectxfn=store,
2135 filectxfn=store,
2136 user=user,
2136 user=user,
2137 date=date,
2137 date=date,
2138 branch=branch,
2138 branch=branch,
2139 editor=editor,
2139 editor=editor,
2140 )
2140 )
2141
2141
2142 overrides = {}
2142 overrides = {}
2143 if opts.get(b'secret'):
2143 if opts.get(b'secret'):
2144 overrides[(b'phases', b'new-commit')] = b'secret'
2144 overrides[(b'phases', b'new-commit')] = b'secret'
2145 with repo.ui.configoverride(overrides, b'import'):
2145 with repo.ui.configoverride(overrides, b'import'):
2146 n = memctx.commit()
2146 n = memctx.commit()
2147 finally:
2147 finally:
2148 store.close()
2148 store.close()
2149 if opts.get(b'exact') and nocommit:
2149 if opts.get(b'exact') and nocommit:
2150 # --exact with --no-commit is still useful in that it does merge
2150 # --exact with --no-commit is still useful in that it does merge
2151 # and branch bits
2151 # and branch bits
2152 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2152 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2153 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2153 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2154 raise error.Abort(_(b'patch is damaged or loses information'))
2154 raise error.Abort(_(b'patch is damaged or loses information'))
2155 msg = _(b'applied to working directory')
2155 msg = _(b'applied to working directory')
2156 if n:
2156 if n:
2157 # i18n: refers to a short changeset id
2157 # i18n: refers to a short changeset id
2158 msg = _(b'created %s') % short(n)
2158 msg = _(b'created %s') % short(n)
2159 return msg, n, rejects
2159 return msg, n, rejects
2160
2160
2161
2161
2162 # facility to let extensions include additional data in an exported patch
2162 # facility to let extensions include additional data in an exported patch
2163 # list of identifiers to be executed in order
2163 # list of identifiers to be executed in order
2164 extraexport = []
2164 extraexport = []
2165 # mapping from identifier to actual export function
2165 # mapping from identifier to actual export function
2166 # function as to return a string to be added to the header or None
2166 # function as to return a string to be added to the header or None
2167 # it is given two arguments (sequencenumber, changectx)
2167 # it is given two arguments (sequencenumber, changectx)
2168 extraexportmap = {}
2168 extraexportmap = {}
2169
2169
2170
2170
2171 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2171 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2172 node = scmutil.binnode(ctx)
2172 node = scmutil.binnode(ctx)
2173 parents = [p.node() for p in ctx.parents() if p]
2173 parents = [p.node() for p in ctx.parents() if p]
2174 branch = ctx.branch()
2174 branch = ctx.branch()
2175 if switch_parent:
2175 if switch_parent:
2176 parents.reverse()
2176 parents.reverse()
2177
2177
2178 if parents:
2178 if parents:
2179 prev = parents[0]
2179 prev = parents[0]
2180 else:
2180 else:
2181 prev = repo.nullid
2181 prev = repo.nullid
2182
2182
2183 fm.context(ctx=ctx)
2183 fm.context(ctx=ctx)
2184 fm.plain(b'# HG changeset patch\n')
2184 fm.plain(b'# HG changeset patch\n')
2185 fm.write(b'user', b'# User %s\n', ctx.user())
2185 fm.write(b'user', b'# User %s\n', ctx.user())
2186 fm.plain(b'# Date %d %d\n' % ctx.date())
2186 fm.plain(b'# Date %d %d\n' % ctx.date())
2187 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2187 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2188 fm.condwrite(
2188 fm.condwrite(
2189 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2189 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2190 )
2190 )
2191 fm.write(b'node', b'# Node ID %s\n', hex(node))
2191 fm.write(b'node', b'# Node ID %s\n', hex(node))
2192 fm.plain(b'# Parent %s\n' % hex(prev))
2192 fm.plain(b'# Parent %s\n' % hex(prev))
2193 if len(parents) > 1:
2193 if len(parents) > 1:
2194 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2194 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2195 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2195 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2196
2196
2197 # TODO: redesign extraexportmap function to support formatter
2197 # TODO: redesign extraexportmap function to support formatter
2198 for headerid in extraexport:
2198 for headerid in extraexport:
2199 header = extraexportmap[headerid](seqno, ctx)
2199 header = extraexportmap[headerid](seqno, ctx)
2200 if header is not None:
2200 if header is not None:
2201 fm.plain(b'# %s\n' % header)
2201 fm.plain(b'# %s\n' % header)
2202
2202
2203 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2203 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2204 fm.plain(b'\n')
2204 fm.plain(b'\n')
2205
2205
2206 if fm.isplain():
2206 if fm.isplain():
2207 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2207 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2208 for chunk, label in chunkiter:
2208 for chunk, label in chunkiter:
2209 fm.plain(chunk, label=label)
2209 fm.plain(chunk, label=label)
2210 else:
2210 else:
2211 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2211 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2212 # TODO: make it structured?
2212 # TODO: make it structured?
2213 fm.data(diff=b''.join(chunkiter))
2213 fm.data(diff=b''.join(chunkiter))
2214
2214
2215
2215
2216 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2216 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2217 """Export changesets to stdout or a single file"""
2217 """Export changesets to stdout or a single file"""
2218 for seqno, rev in enumerate(revs, 1):
2218 for seqno, rev in enumerate(revs, 1):
2219 ctx = repo[rev]
2219 ctx = repo[rev]
2220 if not dest.startswith(b'<'):
2220 if not dest.startswith(b'<'):
2221 repo.ui.note(b"%s\n" % dest)
2221 repo.ui.note(b"%s\n" % dest)
2222 fm.startitem()
2222 fm.startitem()
2223 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2223 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2224
2224
2225
2225
2226 def _exportfntemplate(
2226 def _exportfntemplate(
2227 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2227 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2228 ):
2228 ):
2229 """Export changesets to possibly multiple files"""
2229 """Export changesets to possibly multiple files"""
2230 total = len(revs)
2230 total = len(revs)
2231 revwidth = max(len(str(rev)) for rev in revs)
2231 revwidth = max(len(str(rev)) for rev in revs)
2232 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2232 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2233
2233
2234 for seqno, rev in enumerate(revs, 1):
2234 for seqno, rev in enumerate(revs, 1):
2235 ctx = repo[rev]
2235 ctx = repo[rev]
2236 dest = makefilename(
2236 dest = makefilename(
2237 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2237 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2238 )
2238 )
2239 filemap.setdefault(dest, []).append((seqno, rev))
2239 filemap.setdefault(dest, []).append((seqno, rev))
2240
2240
2241 for dest in filemap:
2241 for dest in filemap:
2242 with formatter.maybereopen(basefm, dest) as fm:
2242 with formatter.maybereopen(basefm, dest) as fm:
2243 repo.ui.note(b"%s\n" % dest)
2243 repo.ui.note(b"%s\n" % dest)
2244 for seqno, rev in filemap[dest]:
2244 for seqno, rev in filemap[dest]:
2245 fm.startitem()
2245 fm.startitem()
2246 ctx = repo[rev]
2246 ctx = repo[rev]
2247 _exportsingle(
2247 _exportsingle(
2248 repo, ctx, fm, match, switch_parent, seqno, diffopts
2248 repo, ctx, fm, match, switch_parent, seqno, diffopts
2249 )
2249 )
2250
2250
2251
2251
2252 def _prefetchchangedfiles(repo, revs, match):
2252 def _prefetchchangedfiles(repo, revs, match):
2253 allfiles = set()
2253 allfiles = set()
2254 for rev in revs:
2254 for rev in revs:
2255 for file in repo[rev].files():
2255 for file in repo[rev].files():
2256 if not match or match(file):
2256 if not match or match(file):
2257 allfiles.add(file)
2257 allfiles.add(file)
2258 match = scmutil.matchfiles(repo, allfiles)
2258 match = scmutil.matchfiles(repo, allfiles)
2259 revmatches = [(rev, match) for rev in revs]
2259 revmatches = [(rev, match) for rev in revs]
2260 scmutil.prefetchfiles(repo, revmatches)
2260 scmutil.prefetchfiles(repo, revmatches)
2261
2261
2262
2262
2263 def export(
2263 def export(
2264 repo,
2264 repo,
2265 revs,
2265 revs,
2266 basefm,
2266 basefm,
2267 fntemplate=b'hg-%h.patch',
2267 fntemplate=b'hg-%h.patch',
2268 switch_parent=False,
2268 switch_parent=False,
2269 opts=None,
2269 opts=None,
2270 match=None,
2270 match=None,
2271 ):
2271 ):
2272 """export changesets as hg patches
2272 """export changesets as hg patches
2273
2273
2274 Args:
2274 Args:
2275 repo: The repository from which we're exporting revisions.
2275 repo: The repository from which we're exporting revisions.
2276 revs: A list of revisions to export as revision numbers.
2276 revs: A list of revisions to export as revision numbers.
2277 basefm: A formatter to which patches should be written.
2277 basefm: A formatter to which patches should be written.
2278 fntemplate: An optional string to use for generating patch file names.
2278 fntemplate: An optional string to use for generating patch file names.
2279 switch_parent: If True, show diffs against second parent when not nullid.
2279 switch_parent: If True, show diffs against second parent when not nullid.
2280 Default is false, which always shows diff against p1.
2280 Default is false, which always shows diff against p1.
2281 opts: diff options to use for generating the patch.
2281 opts: diff options to use for generating the patch.
2282 match: If specified, only export changes to files matching this matcher.
2282 match: If specified, only export changes to files matching this matcher.
2283
2283
2284 Returns:
2284 Returns:
2285 Nothing.
2285 Nothing.
2286
2286
2287 Side Effect:
2287 Side Effect:
2288 "HG Changeset Patch" data is emitted to one of the following
2288 "HG Changeset Patch" data is emitted to one of the following
2289 destinations:
2289 destinations:
2290 fntemplate specified: Each rev is written to a unique file named using
2290 fntemplate specified: Each rev is written to a unique file named using
2291 the given template.
2291 the given template.
2292 Otherwise: All revs will be written to basefm.
2292 Otherwise: All revs will be written to basefm.
2293 """
2293 """
2294 _prefetchchangedfiles(repo, revs, match)
2294 _prefetchchangedfiles(repo, revs, match)
2295
2295
2296 if not fntemplate:
2296 if not fntemplate:
2297 _exportfile(
2297 _exportfile(
2298 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2298 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2299 )
2299 )
2300 else:
2300 else:
2301 _exportfntemplate(
2301 _exportfntemplate(
2302 repo, revs, basefm, fntemplate, switch_parent, opts, match
2302 repo, revs, basefm, fntemplate, switch_parent, opts, match
2303 )
2303 )
2304
2304
2305
2305
2306 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2306 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2307 """Export changesets to the given file stream"""
2307 """Export changesets to the given file stream"""
2308 _prefetchchangedfiles(repo, revs, match)
2308 _prefetchchangedfiles(repo, revs, match)
2309
2309
2310 dest = getattr(fp, 'name', b'<unnamed>')
2310 dest = getattr(fp, 'name', b'<unnamed>')
2311 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2311 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2312 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2312 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2313
2313
2314
2314
2315 def showmarker(fm, marker, index=None):
2315 def showmarker(fm, marker, index=None):
2316 """utility function to display obsolescence marker in a readable way
2316 """utility function to display obsolescence marker in a readable way
2317
2317
2318 To be used by debug function."""
2318 To be used by debug function."""
2319 if index is not None:
2319 if index is not None:
2320 fm.write(b'index', b'%i ', index)
2320 fm.write(b'index', b'%i ', index)
2321 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2321 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2322 succs = marker.succnodes()
2322 succs = marker.succnodes()
2323 fm.condwrite(
2323 fm.condwrite(
2324 succs,
2324 succs,
2325 b'succnodes',
2325 b'succnodes',
2326 b'%s ',
2326 b'%s ',
2327 fm.formatlist(map(hex, succs), name=b'node'),
2327 fm.formatlist(map(hex, succs), name=b'node'),
2328 )
2328 )
2329 fm.write(b'flag', b'%X ', marker.flags())
2329 fm.write(b'flag', b'%X ', marker.flags())
2330 parents = marker.parentnodes()
2330 parents = marker.parentnodes()
2331 if parents is not None:
2331 if parents is not None:
2332 fm.write(
2332 fm.write(
2333 b'parentnodes',
2333 b'parentnodes',
2334 b'{%s} ',
2334 b'{%s} ',
2335 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2335 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2336 )
2336 )
2337 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2337 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2338 meta = marker.metadata().copy()
2338 meta = marker.metadata().copy()
2339 meta.pop(b'date', None)
2339 meta.pop(b'date', None)
2340 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2340 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2341 fm.write(
2341 fm.write(
2342 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2342 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2343 )
2343 )
2344 fm.plain(b'\n')
2344 fm.plain(b'\n')
2345
2345
2346
2346
2347 def finddate(ui, repo, date):
2347 def finddate(ui, repo, date):
2348 """Find the tipmost changeset that matches the given date spec"""
2348 """Find the tipmost changeset that matches the given date spec"""
2349 mrevs = repo.revs(b'date(%s)', date)
2349 mrevs = repo.revs(b'date(%s)', date)
2350 try:
2350 try:
2351 rev = mrevs.max()
2351 rev = mrevs.max()
2352 except ValueError:
2352 except ValueError:
2353 raise error.InputError(_(b"revision matching date not found"))
2353 raise error.InputError(_(b"revision matching date not found"))
2354
2354
2355 ui.status(
2355 ui.status(
2356 _(b"found revision %d from %s\n")
2356 _(b"found revision %d from %s\n")
2357 % (rev, dateutil.datestr(repo[rev].date()))
2357 % (rev, dateutil.datestr(repo[rev].date()))
2358 )
2358 )
2359 return b'%d' % rev
2359 return b'%d' % rev
2360
2360
2361
2361
2362 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2362 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2363 bad = []
2363 bad = []
2364
2364
2365 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2365 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2366 names = []
2366 names = []
2367 wctx = repo[None]
2367 wctx = repo[None]
2368 cca = None
2368 cca = None
2369 abort, warn = scmutil.checkportabilityalert(ui)
2369 abort, warn = scmutil.checkportabilityalert(ui)
2370 if abort or warn:
2370 if abort or warn:
2371 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2371 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2372
2372
2373 match = repo.narrowmatch(match, includeexact=True)
2373 match = repo.narrowmatch(match, includeexact=True)
2374 badmatch = matchmod.badmatch(match, badfn)
2374 badmatch = matchmod.badmatch(match, badfn)
2375 dirstate = repo.dirstate
2375 dirstate = repo.dirstate
2376 # We don't want to just call wctx.walk here, since it would return a lot of
2376 # We don't want to just call wctx.walk here, since it would return a lot of
2377 # clean files, which we aren't interested in and takes time.
2377 # clean files, which we aren't interested in and takes time.
2378 for f in sorted(
2378 for f in sorted(
2379 dirstate.walk(
2379 dirstate.walk(
2380 badmatch,
2380 badmatch,
2381 subrepos=sorted(wctx.substate),
2381 subrepos=sorted(wctx.substate),
2382 unknown=True,
2382 unknown=True,
2383 ignored=False,
2383 ignored=False,
2384 full=False,
2384 full=False,
2385 )
2385 )
2386 ):
2386 ):
2387 exact = match.exact(f)
2387 exact = match.exact(f)
2388 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2388 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2389 if cca:
2389 if cca:
2390 cca(f)
2390 cca(f)
2391 names.append(f)
2391 names.append(f)
2392 if ui.verbose or not exact:
2392 if ui.verbose or not exact:
2393 ui.status(
2393 ui.status(
2394 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2394 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2395 )
2395 )
2396
2396
2397 for subpath in sorted(wctx.substate):
2397 for subpath in sorted(wctx.substate):
2398 sub = wctx.sub(subpath)
2398 sub = wctx.sub(subpath)
2399 try:
2399 try:
2400 submatch = matchmod.subdirmatcher(subpath, match)
2400 submatch = matchmod.subdirmatcher(subpath, match)
2401 subprefix = repo.wvfs.reljoin(prefix, subpath)
2401 subprefix = repo.wvfs.reljoin(prefix, subpath)
2402 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2402 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2403 if opts.get('subrepos'):
2403 if opts.get('subrepos'):
2404 bad.extend(
2404 bad.extend(
2405 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2405 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2406 )
2406 )
2407 else:
2407 else:
2408 bad.extend(
2408 bad.extend(
2409 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2409 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2410 )
2410 )
2411 except error.LookupError:
2411 except error.LookupError:
2412 ui.status(
2412 ui.status(
2413 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2413 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2414 )
2414 )
2415
2415
2416 if not opts.get('dry_run'):
2416 if not opts.get('dry_run'):
2417 rejected = wctx.add(names, prefix)
2417 rejected = wctx.add(names, prefix)
2418 bad.extend(f for f in rejected if f in match.files())
2418 bad.extend(f for f in rejected if f in match.files())
2419 return bad
2419 return bad
2420
2420
2421
2421
2422 def addwebdirpath(repo, serverpath, webconf):
2422 def addwebdirpath(repo, serverpath, webconf):
2423 webconf[serverpath] = repo.root
2423 webconf[serverpath] = repo.root
2424 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2424 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2425
2425
2426 for r in repo.revs(b'filelog("path:.hgsub")'):
2426 for r in repo.revs(b'filelog("path:.hgsub")'):
2427 ctx = repo[r]
2427 ctx = repo[r]
2428 for subpath in ctx.substate:
2428 for subpath in ctx.substate:
2429 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2429 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2430
2430
2431
2431
2432 def forget(
2432 def forget(
2433 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2433 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2434 ):
2434 ):
2435 if dryrun and interactive:
2435 if dryrun and interactive:
2436 raise error.InputError(
2436 raise error.InputError(
2437 _(b"cannot specify both --dry-run and --interactive")
2437 _(b"cannot specify both --dry-run and --interactive")
2438 )
2438 )
2439 bad = []
2439 bad = []
2440 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2440 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2441 wctx = repo[None]
2441 wctx = repo[None]
2442 forgot = []
2442 forgot = []
2443
2443
2444 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2444 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2445 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2445 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2446 if explicitonly:
2446 if explicitonly:
2447 forget = [f for f in forget if match.exact(f)]
2447 forget = [f for f in forget if match.exact(f)]
2448
2448
2449 for subpath in sorted(wctx.substate):
2449 for subpath in sorted(wctx.substate):
2450 sub = wctx.sub(subpath)
2450 sub = wctx.sub(subpath)
2451 submatch = matchmod.subdirmatcher(subpath, match)
2451 submatch = matchmod.subdirmatcher(subpath, match)
2452 subprefix = repo.wvfs.reljoin(prefix, subpath)
2452 subprefix = repo.wvfs.reljoin(prefix, subpath)
2453 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2453 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2454 try:
2454 try:
2455 subbad, subforgot = sub.forget(
2455 subbad, subforgot = sub.forget(
2456 submatch,
2456 submatch,
2457 subprefix,
2457 subprefix,
2458 subuipathfn,
2458 subuipathfn,
2459 dryrun=dryrun,
2459 dryrun=dryrun,
2460 interactive=interactive,
2460 interactive=interactive,
2461 )
2461 )
2462 bad.extend([subpath + b'/' + f for f in subbad])
2462 bad.extend([subpath + b'/' + f for f in subbad])
2463 forgot.extend([subpath + b'/' + f for f in subforgot])
2463 forgot.extend([subpath + b'/' + f for f in subforgot])
2464 except error.LookupError:
2464 except error.LookupError:
2465 ui.status(
2465 ui.status(
2466 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2466 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2467 )
2467 )
2468
2468
2469 if not explicitonly:
2469 if not explicitonly:
2470 for f in match.files():
2470 for f in match.files():
2471 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2471 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2472 if f not in forgot:
2472 if f not in forgot:
2473 if repo.wvfs.exists(f):
2473 if repo.wvfs.exists(f):
2474 # Don't complain if the exact case match wasn't given.
2474 # Don't complain if the exact case match wasn't given.
2475 # But don't do this until after checking 'forgot', so
2475 # But don't do this until after checking 'forgot', so
2476 # that subrepo files aren't normalized, and this op is
2476 # that subrepo files aren't normalized, and this op is
2477 # purely from data cached by the status walk above.
2477 # purely from data cached by the status walk above.
2478 if repo.dirstate.normalize(f) in repo.dirstate:
2478 if repo.dirstate.normalize(f) in repo.dirstate:
2479 continue
2479 continue
2480 ui.warn(
2480 ui.warn(
2481 _(
2481 _(
2482 b'not removing %s: '
2482 b'not removing %s: '
2483 b'file is already untracked\n'
2483 b'file is already untracked\n'
2484 )
2484 )
2485 % uipathfn(f)
2485 % uipathfn(f)
2486 )
2486 )
2487 bad.append(f)
2487 bad.append(f)
2488
2488
2489 if interactive:
2489 if interactive:
2490 responses = _(
2490 responses = _(
2491 b'[Ynsa?]'
2491 b'[Ynsa?]'
2492 b'$$ &Yes, forget this file'
2492 b'$$ &Yes, forget this file'
2493 b'$$ &No, skip this file'
2493 b'$$ &No, skip this file'
2494 b'$$ &Skip remaining files'
2494 b'$$ &Skip remaining files'
2495 b'$$ Include &all remaining files'
2495 b'$$ Include &all remaining files'
2496 b'$$ &? (display help)'
2496 b'$$ &? (display help)'
2497 )
2497 )
2498 for filename in forget[:]:
2498 for filename in forget[:]:
2499 r = ui.promptchoice(
2499 r = ui.promptchoice(
2500 _(b'forget %s %s') % (uipathfn(filename), responses)
2500 _(b'forget %s %s') % (uipathfn(filename), responses)
2501 )
2501 )
2502 if r == 4: # ?
2502 if r == 4: # ?
2503 while r == 4:
2503 while r == 4:
2504 for c, t in ui.extractchoices(responses)[1]:
2504 for c, t in ui.extractchoices(responses)[1]:
2505 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2505 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2506 r = ui.promptchoice(
2506 r = ui.promptchoice(
2507 _(b'forget %s %s') % (uipathfn(filename), responses)
2507 _(b'forget %s %s') % (uipathfn(filename), responses)
2508 )
2508 )
2509 if r == 0: # yes
2509 if r == 0: # yes
2510 continue
2510 continue
2511 elif r == 1: # no
2511 elif r == 1: # no
2512 forget.remove(filename)
2512 forget.remove(filename)
2513 elif r == 2: # Skip
2513 elif r == 2: # Skip
2514 fnindex = forget.index(filename)
2514 fnindex = forget.index(filename)
2515 del forget[fnindex:]
2515 del forget[fnindex:]
2516 break
2516 break
2517 elif r == 3: # All
2517 elif r == 3: # All
2518 break
2518 break
2519
2519
2520 for f in forget:
2520 for f in forget:
2521 if ui.verbose or not match.exact(f) or interactive:
2521 if ui.verbose or not match.exact(f) or interactive:
2522 ui.status(
2522 ui.status(
2523 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2523 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2524 )
2524 )
2525
2525
2526 if not dryrun:
2526 if not dryrun:
2527 rejected = wctx.forget(forget, prefix)
2527 rejected = wctx.forget(forget, prefix)
2528 bad.extend(f for f in rejected if f in match.files())
2528 bad.extend(f for f in rejected if f in match.files())
2529 forgot.extend(f for f in forget if f not in rejected)
2529 forgot.extend(f for f in forget if f not in rejected)
2530 return bad, forgot
2530 return bad, forgot
2531
2531
2532
2532
2533 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2533 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2534 ret = 1
2534 ret = 1
2535
2535
2536 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2536 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2537 if fm.isplain() and not needsfctx:
2537 if fm.isplain() and not needsfctx:
2538 # Fast path. The speed-up comes from skipping the formatter, and batching
2538 # Fast path. The speed-up comes from skipping the formatter, and batching
2539 # calls to ui.write.
2539 # calls to ui.write.
2540 buf = []
2540 buf = []
2541 for f in ctx.matches(m):
2541 for f in ctx.matches(m):
2542 buf.append(fmt % uipathfn(f))
2542 buf.append(fmt % uipathfn(f))
2543 if len(buf) > 100:
2543 if len(buf) > 100:
2544 ui.write(b''.join(buf))
2544 ui.write(b''.join(buf))
2545 del buf[:]
2545 del buf[:]
2546 ret = 0
2546 ret = 0
2547 if buf:
2547 if buf:
2548 ui.write(b''.join(buf))
2548 ui.write(b''.join(buf))
2549 else:
2549 else:
2550 for f in ctx.matches(m):
2550 for f in ctx.matches(m):
2551 fm.startitem()
2551 fm.startitem()
2552 fm.context(ctx=ctx)
2552 fm.context(ctx=ctx)
2553 if needsfctx:
2553 if needsfctx:
2554 fc = ctx[f]
2554 fc = ctx[f]
2555 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2555 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2556 fm.data(path=f)
2556 fm.data(path=f)
2557 fm.plain(fmt % uipathfn(f))
2557 fm.plain(fmt % uipathfn(f))
2558 ret = 0
2558 ret = 0
2559
2559
2560 for subpath in sorted(ctx.substate):
2560 for subpath in sorted(ctx.substate):
2561 submatch = matchmod.subdirmatcher(subpath, m)
2561 submatch = matchmod.subdirmatcher(subpath, m)
2562 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2562 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2563 if subrepos or m.exact(subpath) or any(submatch.files()):
2563 if subrepos or m.exact(subpath) or any(submatch.files()):
2564 sub = ctx.sub(subpath)
2564 sub = ctx.sub(subpath)
2565 try:
2565 try:
2566 recurse = m.exact(subpath) or subrepos
2566 recurse = m.exact(subpath) or subrepos
2567 if (
2567 if (
2568 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2568 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2569 == 0
2569 == 0
2570 ):
2570 ):
2571 ret = 0
2571 ret = 0
2572 except error.LookupError:
2572 except error.LookupError:
2573 ui.status(
2573 ui.status(
2574 _(b"skipping missing subrepository: %s\n")
2574 _(b"skipping missing subrepository: %s\n")
2575 % uipathfn(subpath)
2575 % uipathfn(subpath)
2576 )
2576 )
2577
2577
2578 return ret
2578 return ret
2579
2579
2580
2580
2581 def remove(
2581 def remove(
2582 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2582 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2583 ):
2583 ):
2584 ret = 0
2584 ret = 0
2585 s = repo.status(match=m, clean=True)
2585 s = repo.status(match=m, clean=True)
2586 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2586 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2587
2587
2588 wctx = repo[None]
2588 wctx = repo[None]
2589
2589
2590 if warnings is None:
2590 if warnings is None:
2591 warnings = []
2591 warnings = []
2592 warn = True
2592 warn = True
2593 else:
2593 else:
2594 warn = False
2594 warn = False
2595
2595
2596 subs = sorted(wctx.substate)
2596 subs = sorted(wctx.substate)
2597 progress = ui.makeprogress(
2597 progress = ui.makeprogress(
2598 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2598 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2599 )
2599 )
2600 for subpath in subs:
2600 for subpath in subs:
2601 submatch = matchmod.subdirmatcher(subpath, m)
2601 submatch = matchmod.subdirmatcher(subpath, m)
2602 subprefix = repo.wvfs.reljoin(prefix, subpath)
2602 subprefix = repo.wvfs.reljoin(prefix, subpath)
2603 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2603 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2604 if subrepos or m.exact(subpath) or any(submatch.files()):
2604 if subrepos or m.exact(subpath) or any(submatch.files()):
2605 progress.increment()
2605 progress.increment()
2606 sub = wctx.sub(subpath)
2606 sub = wctx.sub(subpath)
2607 try:
2607 try:
2608 if sub.removefiles(
2608 if sub.removefiles(
2609 submatch,
2609 submatch,
2610 subprefix,
2610 subprefix,
2611 subuipathfn,
2611 subuipathfn,
2612 after,
2612 after,
2613 force,
2613 force,
2614 subrepos,
2614 subrepos,
2615 dryrun,
2615 dryrun,
2616 warnings,
2616 warnings,
2617 ):
2617 ):
2618 ret = 1
2618 ret = 1
2619 except error.LookupError:
2619 except error.LookupError:
2620 warnings.append(
2620 warnings.append(
2621 _(b"skipping missing subrepository: %s\n")
2621 _(b"skipping missing subrepository: %s\n")
2622 % uipathfn(subpath)
2622 % uipathfn(subpath)
2623 )
2623 )
2624 progress.complete()
2624 progress.complete()
2625
2625
2626 # warn about failure to delete explicit files/dirs
2626 # warn about failure to delete explicit files/dirs
2627 deleteddirs = pathutil.dirs(deleted)
2627 deleteddirs = pathutil.dirs(deleted)
2628 files = m.files()
2628 files = m.files()
2629 progress = ui.makeprogress(
2629 progress = ui.makeprogress(
2630 _(b'deleting'), total=len(files), unit=_(b'files')
2630 _(b'deleting'), total=len(files), unit=_(b'files')
2631 )
2631 )
2632 for f in files:
2632 for f in files:
2633
2633
2634 def insubrepo():
2634 def insubrepo():
2635 for subpath in wctx.substate:
2635 for subpath in wctx.substate:
2636 if f.startswith(subpath + b'/'):
2636 if f.startswith(subpath + b'/'):
2637 return True
2637 return True
2638 return False
2638 return False
2639
2639
2640 progress.increment()
2640 progress.increment()
2641 isdir = f in deleteddirs or wctx.hasdir(f)
2641 isdir = f in deleteddirs or wctx.hasdir(f)
2642 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2642 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2643 continue
2643 continue
2644
2644
2645 if repo.wvfs.exists(f):
2645 if repo.wvfs.exists(f):
2646 if repo.wvfs.isdir(f):
2646 if repo.wvfs.isdir(f):
2647 warnings.append(
2647 warnings.append(
2648 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2648 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2649 )
2649 )
2650 else:
2650 else:
2651 warnings.append(
2651 warnings.append(
2652 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2652 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2653 )
2653 )
2654 # missing files will generate a warning elsewhere
2654 # missing files will generate a warning elsewhere
2655 ret = 1
2655 ret = 1
2656 progress.complete()
2656 progress.complete()
2657
2657
2658 if force:
2658 if force:
2659 list = modified + deleted + clean + added
2659 list = modified + deleted + clean + added
2660 elif after:
2660 elif after:
2661 list = deleted
2661 list = deleted
2662 remaining = modified + added + clean
2662 remaining = modified + added + clean
2663 progress = ui.makeprogress(
2663 progress = ui.makeprogress(
2664 _(b'skipping'), total=len(remaining), unit=_(b'files')
2664 _(b'skipping'), total=len(remaining), unit=_(b'files')
2665 )
2665 )
2666 for f in remaining:
2666 for f in remaining:
2667 progress.increment()
2667 progress.increment()
2668 if ui.verbose or (f in files):
2668 if ui.verbose or (f in files):
2669 warnings.append(
2669 warnings.append(
2670 _(b'not removing %s: file still exists\n') % uipathfn(f)
2670 _(b'not removing %s: file still exists\n') % uipathfn(f)
2671 )
2671 )
2672 ret = 1
2672 ret = 1
2673 progress.complete()
2673 progress.complete()
2674 else:
2674 else:
2675 list = deleted + clean
2675 list = deleted + clean
2676 progress = ui.makeprogress(
2676 progress = ui.makeprogress(
2677 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2677 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2678 )
2678 )
2679 for f in modified:
2679 for f in modified:
2680 progress.increment()
2680 progress.increment()
2681 warnings.append(
2681 warnings.append(
2682 _(
2682 _(
2683 b'not removing %s: file is modified (use -f'
2683 b'not removing %s: file is modified (use -f'
2684 b' to force removal)\n'
2684 b' to force removal)\n'
2685 )
2685 )
2686 % uipathfn(f)
2686 % uipathfn(f)
2687 )
2687 )
2688 ret = 1
2688 ret = 1
2689 for f in added:
2689 for f in added:
2690 progress.increment()
2690 progress.increment()
2691 warnings.append(
2691 warnings.append(
2692 _(
2692 _(
2693 b"not removing %s: file has been marked for add"
2693 b"not removing %s: file has been marked for add"
2694 b" (use 'hg forget' to undo add)\n"
2694 b" (use 'hg forget' to undo add)\n"
2695 )
2695 )
2696 % uipathfn(f)
2696 % uipathfn(f)
2697 )
2697 )
2698 ret = 1
2698 ret = 1
2699 progress.complete()
2699 progress.complete()
2700
2700
2701 list = sorted(list)
2701 list = sorted(list)
2702 progress = ui.makeprogress(
2702 progress = ui.makeprogress(
2703 _(b'deleting'), total=len(list), unit=_(b'files')
2703 _(b'deleting'), total=len(list), unit=_(b'files')
2704 )
2704 )
2705 for f in list:
2705 for f in list:
2706 if ui.verbose or not m.exact(f):
2706 if ui.verbose or not m.exact(f):
2707 progress.increment()
2707 progress.increment()
2708 ui.status(
2708 ui.status(
2709 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2709 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2710 )
2710 )
2711 progress.complete()
2711 progress.complete()
2712
2712
2713 if not dryrun:
2713 if not dryrun:
2714 with repo.wlock():
2714 with repo.wlock():
2715 if not after:
2715 if not after:
2716 for f in list:
2716 for f in list:
2717 if f in added:
2717 if f in added:
2718 continue # we never unlink added files on remove
2718 continue # we never unlink added files on remove
2719 rmdir = repo.ui.configbool(
2719 rmdir = repo.ui.configbool(
2720 b'experimental', b'removeemptydirs'
2720 b'experimental', b'removeemptydirs'
2721 )
2721 )
2722 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2722 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2723 repo[None].forget(list)
2723 repo[None].forget(list)
2724
2724
2725 if warn:
2725 if warn:
2726 for warning in warnings:
2726 for warning in warnings:
2727 ui.warn(warning)
2727 ui.warn(warning)
2728
2728
2729 return ret
2729 return ret
2730
2730
2731
2731
2732 def _catfmtneedsdata(fm):
2732 def _catfmtneedsdata(fm):
2733 return not fm.datahint() or b'data' in fm.datahint()
2733 return not fm.datahint() or b'data' in fm.datahint()
2734
2734
2735
2735
2736 def _updatecatformatter(fm, ctx, matcher, path, decode):
2736 def _updatecatformatter(fm, ctx, matcher, path, decode):
2737 """Hook for adding data to the formatter used by ``hg cat``.
2737 """Hook for adding data to the formatter used by ``hg cat``.
2738
2738
2739 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2739 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2740 this method first."""
2740 this method first."""
2741
2741
2742 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2742 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2743 # wasn't requested.
2743 # wasn't requested.
2744 data = b''
2744 data = b''
2745 if _catfmtneedsdata(fm):
2745 if _catfmtneedsdata(fm):
2746 data = ctx[path].data()
2746 data = ctx[path].data()
2747 if decode:
2747 if decode:
2748 data = ctx.repo().wwritedata(path, data)
2748 data = ctx.repo().wwritedata(path, data)
2749 fm.startitem()
2749 fm.startitem()
2750 fm.context(ctx=ctx)
2750 fm.context(ctx=ctx)
2751 fm.write(b'data', b'%s', data)
2751 fm.write(b'data', b'%s', data)
2752 fm.data(path=path)
2752 fm.data(path=path)
2753
2753
2754
2754
2755 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2755 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2756 err = 1
2756 err = 1
2757
2757
2758 def write(path):
2758 def write(path):
2759 filename = None
2759 filename = None
2760 if fntemplate:
2760 if fntemplate:
2761 filename = makefilename(
2761 filename = makefilename(
2762 ctx, fntemplate, pathname=os.path.join(prefix, path)
2762 ctx, fntemplate, pathname=os.path.join(prefix, path)
2763 )
2763 )
2764 # attempt to create the directory if it does not already exist
2764 # attempt to create the directory if it does not already exist
2765 try:
2765 try:
2766 os.makedirs(os.path.dirname(filename))
2766 os.makedirs(os.path.dirname(filename))
2767 except OSError:
2767 except OSError:
2768 pass
2768 pass
2769 with formatter.maybereopen(basefm, filename) as fm:
2769 with formatter.maybereopen(basefm, filename) as fm:
2770 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2770 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2771
2771
2772 # Automation often uses hg cat on single files, so special case it
2772 # Automation often uses hg cat on single files, so special case it
2773 # for performance to avoid the cost of parsing the manifest.
2773 # for performance to avoid the cost of parsing the manifest.
2774 if len(matcher.files()) == 1 and not matcher.anypats():
2774 if len(matcher.files()) == 1 and not matcher.anypats():
2775 file = matcher.files()[0]
2775 file = matcher.files()[0]
2776 mfl = repo.manifestlog
2776 mfl = repo.manifestlog
2777 mfnode = ctx.manifestnode()
2777 mfnode = ctx.manifestnode()
2778 try:
2778 try:
2779 if mfnode and mfl[mfnode].find(file)[0]:
2779 if mfnode and mfl[mfnode].find(file)[0]:
2780 if _catfmtneedsdata(basefm):
2780 if _catfmtneedsdata(basefm):
2781 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2781 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2782 write(file)
2782 write(file)
2783 return 0
2783 return 0
2784 except KeyError:
2784 except KeyError:
2785 pass
2785 pass
2786
2786
2787 if _catfmtneedsdata(basefm):
2787 if _catfmtneedsdata(basefm):
2788 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2788 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2789
2789
2790 for abs in ctx.walk(matcher):
2790 for abs in ctx.walk(matcher):
2791 write(abs)
2791 write(abs)
2792 err = 0
2792 err = 0
2793
2793
2794 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2794 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2795 for subpath in sorted(ctx.substate):
2795 for subpath in sorted(ctx.substate):
2796 sub = ctx.sub(subpath)
2796 sub = ctx.sub(subpath)
2797 try:
2797 try:
2798 submatch = matchmod.subdirmatcher(subpath, matcher)
2798 submatch = matchmod.subdirmatcher(subpath, matcher)
2799 subprefix = os.path.join(prefix, subpath)
2799 subprefix = os.path.join(prefix, subpath)
2800 if not sub.cat(
2800 if not sub.cat(
2801 submatch,
2801 submatch,
2802 basefm,
2802 basefm,
2803 fntemplate,
2803 fntemplate,
2804 subprefix,
2804 subprefix,
2805 **opts,
2805 **opts,
2806 ):
2806 ):
2807 err = 0
2807 err = 0
2808 except error.RepoLookupError:
2808 except error.RepoLookupError:
2809 ui.status(
2809 ui.status(
2810 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2810 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2811 )
2811 )
2812
2812
2813 return err
2813 return err
2814
2814
2815
2815
2816 class _AddRemoveContext:
2816 class _AddRemoveContext:
2817 """a small (hacky) context to deal with lazy opening of context
2817 """a small (hacky) context to deal with lazy opening of context
2818
2818
2819 This is to be used in the `commit` function right below. This deals with
2819 This is to be used in the `commit` function right below. This deals with
2820 lazily open a `changing_files` context inside a `transaction` that span the
2820 lazily open a `changing_files` context inside a `transaction` that span the
2821 full commit operation.
2821 full commit operation.
2822
2822
2823 We need :
2823 We need :
2824 - a `changing_files` context to wrap the dirstate change within the
2824 - a `changing_files` context to wrap the dirstate change within the
2825 "addremove" operation,
2825 "addremove" operation,
2826 - a transaction to make sure these change are not written right after the
2826 - a transaction to make sure these change are not written right after the
2827 addremove, but when the commit operation succeed.
2827 addremove, but when the commit operation succeed.
2828
2828
2829 However it get complicated because:
2829 However it get complicated because:
2830 - opening a transaction "this early" shuffle hooks order, especially the
2830 - opening a transaction "this early" shuffle hooks order, especially the
2831 `precommit` one happening after the `pretxtopen` one which I am not too
2831 `precommit` one happening after the `pretxtopen` one which I am not too
2832 enthusiastic about.
2832 enthusiastic about.
2833 - the `mq` extensions + the `record` extension stacks many layers of call
2833 - the `mq` extensions + the `record` extension stacks many layers of call
2834 to implement `qrefresh --interactive` and this result with `mq` calling a
2834 to implement `qrefresh --interactive` and this result with `mq` calling a
2835 `strip` in the middle of this function. Which prevent the existence of
2835 `strip` in the middle of this function. Which prevent the existence of
2836 transaction wrapping all of its function code. (however, `qrefresh` never
2836 transaction wrapping all of its function code. (however, `qrefresh` never
2837 call the `addremove` bits.
2837 call the `addremove` bits.
2838 - the largefile extensions (and maybe other extensions?) wraps `addremove`
2838 - the largefile extensions (and maybe other extensions?) wraps `addremove`
2839 so slicing `addremove` in smaller bits is a complex endeavour.
2839 so slicing `addremove` in smaller bits is a complex endeavour.
2840
2840
2841 So I eventually took a this shortcut that open the transaction if we
2841 So I eventually took a this shortcut that open the transaction if we
2842 actually needs it, not disturbing much of the rest of the code.
2842 actually needs it, not disturbing much of the rest of the code.
2843
2843
2844 It will result in some hooks order change for `hg commit --addremove`,
2844 It will result in some hooks order change for `hg commit --addremove`,
2845 however it seems a corner case enough to ignore that for now (hopefully).
2845 however it seems a corner case enough to ignore that for now (hopefully).
2846
2846
2847 Notes that None of the above problems seems insurmountable, however I have
2847 Notes that None of the above problems seems insurmountable, however I have
2848 been fighting with this specific piece of code for a couple of day already
2848 been fighting with this specific piece of code for a couple of day already
2849 and I need a solution to keep moving forward on the bigger work around
2849 and I need a solution to keep moving forward on the bigger work around
2850 `changing_files` context that is being introduced at the same time as this
2850 `changing_files` context that is being introduced at the same time as this
2851 hack.
2851 hack.
2852
2852
2853 Each problem seems to have a solution:
2853 Each problem seems to have a solution:
2854 - the hook order issue could be solved by refactoring the many-layer stack
2854 - the hook order issue could be solved by refactoring the many-layer stack
2855 that currently composes a commit and calling them earlier,
2855 that currently composes a commit and calling them earlier,
2856 - the mq issue could be solved by refactoring `mq` so that the final strip
2856 - the mq issue could be solved by refactoring `mq` so that the final strip
2857 is done after transaction closure. Be warned that the mq code is quite
2857 is done after transaction closure. Be warned that the mq code is quite
2858 antic however.
2858 antic however.
2859 - large-file could be reworked in parallel of the `addremove` to be
2859 - large-file could be reworked in parallel of the `addremove` to be
2860 friendlier to this.
2860 friendlier to this.
2861
2861
2862 However each of these tasks are too much a diversion right now. In addition
2862 However each of these tasks are too much a diversion right now. In addition
2863 they will be much easier to undertake when the `changing_files` dust has
2863 they will be much easier to undertake when the `changing_files` dust has
2864 settled."""
2864 settled."""
2865
2865
2866 def __init__(self, repo):
2866 def __init__(self, repo):
2867 self._repo = repo
2867 self._repo = repo
2868 self._transaction = None
2868 self._transaction = None
2869 self._dirstate_context = None
2869 self._dirstate_context = None
2870 self._state = None
2870 self._state = None
2871
2871
2872 def __enter__(self):
2872 def __enter__(self):
2873 assert self._state is None
2873 assert self._state is None
2874 self._state = True
2874 self._state = True
2875 return self
2875 return self
2876
2876
2877 def open_transaction(self):
2877 def open_transaction(self):
2878 """open a `transaction` and `changing_files` context
2878 """open a `transaction` and `changing_files` context
2879
2879
2880 Call this when you know that change to the dirstate will be needed and
2880 Call this when you know that change to the dirstate will be needed and
2881 we need to open the transaction early
2881 we need to open the transaction early
2882
2882
2883 This will also open the dirstate `changing_files` context, so you should
2883 This will also open the dirstate `changing_files` context, so you should
2884 call `close_dirstate_context` when the distate changes are done.
2884 call `close_dirstate_context` when the distate changes are done.
2885 """
2885 """
2886 assert self._state is not None
2886 assert self._state is not None
2887 if self._transaction is None:
2887 if self._transaction is None:
2888 self._transaction = self._repo.transaction(b'commit')
2888 self._transaction = self._repo.transaction(b'commit')
2889 self._transaction.__enter__()
2889 self._transaction.__enter__()
2890 if self._dirstate_context is None:
2890 if self._dirstate_context is None:
2891 self._dirstate_context = self._repo.dirstate.changing_files(
2891 self._dirstate_context = self._repo.dirstate.changing_files(
2892 self._repo
2892 self._repo
2893 )
2893 )
2894 self._dirstate_context.__enter__()
2894 self._dirstate_context.__enter__()
2895
2895
2896 def close_dirstate_context(self):
2896 def close_dirstate_context(self):
2897 """close the change_files if any
2897 """close the change_files if any
2898
2898
2899 Call this after the (potential) `open_transaction` call to close the
2899 Call this after the (potential) `open_transaction` call to close the
2900 (potential) changing_files context.
2900 (potential) changing_files context.
2901 """
2901 """
2902 if self._dirstate_context is not None:
2902 if self._dirstate_context is not None:
2903 self._dirstate_context.__exit__(None, None, None)
2903 self._dirstate_context.__exit__(None, None, None)
2904 self._dirstate_context = None
2904 self._dirstate_context = None
2905
2905
2906 def __exit__(self, *args):
2906 def __exit__(self, *args):
2907 if self._dirstate_context is not None:
2907 if self._dirstate_context is not None:
2908 self._dirstate_context.__exit__(*args)
2908 self._dirstate_context.__exit__(*args)
2909 if self._transaction is not None:
2909 if self._transaction is not None:
2910 self._transaction.__exit__(*args)
2910 self._transaction.__exit__(*args)
2911
2911
2912
2912
2913 def commit(ui, repo, commitfunc, pats, opts):
2913 def commit(ui, repo, commitfunc, pats, opts):
2914 '''commit the specified files or all outstanding changes'''
2914 '''commit the specified files or all outstanding changes'''
2915 date = opts.get(b'date')
2915 date = opts.get(b'date')
2916 if date:
2916 if date:
2917 opts[b'date'] = dateutil.parsedate(date)
2917 opts[b'date'] = dateutil.parsedate(date)
2918
2918
2919 with repo.wlock(), repo.lock():
2919 with repo.wlock(), repo.lock():
2920 message = logmessage(ui, opts)
2920 message = logmessage(ui, opts)
2921 matcher = scmutil.match(repo[None], pats, opts)
2921 matcher = scmutil.match(repo[None], pats, opts)
2922
2922
2923 with _AddRemoveContext(repo) as c:
2923 with _AddRemoveContext(repo) as c:
2924 # extract addremove carefully -- this function can be called from a
2924 # extract addremove carefully -- this function can be called from a
2925 # command that doesn't support addremove
2925 # command that doesn't support addremove
2926 if opts.get(b'addremove'):
2926 if opts.get(b'addremove'):
2927 relative = scmutil.anypats(pats, opts)
2927 relative = scmutil.anypats(pats, opts)
2928 uipathfn = scmutil.getuipathfn(
2928 uipathfn = scmutil.getuipathfn(
2929 repo,
2929 repo,
2930 legacyrelativevalue=relative,
2930 legacyrelativevalue=relative,
2931 )
2931 )
2932 r = scmutil.addremove(
2932 r = scmutil.addremove(
2933 repo,
2933 repo,
2934 matcher,
2934 matcher,
2935 b"",
2935 b"",
2936 uipathfn,
2936 uipathfn,
2937 opts,
2937 opts,
2938 open_tr=c.open_transaction,
2938 open_tr=c.open_transaction,
2939 )
2939 )
2940 m = _(b"failed to mark all new/missing files as added/removed")
2940 m = _(b"failed to mark all new/missing files as added/removed")
2941 if r != 0:
2941 if r != 0:
2942 raise error.Abort(m)
2942 raise error.Abort(m)
2943 c.close_dirstate_context()
2943 c.close_dirstate_context()
2944 return commitfunc(ui, repo, message, matcher, opts)
2944 return commitfunc(ui, repo, message, matcher, opts)
2945
2945
2946
2946
2947 def samefile(f, ctx1, ctx2):
2947 def samefile(f, ctx1, ctx2):
2948 if f in ctx1.manifest():
2948 if f in ctx1.manifest():
2949 a = ctx1.filectx(f)
2949 a = ctx1.filectx(f)
2950 if f in ctx2.manifest():
2950 if f in ctx2.manifest():
2951 b = ctx2.filectx(f)
2951 b = ctx2.filectx(f)
2952 return not a.cmp(b) and a.flags() == b.flags()
2952 return not a.cmp(b) and a.flags() == b.flags()
2953 else:
2953 else:
2954 return False
2954 return False
2955 else:
2955 else:
2956 return f not in ctx2.manifest()
2956 return f not in ctx2.manifest()
2957
2957
2958
2958
2959 def amend(ui, repo, old, extra, pats, opts: Dict[str, Any]):
2959 def amend(ui, repo, old, extra, pats, opts: Dict[str, Any]):
2960 # avoid cycle context -> subrepo -> cmdutil
2960 # avoid cycle context -> subrepo -> cmdutil
2961 from . import context
2961 from . import context
2962
2962
2963 # amend will reuse the existing user if not specified, but the obsolete
2963 # amend will reuse the existing user if not specified, but the obsolete
2964 # marker creation requires that the current user's name is specified.
2964 # marker creation requires that the current user's name is specified.
2965 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2965 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2966 ui.username() # raise exception if username not set
2966 ui.username() # raise exception if username not set
2967
2967
2968 ui.note(_(b'amending changeset %s\n') % old)
2968 ui.note(_(b'amending changeset %s\n') % old)
2969 base = old.p1()
2969 base = old.p1()
2970
2970
2971 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2971 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2972 # Participating changesets:
2972 # Participating changesets:
2973 #
2973 #
2974 # wctx o - workingctx that contains changes from working copy
2974 # wctx o - workingctx that contains changes from working copy
2975 # | to go into amending commit
2975 # | to go into amending commit
2976 # |
2976 # |
2977 # old o - changeset to amend
2977 # old o - changeset to amend
2978 # |
2978 # |
2979 # base o - first parent of the changeset to amend
2979 # base o - first parent of the changeset to amend
2980 wctx = repo[None]
2980 wctx = repo[None]
2981
2981
2982 # Copy to avoid mutating input
2982 # Copy to avoid mutating input
2983 extra = extra.copy()
2983 extra = extra.copy()
2984 # Update extra dict from amended commit (e.g. to preserve graft
2984 # Update extra dict from amended commit (e.g. to preserve graft
2985 # source)
2985 # source)
2986 extra.update(old.extra())
2986 extra.update(old.extra())
2987
2987
2988 # Also update it from the from the wctx
2988 # Also update it from the from the wctx
2989 extra.update(wctx.extra())
2989 extra.update(wctx.extra())
2990
2990
2991 # date-only change should be ignored?
2991 # date-only change should be ignored?
2992 datemaydiffer = resolve_commit_options(ui, opts)
2992 datemaydiffer = resolve_commit_options(ui, opts)
2993 opts = pycompat.byteskwargs(opts)
2993 opts = pycompat.byteskwargs(opts)
2994
2994
2995 date = old.date()
2995 date = old.date()
2996 if opts.get(b'date'):
2996 if opts.get(b'date'):
2997 date = dateutil.parsedate(opts.get(b'date'))
2997 date = dateutil.parsedate(opts.get(b'date'))
2998 user = opts.get(b'user') or old.user()
2998 user = opts.get(b'user') or old.user()
2999
2999
3000 if len(old.parents()) > 1:
3000 if len(old.parents()) > 1:
3001 # ctx.files() isn't reliable for merges, so fall back to the
3001 # ctx.files() isn't reliable for merges, so fall back to the
3002 # slower repo.status() method
3002 # slower repo.status() method
3003 st = base.status(old)
3003 st = base.status(old)
3004 files = set(st.modified) | set(st.added) | set(st.removed)
3004 files = set(st.modified) | set(st.added) | set(st.removed)
3005 else:
3005 else:
3006 files = set(old.files())
3006 files = set(old.files())
3007
3007
3008 # add/remove the files to the working copy if the "addremove" option
3008 # add/remove the files to the working copy if the "addremove" option
3009 # was specified.
3009 # was specified.
3010 matcher = scmutil.match(wctx, pats, opts)
3010 matcher = scmutil.match(wctx, pats, opts)
3011 relative = scmutil.anypats(pats, opts)
3011 relative = scmutil.anypats(pats, opts)
3012 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
3012 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
3013 if opts.get(b'addremove'):
3013 if opts.get(b'addremove'):
3014 with repo.dirstate.changing_files(repo):
3014 with repo.dirstate.changing_files(repo):
3015 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
3015 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
3016 m = _(
3016 m = _(
3017 b"failed to mark all new/missing files as added/removed"
3017 b"failed to mark all new/missing files as added/removed"
3018 )
3018 )
3019 raise error.Abort(m)
3019 raise error.Abort(m)
3020
3020
3021 # Check subrepos. This depends on in-place wctx._status update in
3021 # Check subrepos. This depends on in-place wctx._status update in
3022 # subrepo.precommit(). To minimize the risk of this hack, we do
3022 # subrepo.precommit(). To minimize the risk of this hack, we do
3023 # nothing if .hgsub does not exist.
3023 # nothing if .hgsub does not exist.
3024 if b'.hgsub' in wctx or b'.hgsub' in old:
3024 if b'.hgsub' in wctx or b'.hgsub' in old:
3025 subs, commitsubs, newsubstate = subrepoutil.precommit(
3025 subs, commitsubs, newsubstate = subrepoutil.precommit(
3026 ui, wctx, wctx._status, matcher
3026 ui, wctx, wctx._status, matcher
3027 )
3027 )
3028 # amend should abort if commitsubrepos is enabled
3028 # amend should abort if commitsubrepos is enabled
3029 assert not commitsubs
3029 assert not commitsubs
3030 if subs:
3030 if subs:
3031 subrepoutil.writestate(repo, newsubstate)
3031 subrepoutil.writestate(repo, newsubstate)
3032
3032
3033 ms = mergestatemod.mergestate.read(repo)
3033 ms = mergestatemod.mergestate.read(repo)
3034 mergeutil.checkunresolved(ms)
3034 mergeutil.checkunresolved(ms)
3035
3035
3036 filestoamend = {f for f in wctx.files() if matcher(f)}
3036 filestoamend = {f for f in wctx.files() if matcher(f)}
3037
3037
3038 changes = len(filestoamend) > 0
3038 changes = len(filestoamend) > 0
3039 changeset_copies = (
3039 changeset_copies = (
3040 repo.ui.config(b'experimental', b'copies.read-from')
3040 repo.ui.config(b'experimental', b'copies.read-from')
3041 != b'filelog-only'
3041 != b'filelog-only'
3042 )
3042 )
3043 # If there are changes to amend or if copy information needs to be read
3043 # If there are changes to amend or if copy information needs to be read
3044 # from the changeset extras, we cannot take the fast path of using
3044 # from the changeset extras, we cannot take the fast path of using
3045 # filectxs from the old commit.
3045 # filectxs from the old commit.
3046 if changes or changeset_copies:
3046 if changes or changeset_copies:
3047 # Recompute copies (avoid recording a -> b -> a)
3047 # Recompute copies (avoid recording a -> b -> a)
3048 copied = copies.pathcopies(base, wctx)
3048 copied = copies.pathcopies(base, wctx)
3049 if old.p2():
3049 if old.p2():
3050 copied.update(copies.pathcopies(old.p2(), wctx))
3050 copied.update(copies.pathcopies(old.p2(), wctx))
3051
3051
3052 # Prune files which were reverted by the updates: if old
3052 # Prune files which were reverted by the updates: if old
3053 # introduced file X and the file was renamed in the working
3053 # introduced file X and the file was renamed in the working
3054 # copy, then those two files are the same and
3054 # copy, then those two files are the same and
3055 # we can discard X from our list of files. Likewise if X
3055 # we can discard X from our list of files. Likewise if X
3056 # was removed, it's no longer relevant. If X is missing (aka
3056 # was removed, it's no longer relevant. If X is missing (aka
3057 # deleted), old X must be preserved.
3057 # deleted), old X must be preserved.
3058 files.update(filestoamend)
3058 files.update(filestoamend)
3059 files = [
3059 files = [
3060 f
3060 f
3061 for f in files
3061 for f in files
3062 if (f not in filestoamend or not samefile(f, wctx, base))
3062 if (f not in filestoamend or not samefile(f, wctx, base))
3063 ]
3063 ]
3064
3064
3065 def filectxfn(repo, ctx_, path):
3065 def filectxfn(repo, ctx_, path):
3066 try:
3066 try:
3067 # If the file being considered is not amongst the files
3067 # If the file being considered is not amongst the files
3068 # to be amended, we should use the file context from the
3068 # to be amended, we should use the file context from the
3069 # old changeset. This avoids issues when only some files in
3069 # old changeset. This avoids issues when only some files in
3070 # the working copy are being amended but there are also
3070 # the working copy are being amended but there are also
3071 # changes to other files from the old changeset.
3071 # changes to other files from the old changeset.
3072 if path in filestoamend:
3072 if path in filestoamend:
3073 # Return None for removed files.
3073 # Return None for removed files.
3074 if path in wctx.removed():
3074 if path in wctx.removed():
3075 return None
3075 return None
3076 fctx = wctx[path]
3076 fctx = wctx[path]
3077 else:
3077 else:
3078 fctx = old.filectx(path)
3078 fctx = old.filectx(path)
3079 flags = fctx.flags()
3079 flags = fctx.flags()
3080 mctx = context.memfilectx(
3080 mctx = context.memfilectx(
3081 repo,
3081 repo,
3082 ctx_,
3082 ctx_,
3083 fctx.path(),
3083 fctx.path(),
3084 fctx.data(),
3084 fctx.data(),
3085 islink=b'l' in flags,
3085 islink=b'l' in flags,
3086 isexec=b'x' in flags,
3086 isexec=b'x' in flags,
3087 copysource=copied.get(path),
3087 copysource=copied.get(path),
3088 )
3088 )
3089 return mctx
3089 return mctx
3090 except KeyError:
3090 except KeyError:
3091 return None
3091 return None
3092
3092
3093 else:
3093 else:
3094 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3094 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3095
3095
3096 # Use version of files as in the old cset
3096 # Use version of files as in the old cset
3097 def filectxfn(repo, ctx_, path):
3097 def filectxfn(repo, ctx_, path):
3098 try:
3098 try:
3099 return old.filectx(path)
3099 return old.filectx(path)
3100 except KeyError:
3100 except KeyError:
3101 return None
3101 return None
3102
3102
3103 # See if we got a message from -m or -l, if not, open the editor with
3103 # See if we got a message from -m or -l, if not, open the editor with
3104 # the message of the changeset to amend.
3104 # the message of the changeset to amend.
3105 message = logmessage(ui, opts)
3105 message = logmessage(ui, opts)
3106
3106
3107 editform = mergeeditform(old, b'commit.amend')
3107 editform = mergeeditform(old, b'commit.amend')
3108
3108
3109 if not message:
3109 if not message:
3110 message = old.description()
3110 message = old.description()
3111 # Default if message isn't provided and --edit is not passed is to
3111 # Default if message isn't provided and --edit is not passed is to
3112 # invoke editor, but allow --no-edit. If somehow we don't have any
3112 # invoke editor, but allow --no-edit. If somehow we don't have any
3113 # description, let's always start the editor.
3113 # description, let's always start the editor.
3114 doedit = not message or opts.get(b'edit') in [True, None]
3114 doedit = not message or opts.get(b'edit') in [True, None]
3115 else:
3115 else:
3116 # Default if message is provided is to not invoke editor, but allow
3116 # Default if message is provided is to not invoke editor, but allow
3117 # --edit.
3117 # --edit.
3118 doedit = opts.get(b'edit') is True
3118 doedit = opts.get(b'edit') is True
3119 editor = getcommiteditor(edit=doedit, editform=editform)
3119 editor = getcommiteditor(edit=doedit, editform=editform)
3120
3120
3121 pureextra = extra.copy()
3121 pureextra = extra.copy()
3122 extra[b'amend_source'] = old.hex()
3122 extra[b'amend_source'] = old.hex()
3123
3123
3124 new = context.memctx(
3124 new = context.memctx(
3125 repo,
3125 repo,
3126 parents=[base.node(), old.p2().node()],
3126 parents=[base.node(), old.p2().node()],
3127 text=message,
3127 text=message,
3128 files=files,
3128 files=files,
3129 filectxfn=filectxfn,
3129 filectxfn=filectxfn,
3130 user=user,
3130 user=user,
3131 date=date,
3131 date=date,
3132 extra=extra,
3132 extra=extra,
3133 editor=editor,
3133 editor=editor,
3134 )
3134 )
3135
3135
3136 newdesc = changelog.stripdesc(new.description())
3136 newdesc = changelog.stripdesc(new.description())
3137 if (
3137 if (
3138 (not changes)
3138 (not changes)
3139 and newdesc == old.description()
3139 and newdesc == old.description()
3140 and user == old.user()
3140 and user == old.user()
3141 and (date == old.date() or datemaydiffer)
3141 and (date == old.date() or datemaydiffer)
3142 and pureextra == old.extra()
3142 and pureextra == old.extra()
3143 ):
3143 ):
3144 # nothing changed. continuing here would create a new node
3144 # nothing changed. continuing here would create a new node
3145 # anyway because of the amend_source noise.
3145 # anyway because of the amend_source noise.
3146 #
3146 #
3147 # This not what we expect from amend.
3147 # This not what we expect from amend.
3148 return old.node()
3148 return old.node()
3149
3149
3150 commitphase = None
3150 commitphase = None
3151 if opts.get(b'secret'):
3151 if opts.get(b'secret'):
3152 commitphase = phases.secret
3152 commitphase = phases.secret
3153 elif opts.get(b'draft'):
3153 elif opts.get(b'draft'):
3154 commitphase = phases.draft
3154 commitphase = phases.draft
3155 newid = repo.commitctx(new)
3155 newid = repo.commitctx(new)
3156 ms.reset()
3156 ms.reset()
3157
3157
3158 with repo.dirstate.changing_parents(repo):
3158 with repo.dirstate.changing_parents(repo):
3159 # Reroute the working copy parent to the new changeset
3159 # Reroute the working copy parent to the new changeset
3160 repo.setparents(newid, repo.nullid)
3160 repo.setparents(newid, repo.nullid)
3161
3161
3162 # Fixing the dirstate because localrepo.commitctx does not update
3162 # Fixing the dirstate because localrepo.commitctx does not update
3163 # it. This is rather convenient because we did not need to update
3163 # it. This is rather convenient because we did not need to update
3164 # the dirstate for all the files in the new commit which commitctx
3164 # the dirstate for all the files in the new commit which commitctx
3165 # could have done if it updated the dirstate. Now, we can
3165 # could have done if it updated the dirstate. Now, we can
3166 # selectively update the dirstate only for the amended files.
3166 # selectively update the dirstate only for the amended files.
3167 dirstate = repo.dirstate
3167 dirstate = repo.dirstate
3168
3168
3169 # Update the state of the files which were added and modified in the
3169 # Update the state of the files which were added and modified in the
3170 # amend to "normal" in the dirstate. We need to use "normallookup" since
3170 # amend to "normal" in the dirstate. We need to use "normallookup" since
3171 # the files may have changed since the command started; using "normal"
3171 # the files may have changed since the command started; using "normal"
3172 # would mark them as clean but with uncommitted contents.
3172 # would mark them as clean but with uncommitted contents.
3173 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3173 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3174 for f in normalfiles:
3174 for f in normalfiles:
3175 dirstate.update_file(
3175 dirstate.update_file(
3176 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
3176 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
3177 )
3177 )
3178
3178
3179 # Update the state of files which were removed in the amend
3179 # Update the state of files which were removed in the amend
3180 # to "removed" in the dirstate.
3180 # to "removed" in the dirstate.
3181 removedfiles = set(wctx.removed()) & filestoamend
3181 removedfiles = set(wctx.removed()) & filestoamend
3182 for f in removedfiles:
3182 for f in removedfiles:
3183 dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
3183 dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
3184
3184
3185 mapping = {old.node(): (newid,)}
3185 mapping = {old.node(): (newid,)}
3186 obsmetadata = None
3186 obsmetadata = None
3187 if opts.get(b'note'):
3187 if opts.get(b'note'):
3188 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3188 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3189 backup = ui.configbool(b'rewrite', b'backup-bundle')
3189 backup = ui.configbool(b'rewrite', b'backup-bundle')
3190 scmutil.cleanupnodes(
3190 scmutil.cleanupnodes(
3191 repo,
3191 repo,
3192 mapping,
3192 mapping,
3193 b'amend',
3193 b'amend',
3194 metadata=obsmetadata,
3194 metadata=obsmetadata,
3195 fixphase=True,
3195 fixphase=True,
3196 targetphase=commitphase,
3196 targetphase=commitphase,
3197 backup=backup,
3197 backup=backup,
3198 )
3198 )
3199
3199
3200 return newid
3200 return newid
3201
3201
3202
3202
3203 def commiteditor(repo, ctx, subs, editform=b''):
3203 def commiteditor(repo, ctx, subs, editform=b''):
3204 if ctx.description():
3204 if ctx.description():
3205 return ctx.description()
3205 return ctx.description()
3206 return commitforceeditor(
3206 return commitforceeditor(
3207 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3207 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3208 )
3208 )
3209
3209
3210
3210
3211 def commitforceeditor(
3211 def commitforceeditor(
3212 repo,
3212 repo,
3213 ctx,
3213 ctx,
3214 subs,
3214 subs,
3215 finishdesc=None,
3215 finishdesc=None,
3216 extramsg=None,
3216 extramsg=None,
3217 editform=b'',
3217 editform=b'',
3218 unchangedmessagedetection=False,
3218 unchangedmessagedetection=False,
3219 ):
3219 ):
3220 if not extramsg:
3220 if not extramsg:
3221 extramsg = _(b"Leave message empty to abort commit.")
3221 extramsg = _(b"Leave message empty to abort commit.")
3222
3222
3223 forms = [e for e in editform.split(b'.') if e]
3223 forms = [e for e in editform.split(b'.') if e]
3224 forms.insert(0, b'changeset')
3224 forms.insert(0, b'changeset')
3225 templatetext = None
3225 templatetext = None
3226 while forms:
3226 while forms:
3227 ref = b'.'.join(forms)
3227 ref = b'.'.join(forms)
3228 if repo.ui.config(b'committemplate', ref):
3228 if repo.ui.config(b'committemplate', ref):
3229 templatetext = committext = buildcommittemplate(
3229 templatetext = committext = buildcommittemplate(
3230 repo, ctx, subs, extramsg, ref
3230 repo, ctx, subs, extramsg, ref
3231 )
3231 )
3232 break
3232 break
3233 forms.pop()
3233 forms.pop()
3234 else:
3234 else:
3235 committext = buildcommittext(repo, ctx, subs, extramsg)
3235 committext = buildcommittext(repo, ctx, subs, extramsg)
3236
3236
3237 # run editor in the repository root
3237 # run editor in the repository root
3238 olddir = encoding.getcwd()
3238 olddir = encoding.getcwd()
3239 os.chdir(repo.root)
3239 os.chdir(repo.root)
3240
3240
3241 # make in-memory changes visible to external process
3241 # make in-memory changes visible to external process
3242 tr = repo.currenttransaction()
3242 tr = repo.currenttransaction()
3243 repo.dirstate.write(tr)
3243 repo.dirstate.write(tr)
3244 pending = tr and tr.writepending() and repo.root
3244 pending = tr and tr.writepending() and repo.root
3245
3245
3246 editortext = repo.ui.edit(
3246 editortext = repo.ui.edit(
3247 committext,
3247 committext,
3248 ctx.user(),
3248 ctx.user(),
3249 ctx.extra(),
3249 ctx.extra(),
3250 editform=editform,
3250 editform=editform,
3251 pending=pending,
3251 pending=pending,
3252 repopath=repo.path,
3252 repopath=repo.path,
3253 action=b'commit',
3253 action=b'commit',
3254 )
3254 )
3255 text = editortext
3255 text = editortext
3256
3256
3257 # strip away anything below this special string (used for editors that want
3257 # strip away anything below this special string (used for editors that want
3258 # to display the diff)
3258 # to display the diff)
3259 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3259 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3260 if stripbelow:
3260 if stripbelow:
3261 text = text[: stripbelow.start()]
3261 text = text[: stripbelow.start()]
3262
3262
3263 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3263 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3264 os.chdir(olddir)
3264 os.chdir(olddir)
3265
3265
3266 if finishdesc:
3266 if finishdesc:
3267 text = finishdesc(text)
3267 text = finishdesc(text)
3268 if not text.strip():
3268 if not text.strip():
3269 raise error.InputError(_(b"empty commit message"))
3269 raise error.InputError(_(b"empty commit message"))
3270 if unchangedmessagedetection and editortext == templatetext:
3270 if unchangedmessagedetection and editortext == templatetext:
3271 raise error.InputError(_(b"commit message unchanged"))
3271 raise error.InputError(_(b"commit message unchanged"))
3272
3272
3273 return text
3273 return text
3274
3274
3275
3275
3276 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3276 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3277 ui = repo.ui
3277 ui = repo.ui
3278 spec = formatter.reference_templatespec(ref)
3278 spec = formatter.reference_templatespec(ref)
3279 t = logcmdutil.changesettemplater(ui, repo, spec)
3279 t = logcmdutil.changesettemplater(ui, repo, spec)
3280 t.t.cache.update(
3280 t.t.cache.update(
3281 (k, templater.unquotestring(v))
3281 (k, templater.unquotestring(v))
3282 for k, v in repo.ui.configitems(b'committemplate')
3282 for k, v in repo.ui.configitems(b'committemplate')
3283 )
3283 )
3284
3284
3285 if not extramsg:
3285 if not extramsg:
3286 extramsg = b'' # ensure that extramsg is string
3286 extramsg = b'' # ensure that extramsg is string
3287
3287
3288 ui.pushbuffer()
3288 ui.pushbuffer()
3289 t.show(ctx, extramsg=extramsg)
3289 t.show(ctx, extramsg=extramsg)
3290 return ui.popbuffer()
3290 return ui.popbuffer()
3291
3291
3292
3292
3293 def hgprefix(msg):
3293 def hgprefix(msg):
3294 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3294 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3295
3295
3296
3296
3297 def buildcommittext(repo, ctx, subs, extramsg):
3297 def buildcommittext(repo, ctx, subs, extramsg):
3298 edittext = []
3298 edittext = []
3299 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3299 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3300 if ctx.description():
3300 if ctx.description():
3301 edittext.append(ctx.description())
3301 edittext.append(ctx.description())
3302 edittext.append(b"")
3302 edittext.append(b"")
3303 edittext.append(b"") # Empty line between message and comments.
3303 edittext.append(b"") # Empty line between message and comments.
3304 edittext.append(
3304 edittext.append(
3305 hgprefix(
3305 hgprefix(
3306 _(
3306 _(
3307 b"Enter commit message."
3307 b"Enter commit message."
3308 b" Lines beginning with 'HG:' are removed."
3308 b" Lines beginning with 'HG:' are removed."
3309 )
3309 )
3310 )
3310 )
3311 )
3311 )
3312 edittext.append(hgprefix(extramsg))
3312 edittext.append(hgprefix(extramsg))
3313 edittext.append(b"HG: --")
3313 edittext.append(b"HG: --")
3314 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3314 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3315 if ctx.p2():
3315 if ctx.p2():
3316 edittext.append(hgprefix(_(b"branch merge")))
3316 edittext.append(hgprefix(_(b"branch merge")))
3317 if ctx.branch():
3317 if ctx.branch():
3318 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3318 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3319 if bookmarks.isactivewdirparent(repo):
3319 if bookmarks.isactivewdirparent(repo):
3320 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3320 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3321 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3321 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3322 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3322 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3323 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3323 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3324 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3324 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3325 if not added and not modified and not removed:
3325 if not added and not modified and not removed:
3326 edittext.append(hgprefix(_(b"no files changed")))
3326 edittext.append(hgprefix(_(b"no files changed")))
3327 edittext.append(b"")
3327 edittext.append(b"")
3328
3328
3329 return b"\n".join(edittext)
3329 return b"\n".join(edittext)
3330
3330
3331
3331
3332 def commitstatus(repo, node, branch, bheads=None, tip=None, **opts):
3332 def commitstatus(repo, node, branch, bheads=None, tip=None, **opts):
3333 ctx = repo[node]
3333 ctx = repo[node]
3334 parents = ctx.parents()
3334 parents = ctx.parents()
3335
3335
3336 if tip is not None and repo.changelog.tip() == tip:
3336 if tip is not None and repo.changelog.tip() == tip:
3337 # avoid reporting something like "committed new head" when
3337 # avoid reporting something like "committed new head" when
3338 # recommitting old changesets, and issue a helpful warning
3338 # recommitting old changesets, and issue a helpful warning
3339 # for most instances
3339 # for most instances
3340 repo.ui.warn(_(b"warning: commit already existed in the repository!\n"))
3340 repo.ui.warn(_(b"warning: commit already existed in the repository!\n"))
3341 elif (
3341 elif (
3342 not opts.get('amend')
3342 not opts.get('amend')
3343 and bheads
3343 and bheads
3344 and node not in bheads
3344 and node not in bheads
3345 and not any(
3345 and not any(
3346 p.node() in bheads and p.branch() == branch for p in parents
3346 p.node() in bheads and p.branch() == branch for p in parents
3347 )
3347 )
3348 ):
3348 ):
3349 repo.ui.status(_(b'created new head\n'))
3349 repo.ui.status(_(b'created new head\n'))
3350 # The message is not printed for initial roots. For the other
3350 # The message is not printed for initial roots. For the other
3351 # changesets, it is printed in the following situations:
3351 # changesets, it is printed in the following situations:
3352 #
3352 #
3353 # Par column: for the 2 parents with ...
3353 # Par column: for the 2 parents with ...
3354 # N: null or no parent
3354 # N: null or no parent
3355 # B: parent is on another named branch
3355 # B: parent is on another named branch
3356 # C: parent is a regular non head changeset
3356 # C: parent is a regular non head changeset
3357 # H: parent was a branch head of the current branch
3357 # H: parent was a branch head of the current branch
3358 # Msg column: whether we print "created new head" message
3358 # Msg column: whether we print "created new head" message
3359 # In the following, it is assumed that there already exists some
3359 # In the following, it is assumed that there already exists some
3360 # initial branch heads of the current branch, otherwise nothing is
3360 # initial branch heads of the current branch, otherwise nothing is
3361 # printed anyway.
3361 # printed anyway.
3362 #
3362 #
3363 # Par Msg Comment
3363 # Par Msg Comment
3364 # N N y additional topo root
3364 # N N y additional topo root
3365 #
3365 #
3366 # B N y additional branch root
3366 # B N y additional branch root
3367 # C N y additional topo head
3367 # C N y additional topo head
3368 # H N n usual case
3368 # H N n usual case
3369 #
3369 #
3370 # B B y weird additional branch root
3370 # B B y weird additional branch root
3371 # C B y branch merge
3371 # C B y branch merge
3372 # H B n merge with named branch
3372 # H B n merge with named branch
3373 #
3373 #
3374 # C C y additional head from merge
3374 # C C y additional head from merge
3375 # C H n merge with a head
3375 # C H n merge with a head
3376 #
3376 #
3377 # H H n head merge: head count decreases
3377 # H H n head merge: head count decreases
3378
3378
3379 if not opts.get('close_branch'):
3379 if not opts.get('close_branch'):
3380 for r in parents:
3380 for r in parents:
3381 if r.closesbranch() and r.branch() == branch:
3381 if r.closesbranch() and r.branch() == branch:
3382 repo.ui.status(
3382 repo.ui.status(
3383 _(b'reopening closed branch head %d\n') % r.rev()
3383 _(b'reopening closed branch head %d\n') % r.rev()
3384 )
3384 )
3385
3385
3386 if repo.ui.debugflag:
3386 if repo.ui.debugflag:
3387 repo.ui.write(
3387 repo.ui.write(
3388 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3388 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3389 )
3389 )
3390 elif repo.ui.verbose:
3390 elif repo.ui.verbose:
3391 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3391 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3392
3392
3393
3393
3394 def postcommitstatus(repo, pats, opts):
3394 def postcommitstatus(repo, pats, opts):
3395 return repo.status(match=scmutil.match(repo[None], pats, opts))
3395 return repo.status(match=scmutil.match(repo[None], pats, opts))
3396
3396
3397
3397
3398 def revert(ui, repo, ctx, *pats, **opts):
3398 def revert(ui, repo, ctx, *pats, **opts):
3399 opts = pycompat.byteskwargs(opts)
3399 opts = pycompat.byteskwargs(opts)
3400 parent, p2 = repo.dirstate.parents()
3400 parent, p2 = repo.dirstate.parents()
3401 node = ctx.node()
3401 node = ctx.node()
3402
3402
3403 mf = ctx.manifest()
3403 mf = ctx.manifest()
3404 if node == p2:
3404 if node == p2:
3405 parent = p2
3405 parent = p2
3406
3406
3407 # need all matching names in dirstate and manifest of target rev,
3407 # need all matching names in dirstate and manifest of target rev,
3408 # so have to walk both. do not print errors if files exist in one
3408 # so have to walk both. do not print errors if files exist in one
3409 # but not other. in both cases, filesets should be evaluated against
3409 # but not other. in both cases, filesets should be evaluated against
3410 # workingctx to get consistent result (issue4497). this means 'set:**'
3410 # workingctx to get consistent result (issue4497). this means 'set:**'
3411 # cannot be used to select missing files from target rev.
3411 # cannot be used to select missing files from target rev.
3412
3412
3413 # `names` is a mapping for all elements in working copy and target revision
3413 # `names` is a mapping for all elements in working copy and target revision
3414 # The mapping is in the form:
3414 # The mapping is in the form:
3415 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3415 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3416 names = {}
3416 names = {}
3417 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3417 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3418
3418
3419 with repo.wlock(), repo.dirstate.changing_files(repo):
3419 with repo.wlock(), repo.dirstate.changing_files(repo):
3420 ## filling of the `names` mapping
3420 ## filling of the `names` mapping
3421 # walk dirstate to fill `names`
3421 # walk dirstate to fill `names`
3422
3422
3423 interactive = opts.get(b'interactive', False)
3423 interactive = opts.get(b'interactive', False)
3424 wctx = repo[None]
3424 wctx = repo[None]
3425 m = scmutil.match(wctx, pats, opts)
3425 m = scmutil.match(wctx, pats, opts)
3426
3426
3427 # we'll need this later
3427 # we'll need this later
3428 targetsubs = sorted(s for s in wctx.substate if m(s))
3428 targetsubs = sorted(s for s in wctx.substate if m(s))
3429
3429
3430 if not m.always():
3430 if not m.always():
3431 matcher = matchmod.badmatch(m, lambda x, y: False)
3431 matcher = matchmod.badmatch(m, lambda x, y: False)
3432 for abs in wctx.walk(matcher):
3432 for abs in wctx.walk(matcher):
3433 names[abs] = m.exact(abs)
3433 names[abs] = m.exact(abs)
3434
3434
3435 # walk target manifest to fill `names`
3435 # walk target manifest to fill `names`
3436
3436
3437 def badfn(path, msg):
3437 def badfn(path, msg):
3438 if path in names:
3438 if path in names:
3439 return
3439 return
3440 if path in ctx.substate:
3440 if path in ctx.substate:
3441 return
3441 return
3442 path_ = path + b'/'
3442 path_ = path + b'/'
3443 for f in names:
3443 for f in names:
3444 if f.startswith(path_):
3444 if f.startswith(path_):
3445 return
3445 return
3446 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3446 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3447
3447
3448 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3448 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3449 if abs not in names:
3449 if abs not in names:
3450 names[abs] = m.exact(abs)
3450 names[abs] = m.exact(abs)
3451
3451
3452 # Find status of all file in `names`.
3452 # Find status of all file in `names`.
3453 m = scmutil.matchfiles(repo, names)
3453 m = scmutil.matchfiles(repo, names)
3454
3454
3455 changes = repo.status(
3455 changes = repo.status(
3456 node1=node, match=m, unknown=True, ignored=True, clean=True
3456 node1=node, match=m, unknown=True, ignored=True, clean=True
3457 )
3457 )
3458 else:
3458 else:
3459 changes = repo.status(node1=node, match=m)
3459 changes = repo.status(node1=node, match=m)
3460 for kind in changes:
3460 for kind in changes:
3461 for abs in kind:
3461 for abs in kind:
3462 names[abs] = m.exact(abs)
3462 names[abs] = m.exact(abs)
3463
3463
3464 m = scmutil.matchfiles(repo, names)
3464 m = scmutil.matchfiles(repo, names)
3465
3465
3466 modified = set(changes.modified)
3466 modified = set(changes.modified)
3467 added = set(changes.added)
3467 added = set(changes.added)
3468 removed = set(changes.removed)
3468 removed = set(changes.removed)
3469 _deleted = set(changes.deleted)
3469 _deleted = set(changes.deleted)
3470 unknown = set(changes.unknown)
3470 unknown = set(changes.unknown)
3471 unknown.update(changes.ignored)
3471 unknown.update(changes.ignored)
3472 clean = set(changes.clean)
3472 clean = set(changes.clean)
3473 modadded = set()
3473 modadded = set()
3474
3474
3475 # We need to account for the state of the file in the dirstate,
3475 # We need to account for the state of the file in the dirstate,
3476 # even when we revert against something else than parent. This will
3476 # even when we revert against something else than parent. This will
3477 # slightly alter the behavior of revert (doing back up or not, delete
3477 # slightly alter the behavior of revert (doing back up or not, delete
3478 # or just forget etc).
3478 # or just forget etc).
3479 if parent == node:
3479 if parent == node:
3480 dsmodified = modified
3480 dsmodified = modified
3481 dsadded = added
3481 dsadded = added
3482 dsremoved = removed
3482 dsremoved = removed
3483 # store all local modifications, useful later for rename detection
3483 # store all local modifications, useful later for rename detection
3484 localchanges = dsmodified | dsadded
3484 localchanges = dsmodified | dsadded
3485 modified, added, removed = set(), set(), set()
3485 modified, added, removed = set(), set(), set()
3486 else:
3486 else:
3487 changes = repo.status(node1=parent, match=m)
3487 changes = repo.status(node1=parent, match=m)
3488 dsmodified = set(changes.modified)
3488 dsmodified = set(changes.modified)
3489 dsadded = set(changes.added)
3489 dsadded = set(changes.added)
3490 dsremoved = set(changes.removed)
3490 dsremoved = set(changes.removed)
3491 # store all local modifications, useful later for rename detection
3491 # store all local modifications, useful later for rename detection
3492 localchanges = dsmodified | dsadded
3492 localchanges = dsmodified | dsadded
3493
3493
3494 # only take into account for removes between wc and target
3494 # only take into account for removes between wc and target
3495 clean |= dsremoved - removed
3495 clean |= dsremoved - removed
3496 dsremoved &= removed
3496 dsremoved &= removed
3497 # distinct between dirstate remove and other
3497 # distinct between dirstate remove and other
3498 removed -= dsremoved
3498 removed -= dsremoved
3499
3499
3500 modadded = added & dsmodified
3500 modadded = added & dsmodified
3501 added -= modadded
3501 added -= modadded
3502
3502
3503 # tell newly modified apart.
3503 # tell newly modified apart.
3504 dsmodified &= modified
3504 dsmodified &= modified
3505 dsmodified |= modified & dsadded # dirstate added may need backup
3505 dsmodified |= modified & dsadded # dirstate added may need backup
3506 modified -= dsmodified
3506 modified -= dsmodified
3507
3507
3508 # We need to wait for some post-processing to update this set
3508 # We need to wait for some post-processing to update this set
3509 # before making the distinction. The dirstate will be used for
3509 # before making the distinction. The dirstate will be used for
3510 # that purpose.
3510 # that purpose.
3511 dsadded = added
3511 dsadded = added
3512
3512
3513 # in case of merge, files that are actually added can be reported as
3513 # in case of merge, files that are actually added can be reported as
3514 # modified, we need to post process the result
3514 # modified, we need to post process the result
3515 if p2 != repo.nullid:
3515 if p2 != repo.nullid:
3516 mergeadd = set(dsmodified)
3516 mergeadd = set(dsmodified)
3517 for path in dsmodified:
3517 for path in dsmodified:
3518 if path in mf:
3518 if path in mf:
3519 mergeadd.remove(path)
3519 mergeadd.remove(path)
3520 dsadded |= mergeadd
3520 dsadded |= mergeadd
3521 dsmodified -= mergeadd
3521 dsmodified -= mergeadd
3522
3522
3523 # if f is a rename, update `names` to also revert the source
3523 # if f is a rename, update `names` to also revert the source
3524 for f in localchanges:
3524 for f in localchanges:
3525 src = repo.dirstate.copied(f)
3525 src = repo.dirstate.copied(f)
3526 # XXX should we check for rename down to target node?
3526 # XXX should we check for rename down to target node?
3527 if (
3527 if (
3528 src
3528 src
3529 and src not in names
3529 and src not in names
3530 and repo.dirstate.get_entry(src).removed
3530 and repo.dirstate.get_entry(src).removed
3531 ):
3531 ):
3532 dsremoved.add(src)
3532 dsremoved.add(src)
3533 names[src] = True
3533 names[src] = True
3534
3534
3535 # determine the exact nature of the deleted changesets
3535 # determine the exact nature of the deleted changesets
3536 deladded = set(_deleted)
3536 deladded = set(_deleted)
3537 for path in _deleted:
3537 for path in _deleted:
3538 if path in mf:
3538 if path in mf:
3539 deladded.remove(path)
3539 deladded.remove(path)
3540 deleted = _deleted - deladded
3540 deleted = _deleted - deladded
3541
3541
3542 # distinguish between file to forget and the other
3542 # distinguish between file to forget and the other
3543 added = set()
3543 added = set()
3544 for abs in dsadded:
3544 for abs in dsadded:
3545 if not repo.dirstate.get_entry(abs).added:
3545 if not repo.dirstate.get_entry(abs).added:
3546 added.add(abs)
3546 added.add(abs)
3547 dsadded -= added
3547 dsadded -= added
3548
3548
3549 for abs in deladded:
3549 for abs in deladded:
3550 if repo.dirstate.get_entry(abs).added:
3550 if repo.dirstate.get_entry(abs).added:
3551 dsadded.add(abs)
3551 dsadded.add(abs)
3552 deladded -= dsadded
3552 deladded -= dsadded
3553
3553
3554 # For files marked as removed, we check if an unknown file is present at
3554 # For files marked as removed, we check if an unknown file is present at
3555 # the same path. If a such file exists it may need to be backed up.
3555 # the same path. If a such file exists it may need to be backed up.
3556 # Making the distinction at this stage helps have simpler backup
3556 # Making the distinction at this stage helps have simpler backup
3557 # logic.
3557 # logic.
3558 removunk = set()
3558 removunk = set()
3559 for abs in removed:
3559 for abs in removed:
3560 target = repo.wjoin(abs)
3560 target = repo.wjoin(abs)
3561 if os.path.lexists(target):
3561 if os.path.lexists(target):
3562 removunk.add(abs)
3562 removunk.add(abs)
3563 removed -= removunk
3563 removed -= removunk
3564
3564
3565 dsremovunk = set()
3565 dsremovunk = set()
3566 for abs in dsremoved:
3566 for abs in dsremoved:
3567 target = repo.wjoin(abs)
3567 target = repo.wjoin(abs)
3568 if os.path.lexists(target):
3568 if os.path.lexists(target):
3569 dsremovunk.add(abs)
3569 dsremovunk.add(abs)
3570 dsremoved -= dsremovunk
3570 dsremoved -= dsremovunk
3571
3571
3572 # action to be actually performed by revert
3572 # action to be actually performed by revert
3573 # (<list of file>, message>) tuple
3573 # (<list of file>, message>) tuple
3574 actions = {
3574 actions = {
3575 b'revert': ([], _(b'reverting %s\n')),
3575 b'revert': ([], _(b'reverting %s\n')),
3576 b'add': ([], _(b'adding %s\n')),
3576 b'add': ([], _(b'adding %s\n')),
3577 b'remove': ([], _(b'removing %s\n')),
3577 b'remove': ([], _(b'removing %s\n')),
3578 b'drop': ([], _(b'removing %s\n')),
3578 b'drop': ([], _(b'removing %s\n')),
3579 b'forget': ([], _(b'forgetting %s\n')),
3579 b'forget': ([], _(b'forgetting %s\n')),
3580 b'undelete': ([], _(b'undeleting %s\n')),
3580 b'undelete': ([], _(b'undeleting %s\n')),
3581 b'noop': (None, _(b'no changes needed to %s\n')),
3581 b'noop': (None, _(b'no changes needed to %s\n')),
3582 b'unknown': (None, _(b'file not managed: %s\n')),
3582 b'unknown': (None, _(b'file not managed: %s\n')),
3583 }
3583 }
3584
3584
3585 # "constant" that convey the backup strategy.
3585 # "constant" that convey the backup strategy.
3586 # All set to `discard` if `no-backup` is set do avoid checking
3586 # All set to `discard` if `no-backup` is set do avoid checking
3587 # no_backup lower in the code.
3587 # no_backup lower in the code.
3588 # These values are ordered for comparison purposes
3588 # These values are ordered for comparison purposes
3589 backupinteractive = 3 # do backup if interactively modified
3589 backupinteractive = 3 # do backup if interactively modified
3590 backup = 2 # unconditionally do backup
3590 backup = 2 # unconditionally do backup
3591 check = 1 # check if the existing file differs from target
3591 check = 1 # check if the existing file differs from target
3592 discard = 0 # never do backup
3592 discard = 0 # never do backup
3593 if opts.get(b'no_backup'):
3593 if opts.get(b'no_backup'):
3594 backupinteractive = backup = check = discard
3594 backupinteractive = backup = check = discard
3595 if interactive:
3595 if interactive:
3596 dsmodifiedbackup = backupinteractive
3596 dsmodifiedbackup = backupinteractive
3597 else:
3597 else:
3598 dsmodifiedbackup = backup
3598 dsmodifiedbackup = backup
3599 tobackup = set()
3599 tobackup = set()
3600
3600
3601 backupanddel = actions[b'remove']
3601 backupanddel = actions[b'remove']
3602 if not opts.get(b'no_backup'):
3602 if not opts.get(b'no_backup'):
3603 backupanddel = actions[b'drop']
3603 backupanddel = actions[b'drop']
3604
3604
3605 disptable = (
3605 disptable = (
3606 # dispatch table:
3606 # dispatch table:
3607 # file state
3607 # file state
3608 # action
3608 # action
3609 # make backup
3609 # make backup
3610 ## Sets that results that will change file on disk
3610 ## Sets that results that will change file on disk
3611 # Modified compared to target, no local change
3611 # Modified compared to target, no local change
3612 (modified, actions[b'revert'], discard),
3612 (modified, actions[b'revert'], discard),
3613 # Modified compared to target, but local file is deleted
3613 # Modified compared to target, but local file is deleted
3614 (deleted, actions[b'revert'], discard),
3614 (deleted, actions[b'revert'], discard),
3615 # Modified compared to target, local change
3615 # Modified compared to target, local change
3616 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3616 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3617 # Added since target
3617 # Added since target
3618 (added, actions[b'remove'], discard),
3618 (added, actions[b'remove'], discard),
3619 # Added in working directory
3619 # Added in working directory
3620 (dsadded, actions[b'forget'], discard),
3620 (dsadded, actions[b'forget'], discard),
3621 # Added since target, have local modification
3621 # Added since target, have local modification
3622 (modadded, backupanddel, backup),
3622 (modadded, backupanddel, backup),
3623 # Added since target but file is missing in working directory
3623 # Added since target but file is missing in working directory
3624 (deladded, actions[b'drop'], discard),
3624 (deladded, actions[b'drop'], discard),
3625 # Removed since target, before working copy parent
3625 # Removed since target, before working copy parent
3626 (removed, actions[b'add'], discard),
3626 (removed, actions[b'add'], discard),
3627 # Same as `removed` but an unknown file exists at the same path
3627 # Same as `removed` but an unknown file exists at the same path
3628 (removunk, actions[b'add'], check),
3628 (removunk, actions[b'add'], check),
3629 # Removed since targe, marked as such in working copy parent
3629 # Removed since targe, marked as such in working copy parent
3630 (dsremoved, actions[b'undelete'], discard),
3630 (dsremoved, actions[b'undelete'], discard),
3631 # Same as `dsremoved` but an unknown file exists at the same path
3631 # Same as `dsremoved` but an unknown file exists at the same path
3632 (dsremovunk, actions[b'undelete'], check),
3632 (dsremovunk, actions[b'undelete'], check),
3633 ## the following sets does not result in any file changes
3633 ## the following sets does not result in any file changes
3634 # File with no modification
3634 # File with no modification
3635 (clean, actions[b'noop'], discard),
3635 (clean, actions[b'noop'], discard),
3636 # Existing file, not tracked anywhere
3636 # Existing file, not tracked anywhere
3637 (unknown, actions[b'unknown'], discard),
3637 (unknown, actions[b'unknown'], discard),
3638 )
3638 )
3639
3639
3640 for abs, exact in sorted(names.items()):
3640 for abs, exact in sorted(names.items()):
3641 # target file to be touch on disk (relative to cwd)
3641 # target file to be touch on disk (relative to cwd)
3642 target = repo.wjoin(abs)
3642 target = repo.wjoin(abs)
3643 # search the entry in the dispatch table.
3643 # search the entry in the dispatch table.
3644 # if the file is in any of these sets, it was touched in the working
3644 # if the file is in any of these sets, it was touched in the working
3645 # directory parent and we are sure it needs to be reverted.
3645 # directory parent and we are sure it needs to be reverted.
3646 for table, (xlist, msg), dobackup in disptable:
3646 for table, (xlist, msg), dobackup in disptable:
3647 if abs not in table:
3647 if abs not in table:
3648 continue
3648 continue
3649 if xlist is not None:
3649 if xlist is not None:
3650 xlist.append(abs)
3650 xlist.append(abs)
3651 if dobackup:
3651 if dobackup:
3652 # If in interactive mode, don't automatically create
3652 # If in interactive mode, don't automatically create
3653 # .orig files (issue4793)
3653 # .orig files (issue4793)
3654 if dobackup == backupinteractive:
3654 if dobackup == backupinteractive:
3655 tobackup.add(abs)
3655 tobackup.add(abs)
3656 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3656 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3657 absbakname = scmutil.backuppath(ui, repo, abs)
3657 absbakname = scmutil.backuppath(ui, repo, abs)
3658 bakname = os.path.relpath(
3658 bakname = os.path.relpath(
3659 absbakname, start=repo.root
3659 absbakname, start=repo.root
3660 )
3660 )
3661 ui.note(
3661 ui.note(
3662 _(b'saving current version of %s as %s\n')
3662 _(b'saving current version of %s as %s\n')
3663 % (uipathfn(abs), uipathfn(bakname))
3663 % (uipathfn(abs), uipathfn(bakname))
3664 )
3664 )
3665 if not opts.get(b'dry_run'):
3665 if not opts.get(b'dry_run'):
3666 if interactive:
3666 if interactive:
3667 util.copyfile(target, absbakname)
3667 util.copyfile(target, absbakname)
3668 else:
3668 else:
3669 util.rename(target, absbakname)
3669 util.rename(target, absbakname)
3670 if opts.get(b'dry_run'):
3670 if opts.get(b'dry_run'):
3671 if ui.verbose or not exact:
3671 if ui.verbose or not exact:
3672 ui.status(msg % uipathfn(abs))
3672 ui.status(msg % uipathfn(abs))
3673 elif exact:
3673 elif exact:
3674 ui.warn(msg % uipathfn(abs))
3674 ui.warn(msg % uipathfn(abs))
3675 break
3675 break
3676
3676
3677 if not opts.get(b'dry_run'):
3677 if not opts.get(b'dry_run'):
3678 needdata = (b'revert', b'add', b'undelete')
3678 needdata = (b'revert', b'add', b'undelete')
3679 oplist = [actions[name][0] for name in needdata]
3679 oplist = [actions[name][0] for name in needdata]
3680 prefetch = scmutil.prefetchfiles
3680 prefetch = scmutil.prefetchfiles
3681 matchfiles = scmutil.matchfiles(
3681 matchfiles = scmutil.matchfiles(
3682 repo, [f for sublist in oplist for f in sublist]
3682 repo, [f for sublist in oplist for f in sublist]
3683 )
3683 )
3684 prefetch(
3684 prefetch(
3685 repo,
3685 repo,
3686 [(ctx.rev(), matchfiles)],
3686 [(ctx.rev(), matchfiles)],
3687 )
3687 )
3688 match = scmutil.match(repo[None], pats)
3688 match = scmutil.match(repo[None], pats)
3689 _performrevert(
3689 _performrevert(
3690 repo,
3690 repo,
3691 ctx,
3691 ctx,
3692 names,
3692 names,
3693 uipathfn,
3693 uipathfn,
3694 actions,
3694 actions,
3695 match,
3695 match,
3696 interactive,
3696 interactive,
3697 tobackup,
3697 tobackup,
3698 )
3698 )
3699
3699
3700 if targetsubs:
3700 if targetsubs:
3701 # Revert the subrepos on the revert list
3701 # Revert the subrepos on the revert list
3702 for sub in targetsubs:
3702 for sub in targetsubs:
3703 try:
3703 try:
3704 wctx.sub(sub).revert(
3704 wctx.sub(sub).revert(
3705 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3705 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3706 )
3706 )
3707 except KeyError:
3707 except KeyError:
3708 raise error.Abort(
3708 raise error.Abort(
3709 b"subrepository '%s' does not exist in %s!"
3709 b"subrepository '%s' does not exist in %s!"
3710 % (sub, short(ctx.node()))
3710 % (sub, short(ctx.node()))
3711 )
3711 )
3712
3712
3713
3713
3714 def _performrevert(
3714 def _performrevert(
3715 repo,
3715 repo,
3716 ctx,
3716 ctx,
3717 names,
3717 names,
3718 uipathfn,
3718 uipathfn,
3719 actions,
3719 actions,
3720 match,
3720 match,
3721 interactive=False,
3721 interactive=False,
3722 tobackup=None,
3722 tobackup=None,
3723 ):
3723 ):
3724 """function that actually perform all the actions computed for revert
3724 """function that actually perform all the actions computed for revert
3725
3725
3726 This is an independent function to let extension to plug in and react to
3726 This is an independent function to let extension to plug in and react to
3727 the imminent revert.
3727 the imminent revert.
3728
3728
3729 Make sure you have the working directory locked when calling this function.
3729 Make sure you have the working directory locked when calling this function.
3730 """
3730 """
3731 parent, p2 = repo.dirstate.parents()
3731 parent, p2 = repo.dirstate.parents()
3732 node = ctx.node()
3732 node = ctx.node()
3733 excluded_files = []
3733 excluded_files = []
3734
3734
3735 def checkout(f):
3735 def checkout(f):
3736 fc = ctx[f]
3736 fc = ctx[f]
3737 repo.wwrite(f, fc.data(), fc.flags())
3737 repo.wwrite(f, fc.data(), fc.flags())
3738
3738
3739 def doremove(f):
3739 def doremove(f):
3740 try:
3740 try:
3741 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3741 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3742 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3742 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3743 except OSError:
3743 except OSError:
3744 pass
3744 pass
3745 repo.dirstate.set_untracked(f)
3745 repo.dirstate.set_untracked(f)
3746
3746
3747 def prntstatusmsg(action, f):
3747 def prntstatusmsg(action, f):
3748 exact = names[f]
3748 exact = names[f]
3749 if repo.ui.verbose or not exact:
3749 if repo.ui.verbose or not exact:
3750 repo.ui.status(actions[action][1] % uipathfn(f))
3750 repo.ui.status(actions[action][1] % uipathfn(f))
3751
3751
3752 audit_path = pathutil.pathauditor(repo.root, cached=True)
3752 audit_path = pathutil.pathauditor(repo.root, cached=True)
3753 for f in actions[b'forget'][0]:
3753 for f in actions[b'forget'][0]:
3754 if interactive:
3754 if interactive:
3755 choice = repo.ui.promptchoice(
3755 choice = repo.ui.promptchoice(
3756 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3756 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3757 )
3757 )
3758 if choice == 0:
3758 if choice == 0:
3759 prntstatusmsg(b'forget', f)
3759 prntstatusmsg(b'forget', f)
3760 repo.dirstate.set_untracked(f)
3760 repo.dirstate.set_untracked(f)
3761 else:
3761 else:
3762 excluded_files.append(f)
3762 excluded_files.append(f)
3763 else:
3763 else:
3764 prntstatusmsg(b'forget', f)
3764 prntstatusmsg(b'forget', f)
3765 repo.dirstate.set_untracked(f)
3765 repo.dirstate.set_untracked(f)
3766 for f in actions[b'remove'][0]:
3766 for f in actions[b'remove'][0]:
3767 audit_path(f)
3767 audit_path(f)
3768 if interactive:
3768 if interactive:
3769 choice = repo.ui.promptchoice(
3769 choice = repo.ui.promptchoice(
3770 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3770 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3771 )
3771 )
3772 if choice == 0:
3772 if choice == 0:
3773 prntstatusmsg(b'remove', f)
3773 prntstatusmsg(b'remove', f)
3774 doremove(f)
3774 doremove(f)
3775 else:
3775 else:
3776 excluded_files.append(f)
3776 excluded_files.append(f)
3777 else:
3777 else:
3778 prntstatusmsg(b'remove', f)
3778 prntstatusmsg(b'remove', f)
3779 doremove(f)
3779 doremove(f)
3780 for f in actions[b'drop'][0]:
3780 for f in actions[b'drop'][0]:
3781 audit_path(f)
3781 audit_path(f)
3782 prntstatusmsg(b'drop', f)
3782 prntstatusmsg(b'drop', f)
3783 repo.dirstate.set_untracked(f)
3783 repo.dirstate.set_untracked(f)
3784
3784
3785 # We are reverting to our parent. If possible, we had like `hg status`
3785 # We are reverting to our parent. If possible, we had like `hg status`
3786 # to report the file as clean. We have to be less agressive for
3786 # to report the file as clean. We have to be less agressive for
3787 # merges to avoid losing information about copy introduced by the merge.
3787 # merges to avoid losing information about copy introduced by the merge.
3788 # This might comes with bugs ?
3788 # This might comes with bugs ?
3789 reset_copy = p2 == repo.nullid
3789 reset_copy = p2 == repo.nullid
3790
3790
3791 def normal(filename):
3791 def normal(filename):
3792 return repo.dirstate.set_tracked(filename, reset_copy=reset_copy)
3792 return repo.dirstate.set_tracked(filename, reset_copy=reset_copy)
3793
3793
3794 newlyaddedandmodifiedfiles = set()
3794 newlyaddedandmodifiedfiles = set()
3795 if interactive:
3795 if interactive:
3796 # Prompt the user for changes to revert
3796 # Prompt the user for changes to revert
3797 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3797 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3798 m = scmutil.matchfiles(repo, torevert)
3798 m = scmutil.matchfiles(repo, torevert)
3799 diffopts = patch.difffeatureopts(
3799 diffopts = patch.difffeatureopts(
3800 repo.ui,
3800 repo.ui,
3801 whitespace=True,
3801 whitespace=True,
3802 section=b'commands',
3802 section=b'commands',
3803 configprefix=b'revert.interactive.',
3803 configprefix=b'revert.interactive.',
3804 )
3804 )
3805 diffopts.nodates = True
3805 diffopts.nodates = True
3806 diffopts.git = True
3806 diffopts.git = True
3807 operation = b'apply'
3807 operation = b'apply'
3808 if node == parent:
3808 if node == parent:
3809 if repo.ui.configbool(
3809 if repo.ui.configbool(
3810 b'experimental', b'revert.interactive.select-to-keep'
3810 b'experimental', b'revert.interactive.select-to-keep'
3811 ):
3811 ):
3812 operation = b'keep'
3812 operation = b'keep'
3813 else:
3813 else:
3814 operation = b'discard'
3814 operation = b'discard'
3815
3815
3816 if operation == b'apply':
3816 if operation == b'apply':
3817 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3817 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3818 else:
3818 else:
3819 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3819 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3820 original_headers = patch.parsepatch(diff)
3820 original_headers = patch.parsepatch(diff)
3821
3821
3822 try:
3822 try:
3823
3823
3824 chunks, opts = recordfilter(
3824 chunks, opts = recordfilter(
3825 repo.ui, original_headers, match, operation=operation
3825 repo.ui, original_headers, match, operation=operation
3826 )
3826 )
3827 if operation == b'discard':
3827 if operation == b'discard':
3828 chunks = patch.reversehunks(chunks)
3828 chunks = patch.reversehunks(chunks)
3829
3829
3830 except error.PatchParseError as err:
3830 except error.PatchParseError as err:
3831 raise error.InputError(_(b'error parsing patch: %s') % err)
3831 raise error.InputError(_(b'error parsing patch: %s') % err)
3832 except error.PatchApplicationError as err:
3832 except error.PatchApplicationError as err:
3833 raise error.StateError(_(b'error applying patch: %s') % err)
3833 raise error.StateError(_(b'error applying patch: %s') % err)
3834
3834
3835 # FIXME: when doing an interactive revert of a copy, there's no way of
3835 # FIXME: when doing an interactive revert of a copy, there's no way of
3836 # performing a partial revert of the added file, the only option is
3836 # performing a partial revert of the added file, the only option is
3837 # "remove added file <name> (Yn)?", so we don't need to worry about the
3837 # "remove added file <name> (Yn)?", so we don't need to worry about the
3838 # alsorestore value. Ideally we'd be able to partially revert
3838 # alsorestore value. Ideally we'd be able to partially revert
3839 # copied/renamed files.
3839 # copied/renamed files.
3840 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(chunks)
3840 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(chunks)
3841 if tobackup is None:
3841 if tobackup is None:
3842 tobackup = set()
3842 tobackup = set()
3843 # Apply changes
3843 # Apply changes
3844 fp = stringio()
3844 fp = stringio()
3845 # chunks are serialized per file, but files aren't sorted
3845 # chunks are serialized per file, but files aren't sorted
3846 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3846 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3847 prntstatusmsg(b'revert', f)
3847 prntstatusmsg(b'revert', f)
3848 files = set()
3848 files = set()
3849 for c in chunks:
3849 for c in chunks:
3850 if ishunk(c):
3850 if ishunk(c):
3851 abs = c.header.filename()
3851 abs = c.header.filename()
3852 # Create a backup file only if this hunk should be backed up
3852 # Create a backup file only if this hunk should be backed up
3853 if c.header.filename() in tobackup:
3853 if c.header.filename() in tobackup:
3854 target = repo.wjoin(abs)
3854 target = repo.wjoin(abs)
3855 bakname = scmutil.backuppath(repo.ui, repo, abs)
3855 bakname = scmutil.backuppath(repo.ui, repo, abs)
3856 util.copyfile(target, bakname)
3856 util.copyfile(target, bakname)
3857 tobackup.remove(abs)
3857 tobackup.remove(abs)
3858 if abs not in files:
3858 if abs not in files:
3859 files.add(abs)
3859 files.add(abs)
3860 if operation == b'keep':
3860 if operation == b'keep':
3861 checkout(abs)
3861 checkout(abs)
3862 c.write(fp)
3862 c.write(fp)
3863 dopatch = fp.tell()
3863 dopatch = fp.tell()
3864 fp.seek(0)
3864 fp.seek(0)
3865 if dopatch:
3865 if dopatch:
3866 try:
3866 try:
3867 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3867 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3868 except error.PatchParseError as err:
3868 except error.PatchParseError as err:
3869 raise error.InputError(pycompat.bytestr(err))
3869 raise error.InputError(pycompat.bytestr(err))
3870 except error.PatchApplicationError as err:
3870 except error.PatchApplicationError as err:
3871 raise error.StateError(pycompat.bytestr(err))
3871 raise error.StateError(pycompat.bytestr(err))
3872 del fp
3872 del fp
3873 else:
3873 else:
3874 for f in actions[b'revert'][0]:
3874 for f in actions[b'revert'][0]:
3875 prntstatusmsg(b'revert', f)
3875 prntstatusmsg(b'revert', f)
3876 checkout(f)
3876 checkout(f)
3877 if normal:
3877 if normal:
3878 normal(f)
3878 normal(f)
3879
3879
3880 for f in actions[b'add'][0]:
3880 for f in actions[b'add'][0]:
3881 # Don't checkout modified files, they are already created by the diff
3881 # Don't checkout modified files, they are already created by the diff
3882 if f in newlyaddedandmodifiedfiles:
3882 if f in newlyaddedandmodifiedfiles:
3883 continue
3883 continue
3884
3884
3885 if interactive:
3885 if interactive:
3886 choice = repo.ui.promptchoice(
3886 choice = repo.ui.promptchoice(
3887 _(b"add new file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3887 _(b"add new file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3888 )
3888 )
3889 if choice != 0:
3889 if choice != 0:
3890 continue
3890 continue
3891 prntstatusmsg(b'add', f)
3891 prntstatusmsg(b'add', f)
3892 checkout(f)
3892 checkout(f)
3893 repo.dirstate.set_tracked(f)
3893 repo.dirstate.set_tracked(f)
3894
3894
3895 for f in actions[b'undelete'][0]:
3895 for f in actions[b'undelete'][0]:
3896 if interactive:
3896 if interactive:
3897 choice = repo.ui.promptchoice(
3897 choice = repo.ui.promptchoice(
3898 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3898 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3899 )
3899 )
3900 if choice == 0:
3900 if choice == 0:
3901 prntstatusmsg(b'undelete', f)
3901 prntstatusmsg(b'undelete', f)
3902 checkout(f)
3902 checkout(f)
3903 normal(f)
3903 normal(f)
3904 else:
3904 else:
3905 excluded_files.append(f)
3905 excluded_files.append(f)
3906 else:
3906 else:
3907 prntstatusmsg(b'undelete', f)
3907 prntstatusmsg(b'undelete', f)
3908 checkout(f)
3908 checkout(f)
3909 normal(f)
3909 normal(f)
3910
3910
3911 copied = copies.pathcopies(repo[parent], ctx)
3911 copied = copies.pathcopies(repo[parent], ctx)
3912
3912
3913 for f in (
3913 for f in (
3914 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3914 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3915 ):
3915 ):
3916 if f in copied:
3916 if f in copied:
3917 repo.dirstate.copy(copied[f], f)
3917 repo.dirstate.copy(copied[f], f)
3918
3918
3919
3919
3920 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3920 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3921 # commands.outgoing. "missing" is "missing" of the result of
3921 # commands.outgoing. "missing" is "missing" of the result of
3922 # "findcommonoutgoing()"
3922 # "findcommonoutgoing()"
3923 outgoinghooks = util.hooks()
3923 outgoinghooks = util.hooks()
3924
3924
3925 # a list of (ui, repo) functions called by commands.summary
3925 # a list of (ui, repo) functions called by commands.summary
3926 summaryhooks = util.hooks()
3926 summaryhooks = util.hooks()
3927
3927
3928 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3928 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3929 #
3929 #
3930 # functions should return tuple of booleans below, if 'changes' is None:
3930 # functions should return tuple of booleans below, if 'changes' is None:
3931 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3931 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3932 #
3932 #
3933 # otherwise, 'changes' is a tuple of tuples below:
3933 # otherwise, 'changes' is a tuple of tuples below:
3934 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3934 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3935 # - (desturl, destbranch, destpeer, outgoing)
3935 # - (desturl, destbranch, destpeer, outgoing)
3936 summaryremotehooks = util.hooks()
3936 summaryremotehooks = util.hooks()
3937
3937
3938
3938
3939 def checkunfinished(repo, commit=False, skipmerge=False):
3939 def checkunfinished(repo, commit=False, skipmerge=False):
3940 """Look for an unfinished multistep operation, like graft, and abort
3940 """Look for an unfinished multistep operation, like graft, and abort
3941 if found. It's probably good to check this right before
3941 if found. It's probably good to check this right before
3942 bailifchanged().
3942 bailifchanged().
3943 """
3943 """
3944 # Check for non-clearable states first, so things like rebase will take
3944 # Check for non-clearable states first, so things like rebase will take
3945 # precedence over update.
3945 # precedence over update.
3946 for state in statemod._unfinishedstates:
3946 for state in statemod._unfinishedstates:
3947 if (
3947 if (
3948 state._clearable
3948 state._clearable
3949 or (commit and state._allowcommit)
3949 or (commit and state._allowcommit)
3950 or state._reportonly
3950 or state._reportonly
3951 ):
3951 ):
3952 continue
3952 continue
3953 if state.isunfinished(repo):
3953 if state.isunfinished(repo):
3954 raise error.StateError(state.msg(), hint=state.hint())
3954 raise error.StateError(state.msg(), hint=state.hint())
3955
3955
3956 for s in statemod._unfinishedstates:
3956 for s in statemod._unfinishedstates:
3957 if (
3957 if (
3958 not s._clearable
3958 not s._clearable
3959 or (commit and s._allowcommit)
3959 or (commit and s._allowcommit)
3960 or (s._opname == b'merge' and skipmerge)
3960 or (s._opname == b'merge' and skipmerge)
3961 or s._reportonly
3961 or s._reportonly
3962 ):
3962 ):
3963 continue
3963 continue
3964 if s.isunfinished(repo):
3964 if s.isunfinished(repo):
3965 raise error.StateError(s.msg(), hint=s.hint())
3965 raise error.StateError(s.msg(), hint=s.hint())
3966
3966
3967
3967
3968 def clearunfinished(repo):
3968 def clearunfinished(repo):
3969 """Check for unfinished operations (as above), and clear the ones
3969 """Check for unfinished operations (as above), and clear the ones
3970 that are clearable.
3970 that are clearable.
3971 """
3971 """
3972 for state in statemod._unfinishedstates:
3972 for state in statemod._unfinishedstates:
3973 if state._reportonly:
3973 if state._reportonly:
3974 continue
3974 continue
3975 if not state._clearable and state.isunfinished(repo):
3975 if not state._clearable and state.isunfinished(repo):
3976 raise error.StateError(state.msg(), hint=state.hint())
3976 raise error.StateError(state.msg(), hint=state.hint())
3977
3977
3978 for s in statemod._unfinishedstates:
3978 for s in statemod._unfinishedstates:
3979 if s._opname == b'merge' or s._reportonly:
3979 if s._opname == b'merge' or s._reportonly:
3980 continue
3980 continue
3981 if s._clearable and s.isunfinished(repo):
3981 if s._clearable and s.isunfinished(repo):
3982 util.unlink(repo.vfs.join(s._fname))
3982 util.unlink(repo.vfs.join(s._fname))
3983
3983
3984
3984
3985 def getunfinishedstate(repo):
3985 def getunfinishedstate(repo):
3986 """Checks for unfinished operations and returns statecheck object
3986 """Checks for unfinished operations and returns statecheck object
3987 for it"""
3987 for it"""
3988 for state in statemod._unfinishedstates:
3988 for state in statemod._unfinishedstates:
3989 if state.isunfinished(repo):
3989 if state.isunfinished(repo):
3990 return state
3990 return state
3991 return None
3991 return None
3992
3992
3993
3993
3994 def howtocontinue(repo):
3994 def howtocontinue(repo):
3995 """Check for an unfinished operation and return the command to finish
3995 """Check for an unfinished operation and return the command to finish
3996 it.
3996 it.
3997
3997
3998 statemod._unfinishedstates list is checked for an unfinished operation
3998 statemod._unfinishedstates list is checked for an unfinished operation
3999 and the corresponding message to finish it is generated if a method to
3999 and the corresponding message to finish it is generated if a method to
4000 continue is supported by the operation.
4000 continue is supported by the operation.
4001
4001
4002 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
4002 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
4003 a boolean.
4003 a boolean.
4004 """
4004 """
4005 contmsg = _(b"continue: %s")
4005 contmsg = _(b"continue: %s")
4006 for state in statemod._unfinishedstates:
4006 for state in statemod._unfinishedstates:
4007 if not state._continueflag:
4007 if not state._continueflag:
4008 continue
4008 continue
4009 if state.isunfinished(repo):
4009 if state.isunfinished(repo):
4010 return contmsg % state.continuemsg(), True
4010 return contmsg % state.continuemsg(), True
4011 if repo[None].dirty(missing=True, merge=False, branch=False):
4011 if repo[None].dirty(missing=True, merge=False, branch=False):
4012 return contmsg % _(b"hg commit"), False
4012 return contmsg % _(b"hg commit"), False
4013 return None, None
4013 return None, None
4014
4014
4015
4015
4016 def checkafterresolved(repo):
4016 def checkafterresolved(repo):
4017 """Inform the user about the next action after completing hg resolve
4017 """Inform the user about the next action after completing hg resolve
4018
4018
4019 If there's a an unfinished operation that supports continue flag,
4019 If there's a an unfinished operation that supports continue flag,
4020 howtocontinue will yield repo.ui.warn as the reporter.
4020 howtocontinue will yield repo.ui.warn as the reporter.
4021
4021
4022 Otherwise, it will yield repo.ui.note.
4022 Otherwise, it will yield repo.ui.note.
4023 """
4023 """
4024 msg, warning = howtocontinue(repo)
4024 msg, warning = howtocontinue(repo)
4025 if msg is not None:
4025 if msg is not None:
4026 if warning:
4026 if warning:
4027 repo.ui.warn(b"%s\n" % msg)
4027 repo.ui.warn(b"%s\n" % msg)
4028 else:
4028 else:
4029 repo.ui.note(b"%s\n" % msg)
4029 repo.ui.note(b"%s\n" % msg)
4030
4030
4031
4031
4032 def wrongtooltocontinue(repo, task):
4032 def wrongtooltocontinue(repo, task):
4033 """Raise an abort suggesting how to properly continue if there is an
4033 """Raise an abort suggesting how to properly continue if there is an
4034 active task.
4034 active task.
4035
4035
4036 Uses howtocontinue() to find the active task.
4036 Uses howtocontinue() to find the active task.
4037
4037
4038 If there's no task (repo.ui.note for 'hg commit'), it does not offer
4038 If there's no task (repo.ui.note for 'hg commit'), it does not offer
4039 a hint.
4039 a hint.
4040 """
4040 """
4041 after = howtocontinue(repo)
4041 after = howtocontinue(repo)
4042 hint = None
4042 hint = None
4043 if after[1]:
4043 if after[1]:
4044 hint = after[0]
4044 hint = after[0]
4045 raise error.StateError(_(b'no %s in progress') % task, hint=hint)
4045 raise error.StateError(_(b'no %s in progress') % task, hint=hint)
4046
4046
4047
4047
4048 def abortgraft(ui, repo, graftstate):
4048 def abortgraft(ui, repo, graftstate):
4049 """abort the interrupted graft and rollbacks to the state before interrupted
4049 """abort the interrupted graft and rollbacks to the state before interrupted
4050 graft"""
4050 graft"""
4051 if not graftstate.exists():
4051 if not graftstate.exists():
4052 raise error.StateError(_(b"no interrupted graft to abort"))
4052 raise error.StateError(_(b"no interrupted graft to abort"))
4053 statedata = readgraftstate(repo, graftstate)
4053 statedata = readgraftstate(repo, graftstate)
4054 newnodes = statedata.get(b'newnodes')
4054 newnodes = statedata.get(b'newnodes')
4055 if newnodes is None:
4055 if newnodes is None:
4056 # and old graft state which does not have all the data required to abort
4056 # and old graft state which does not have all the data required to abort
4057 # the graft
4057 # the graft
4058 raise error.Abort(_(b"cannot abort using an old graftstate"))
4058 raise error.Abort(_(b"cannot abort using an old graftstate"))
4059
4059
4060 # changeset from which graft operation was started
4060 # changeset from which graft operation was started
4061 if len(newnodes) > 0:
4061 if len(newnodes) > 0:
4062 startctx = repo[newnodes[0]].p1()
4062 startctx = repo[newnodes[0]].p1()
4063 else:
4063 else:
4064 startctx = repo[b'.']
4064 startctx = repo[b'.']
4065 # whether to strip or not
4065 # whether to strip or not
4066 cleanup = False
4066 cleanup = False
4067
4067
4068 if newnodes:
4068 if newnodes:
4069 newnodes = [repo[r].rev() for r in newnodes]
4069 newnodes = [repo[r].rev() for r in newnodes]
4070 cleanup = True
4070 cleanup = True
4071 # checking that none of the newnodes turned public or is public
4071 # checking that none of the newnodes turned public or is public
4072 immutable = [c for c in newnodes if not repo[c].mutable()]
4072 immutable = [c for c in newnodes if not repo[c].mutable()]
4073 if immutable:
4073 if immutable:
4074 repo.ui.warn(
4074 repo.ui.warn(
4075 _(b"cannot clean up public changesets %s\n")
4075 _(b"cannot clean up public changesets %s\n")
4076 % b', '.join(bytes(repo[r]) for r in immutable),
4076 % b', '.join(bytes(repo[r]) for r in immutable),
4077 hint=_(b"see 'hg help phases' for details"),
4077 hint=_(b"see 'hg help phases' for details"),
4078 )
4078 )
4079 cleanup = False
4079 cleanup = False
4080
4080
4081 # checking that no new nodes are created on top of grafted revs
4081 # checking that no new nodes are created on top of grafted revs
4082 desc = set(repo.changelog.descendants(newnodes))
4082 desc = set(repo.changelog.descendants(newnodes))
4083 if desc - set(newnodes):
4083 if desc - set(newnodes):
4084 repo.ui.warn(
4084 repo.ui.warn(
4085 _(
4085 _(
4086 b"new changesets detected on destination "
4086 b"new changesets detected on destination "
4087 b"branch, can't strip\n"
4087 b"branch, can't strip\n"
4088 )
4088 )
4089 )
4089 )
4090 cleanup = False
4090 cleanup = False
4091
4091
4092 if cleanup:
4092 if cleanup:
4093 with repo.wlock(), repo.lock():
4093 with repo.wlock(), repo.lock():
4094 mergemod.clean_update(startctx)
4094 mergemod.clean_update(startctx)
4095 # stripping the new nodes created
4095 # stripping the new nodes created
4096 strippoints = [
4096 strippoints = [
4097 c.node() for c in repo.set(b"roots(%ld)", newnodes)
4097 c.node() for c in repo.set(b"roots(%ld)", newnodes)
4098 ]
4098 ]
4099 repair.strip(repo.ui, repo, strippoints, backup=False)
4099 repair.strip(repo.ui, repo, strippoints, backup=False)
4100
4100
4101 if not cleanup:
4101 if not cleanup:
4102 # we don't update to the startnode if we can't strip
4102 # we don't update to the startnode if we can't strip
4103 startctx = repo[b'.']
4103 startctx = repo[b'.']
4104 mergemod.clean_update(startctx)
4104 mergemod.clean_update(startctx)
4105
4105
4106 ui.status(_(b"graft aborted\n"))
4106 ui.status(_(b"graft aborted\n"))
4107 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4107 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4108 graftstate.delete()
4108 graftstate.delete()
4109 return 0
4109 return 0
4110
4110
4111
4111
4112 def readgraftstate(repo, graftstate):
4112 def readgraftstate(repo, graftstate):
4113 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4113 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4114 """read the graft state file and return a dict of the data stored in it"""
4114 """read the graft state file and return a dict of the data stored in it"""
4115 try:
4115 try:
4116 return graftstate.read()
4116 return graftstate.read()
4117 except error.CorruptedState:
4117 except error.CorruptedState:
4118 nodes = repo.vfs.read(b'graftstate').splitlines()
4118 nodes = repo.vfs.read(b'graftstate').splitlines()
4119 return {b'nodes': nodes}
4119 return {b'nodes': nodes}
4120
4120
4121
4121
4122 def hgabortgraft(ui, repo):
4122 def hgabortgraft(ui, repo):
4123 """abort logic for aborting graft using 'hg abort'"""
4123 """abort logic for aborting graft using 'hg abort'"""
4124 with repo.wlock():
4124 with repo.wlock():
4125 graftstate = statemod.cmdstate(repo, b'graftstate')
4125 graftstate = statemod.cmdstate(repo, b'graftstate')
4126 return abortgraft(ui, repo, graftstate)
4126 return abortgraft(ui, repo, graftstate)
@@ -1,8052 +1,8051 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8
8
9 import os
9 import os
10 import re
10 import re
11 import sys
11 import sys
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 hex,
15 hex,
16 nullid,
16 nullid,
17 nullrev,
17 nullrev,
18 short,
18 short,
19 wdirrev,
19 wdirrev,
20 )
20 )
21 from .pycompat import open
21 from .pycompat import open
22 from . import (
22 from . import (
23 archival,
23 archival,
24 bookmarks,
24 bookmarks,
25 bundle2,
25 bundle2,
26 bundlecaches,
26 bundlecaches,
27 changegroup,
27 changegroup,
28 cmdutil,
28 cmdutil,
29 copies,
29 copies,
30 debugcommands as debugcommandsmod,
30 debugcommands as debugcommandsmod,
31 destutil,
31 destutil,
32 discovery,
32 discovery,
33 encoding,
33 encoding,
34 error,
34 error,
35 exchange,
35 exchange,
36 extensions,
36 extensions,
37 filemerge,
37 filemerge,
38 formatter,
38 formatter,
39 graphmod,
39 graphmod,
40 grep as grepmod,
40 grep as grepmod,
41 hbisect,
41 hbisect,
42 help,
42 help,
43 hg,
43 hg,
44 logcmdutil,
44 logcmdutil,
45 merge as mergemod,
45 merge as mergemod,
46 mergestate as mergestatemod,
46 mergestate as mergestatemod,
47 narrowspec,
47 narrowspec,
48 obsolete,
48 obsolete,
49 obsutil,
49 obsutil,
50 patch,
50 patch,
51 phases,
51 phases,
52 pycompat,
52 pycompat,
53 rcutil,
53 rcutil,
54 registrar,
54 registrar,
55 requirements,
55 requirements,
56 revsetlang,
56 revsetlang,
57 rewriteutil,
57 rewriteutil,
58 scmutil,
58 scmutil,
59 server,
59 server,
60 shelve as shelvemod,
60 shelve as shelvemod,
61 state as statemod,
61 state as statemod,
62 streamclone,
62 streamclone,
63 tags as tagsmod,
63 tags as tagsmod,
64 ui as uimod,
64 ui as uimod,
65 util,
65 util,
66 verify as verifymod,
66 verify as verifymod,
67 vfs as vfsmod,
67 vfs as vfsmod,
68 wireprotoserver,
68 wireprotoserver,
69 )
69 )
70 from .utils import (
70 from .utils import (
71 dateutil,
71 dateutil,
72 procutil,
72 procutil,
73 stringutil,
73 stringutil,
74 urlutil,
74 urlutil,
75 )
75 )
76
76
77 table = {}
77 table = {}
78 table.update(debugcommandsmod.command._table)
78 table.update(debugcommandsmod.command._table)
79
79
80 command = registrar.command(table)
80 command = registrar.command(table)
81 INTENT_READONLY = registrar.INTENT_READONLY
81 INTENT_READONLY = registrar.INTENT_READONLY
82
82
83 # common command options
83 # common command options
84
84
85 globalopts = [
85 globalopts = [
86 (
86 (
87 b'R',
87 b'R',
88 b'repository',
88 b'repository',
89 b'',
89 b'',
90 _(b'repository root directory or name of overlay bundle file'),
90 _(b'repository root directory or name of overlay bundle file'),
91 _(b'REPO'),
91 _(b'REPO'),
92 ),
92 ),
93 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
93 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
94 (
94 (
95 b'y',
95 b'y',
96 b'noninteractive',
96 b'noninteractive',
97 None,
97 None,
98 _(
98 _(
99 b'do not prompt, automatically pick the first choice for all prompts'
99 b'do not prompt, automatically pick the first choice for all prompts'
100 ),
100 ),
101 ),
101 ),
102 (b'q', b'quiet', None, _(b'suppress output')),
102 (b'q', b'quiet', None, _(b'suppress output')),
103 (b'v', b'verbose', None, _(b'enable additional output')),
103 (b'v', b'verbose', None, _(b'enable additional output')),
104 (
104 (
105 b'',
105 b'',
106 b'color',
106 b'color',
107 b'',
107 b'',
108 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
108 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
109 # and should not be translated
109 # and should not be translated
110 _(b"when to colorize (boolean, always, auto, never, or debug)"),
110 _(b"when to colorize (boolean, always, auto, never, or debug)"),
111 _(b'TYPE'),
111 _(b'TYPE'),
112 ),
112 ),
113 (
113 (
114 b'',
114 b'',
115 b'config',
115 b'config',
116 [],
116 [],
117 _(b'set/override config option (use \'section.name=value\')'),
117 _(b'set/override config option (use \'section.name=value\')'),
118 _(b'CONFIG'),
118 _(b'CONFIG'),
119 ),
119 ),
120 (b'', b'debug', None, _(b'enable debugging output')),
120 (b'', b'debug', None, _(b'enable debugging output')),
121 (b'', b'debugger', None, _(b'start debugger')),
121 (b'', b'debugger', None, _(b'start debugger')),
122 (
122 (
123 b'',
123 b'',
124 b'encoding',
124 b'encoding',
125 encoding.encoding,
125 encoding.encoding,
126 _(b'set the charset encoding'),
126 _(b'set the charset encoding'),
127 _(b'ENCODE'),
127 _(b'ENCODE'),
128 ),
128 ),
129 (
129 (
130 b'',
130 b'',
131 b'encodingmode',
131 b'encodingmode',
132 encoding.encodingmode,
132 encoding.encodingmode,
133 _(b'set the charset encoding mode'),
133 _(b'set the charset encoding mode'),
134 _(b'MODE'),
134 _(b'MODE'),
135 ),
135 ),
136 (b'', b'traceback', None, _(b'always print a traceback on exception')),
136 (b'', b'traceback', None, _(b'always print a traceback on exception')),
137 (b'', b'time', None, _(b'time how long the command takes')),
137 (b'', b'time', None, _(b'time how long the command takes')),
138 (b'', b'profile', None, _(b'print command execution profile')),
138 (b'', b'profile', None, _(b'print command execution profile')),
139 (b'', b'version', None, _(b'output version information and exit')),
139 (b'', b'version', None, _(b'output version information and exit')),
140 (b'h', b'help', None, _(b'display help and exit')),
140 (b'h', b'help', None, _(b'display help and exit')),
141 (b'', b'hidden', False, _(b'consider hidden changesets')),
141 (b'', b'hidden', False, _(b'consider hidden changesets')),
142 (
142 (
143 b'',
143 b'',
144 b'pager',
144 b'pager',
145 b'auto',
145 b'auto',
146 _(b"when to paginate (boolean, always, auto, or never)"),
146 _(b"when to paginate (boolean, always, auto, or never)"),
147 _(b'TYPE'),
147 _(b'TYPE'),
148 ),
148 ),
149 ]
149 ]
150
150
151 dryrunopts = cmdutil.dryrunopts
151 dryrunopts = cmdutil.dryrunopts
152 remoteopts = cmdutil.remoteopts
152 remoteopts = cmdutil.remoteopts
153 walkopts = cmdutil.walkopts
153 walkopts = cmdutil.walkopts
154 commitopts = cmdutil.commitopts
154 commitopts = cmdutil.commitopts
155 commitopts2 = cmdutil.commitopts2
155 commitopts2 = cmdutil.commitopts2
156 commitopts3 = cmdutil.commitopts3
156 commitopts3 = cmdutil.commitopts3
157 formatteropts = cmdutil.formatteropts
157 formatteropts = cmdutil.formatteropts
158 templateopts = cmdutil.templateopts
158 templateopts = cmdutil.templateopts
159 logopts = cmdutil.logopts
159 logopts = cmdutil.logopts
160 diffopts = cmdutil.diffopts
160 diffopts = cmdutil.diffopts
161 diffwsopts = cmdutil.diffwsopts
161 diffwsopts = cmdutil.diffwsopts
162 diffopts2 = cmdutil.diffopts2
162 diffopts2 = cmdutil.diffopts2
163 mergetoolopts = cmdutil.mergetoolopts
163 mergetoolopts = cmdutil.mergetoolopts
164 similarityopts = cmdutil.similarityopts
164 similarityopts = cmdutil.similarityopts
165 subrepoopts = cmdutil.subrepoopts
165 subrepoopts = cmdutil.subrepoopts
166 debugrevlogopts = cmdutil.debugrevlogopts
166 debugrevlogopts = cmdutil.debugrevlogopts
167
167
168 # Commands start here, listed alphabetically
168 # Commands start here, listed alphabetically
169
169
170
170
171 @command(
171 @command(
172 b'abort',
172 b'abort',
173 dryrunopts,
173 dryrunopts,
174 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
174 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
175 helpbasic=True,
175 helpbasic=True,
176 )
176 )
177 def abort(ui, repo, **opts):
177 def abort(ui, repo, **opts):
178 """abort an unfinished operation (EXPERIMENTAL)
178 """abort an unfinished operation (EXPERIMENTAL)
179
179
180 Aborts a multistep operation like graft, histedit, rebase, merge,
180 Aborts a multistep operation like graft, histedit, rebase, merge,
181 and unshelve if they are in an unfinished state.
181 and unshelve if they are in an unfinished state.
182
182
183 use --dry-run/-n to dry run the command.
183 use --dry-run/-n to dry run the command.
184 """
184 """
185 dryrun = opts.get('dry_run')
185 dryrun = opts.get('dry_run')
186 abortstate = cmdutil.getunfinishedstate(repo)
186 abortstate = cmdutil.getunfinishedstate(repo)
187 if not abortstate:
187 if not abortstate:
188 raise error.StateError(_(b'no operation in progress'))
188 raise error.StateError(_(b'no operation in progress'))
189 if not abortstate.abortfunc:
189 if not abortstate.abortfunc:
190 raise error.InputError(
190 raise error.InputError(
191 (
191 (
192 _(b"%s in progress but does not support 'hg abort'")
192 _(b"%s in progress but does not support 'hg abort'")
193 % (abortstate._opname)
193 % (abortstate._opname)
194 ),
194 ),
195 hint=abortstate.hint(),
195 hint=abortstate.hint(),
196 )
196 )
197 if dryrun:
197 if dryrun:
198 ui.status(
198 ui.status(
199 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
199 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
200 )
200 )
201 return
201 return
202 return abortstate.abortfunc(ui, repo)
202 return abortstate.abortfunc(ui, repo)
203
203
204
204
205 @command(
205 @command(
206 b'add',
206 b'add',
207 walkopts + subrepoopts + dryrunopts,
207 walkopts + subrepoopts + dryrunopts,
208 _(b'[OPTION]... [FILE]...'),
208 _(b'[OPTION]... [FILE]...'),
209 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
209 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
210 helpbasic=True,
210 helpbasic=True,
211 inferrepo=True,
211 inferrepo=True,
212 )
212 )
213 def add(ui, repo, *pats, **opts):
213 def add(ui, repo, *pats, **opts):
214 """add the specified files on the next commit
214 """add the specified files on the next commit
215
215
216 Schedule files to be version controlled and added to the
216 Schedule files to be version controlled and added to the
217 repository.
217 repository.
218
218
219 The files will be added to the repository at the next commit. To
219 The files will be added to the repository at the next commit. To
220 undo an add before that, see :hg:`forget`.
220 undo an add before that, see :hg:`forget`.
221
221
222 If no names are given, add all files to the repository (except
222 If no names are given, add all files to the repository (except
223 files matching ``.hgignore``).
223 files matching ``.hgignore``).
224
224
225 .. container:: verbose
225 .. container:: verbose
226
226
227 Examples:
227 Examples:
228
228
229 - New (unknown) files are added
229 - New (unknown) files are added
230 automatically by :hg:`add`::
230 automatically by :hg:`add`::
231
231
232 $ ls
232 $ ls
233 foo.c
233 foo.c
234 $ hg status
234 $ hg status
235 ? foo.c
235 ? foo.c
236 $ hg add
236 $ hg add
237 adding foo.c
237 adding foo.c
238 $ hg status
238 $ hg status
239 A foo.c
239 A foo.c
240
240
241 - Specific files to be added can be specified::
241 - Specific files to be added can be specified::
242
242
243 $ ls
243 $ ls
244 bar.c foo.c
244 bar.c foo.c
245 $ hg status
245 $ hg status
246 ? bar.c
246 ? bar.c
247 ? foo.c
247 ? foo.c
248 $ hg add bar.c
248 $ hg add bar.c
249 $ hg status
249 $ hg status
250 A bar.c
250 A bar.c
251 ? foo.c
251 ? foo.c
252
252
253 Returns 0 if all files are successfully added.
253 Returns 0 if all files are successfully added.
254 """
254 """
255
255
256 with repo.wlock(), repo.dirstate.changing_files(repo):
256 with repo.wlock(), repo.dirstate.changing_files(repo):
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
260 return rejected and 1 or 0
260 return rejected and 1 or 0
261
261
262
262
263 @command(
263 @command(
264 b'addremove',
264 b'addremove',
265 similarityopts + subrepoopts + walkopts + dryrunopts,
265 similarityopts + subrepoopts + walkopts + dryrunopts,
266 _(b'[OPTION]... [FILE]...'),
266 _(b'[OPTION]... [FILE]...'),
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
268 inferrepo=True,
268 inferrepo=True,
269 )
269 )
270 def addremove(ui, repo, *pats, **opts):
270 def addremove(ui, repo, *pats, **opts):
271 """add all new files, delete all missing files
271 """add all new files, delete all missing files
272
272
273 Add all new files and remove all missing files from the
273 Add all new files and remove all missing files from the
274 repository.
274 repository.
275
275
276 Unless names are given, new files are ignored if they match any of
276 Unless names are given, new files are ignored if they match any of
277 the patterns in ``.hgignore``. As with add, these changes take
277 the patterns in ``.hgignore``. As with add, these changes take
278 effect at the next commit.
278 effect at the next commit.
279
279
280 Use the -s/--similarity option to detect renamed files. This
280 Use the -s/--similarity option to detect renamed files. This
281 option takes a percentage between 0 (disabled) and 100 (files must
281 option takes a percentage between 0 (disabled) and 100 (files must
282 be identical) as its parameter. With a parameter greater than 0,
282 be identical) as its parameter. With a parameter greater than 0,
283 this compares every removed file with every added file and records
283 this compares every removed file with every added file and records
284 those similar enough as renames. Detecting renamed files this way
284 those similar enough as renames. Detecting renamed files this way
285 can be expensive. After using this option, :hg:`status -C` can be
285 can be expensive. After using this option, :hg:`status -C` can be
286 used to check which files were identified as moved or renamed. If
286 used to check which files were identified as moved or renamed. If
287 not specified, -s/--similarity defaults to 100 and only renames of
287 not specified, -s/--similarity defaults to 100 and only renames of
288 identical files are detected.
288 identical files are detected.
289
289
290 .. container:: verbose
290 .. container:: verbose
291
291
292 Examples:
292 Examples:
293
293
294 - A number of files (bar.c and foo.c) are new,
294 - A number of files (bar.c and foo.c) are new,
295 while foobar.c has been removed (without using :hg:`remove`)
295 while foobar.c has been removed (without using :hg:`remove`)
296 from the repository::
296 from the repository::
297
297
298 $ ls
298 $ ls
299 bar.c foo.c
299 bar.c foo.c
300 $ hg status
300 $ hg status
301 ! foobar.c
301 ! foobar.c
302 ? bar.c
302 ? bar.c
303 ? foo.c
303 ? foo.c
304 $ hg addremove
304 $ hg addremove
305 adding bar.c
305 adding bar.c
306 adding foo.c
306 adding foo.c
307 removing foobar.c
307 removing foobar.c
308 $ hg status
308 $ hg status
309 A bar.c
309 A bar.c
310 A foo.c
310 A foo.c
311 R foobar.c
311 R foobar.c
312
312
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
314 Afterwards, it was edited slightly::
314 Afterwards, it was edited slightly::
315
315
316 $ ls
316 $ ls
317 foo.c
317 foo.c
318 $ hg status
318 $ hg status
319 ! foobar.c
319 ! foobar.c
320 ? foo.c
320 ? foo.c
321 $ hg addremove --similarity 90
321 $ hg addremove --similarity 90
322 removing foobar.c
322 removing foobar.c
323 adding foo.c
323 adding foo.c
324 recording removal of foobar.c as rename to foo.c (94% similar)
324 recording removal of foobar.c as rename to foo.c (94% similar)
325 $ hg status -C
325 $ hg status -C
326 A foo.c
326 A foo.c
327 foobar.c
327 foobar.c
328 R foobar.c
328 R foobar.c
329
329
330 Returns 0 if all files are successfully added.
330 Returns 0 if all files are successfully added.
331 """
331 """
332 opts = pycompat.byteskwargs(opts)
332 opts = pycompat.byteskwargs(opts)
333 if not opts.get(b'similarity'):
333 if not opts.get(b'similarity'):
334 opts[b'similarity'] = b'100'
334 opts[b'similarity'] = b'100'
335 with repo.wlock(), repo.dirstate.changing_files(repo):
335 with repo.wlock(), repo.dirstate.changing_files(repo):
336 matcher = scmutil.match(repo[None], pats, opts)
336 matcher = scmutil.match(repo[None], pats, opts)
337 relative = scmutil.anypats(pats, opts)
337 relative = scmutil.anypats(pats, opts)
338 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
339 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
340
340
341
341
342 @command(
342 @command(
343 b'annotate|blame',
343 b'annotate|blame',
344 [
344 [
345 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
346 (
346 (
347 b'',
347 b'',
348 b'follow',
348 b'follow',
349 None,
349 None,
350 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 _(b'follow copies/renames and list the filename (DEPRECATED)'),
351 ),
351 ),
352 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
353 (b'a', b'text', None, _(b'treat all files as text')),
353 (b'a', b'text', None, _(b'treat all files as text')),
354 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 (b'u', b'user', None, _(b'list the author (long with -v)')),
355 (b'f', b'file', None, _(b'list the filename')),
355 (b'f', b'file', None, _(b'list the filename')),
356 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 (b'd', b'date', None, _(b'list the date (short with -q)')),
357 (b'n', b'number', None, _(b'list the revision number (default)')),
357 (b'n', b'number', None, _(b'list the revision number (default)')),
358 (b'c', b'changeset', None, _(b'list the changeset')),
358 (b'c', b'changeset', None, _(b'list the changeset')),
359 (
359 (
360 b'l',
360 b'l',
361 b'line-number',
361 b'line-number',
362 None,
362 None,
363 _(b'show line number at the first appearance'),
363 _(b'show line number at the first appearance'),
364 ),
364 ),
365 (
365 (
366 b'',
366 b'',
367 b'skip',
367 b'skip',
368 [],
368 [],
369 _(b'revset to not display (EXPERIMENTAL)'),
369 _(b'revset to not display (EXPERIMENTAL)'),
370 _(b'REV'),
370 _(b'REV'),
371 ),
371 ),
372 ]
372 ]
373 + diffwsopts
373 + diffwsopts
374 + walkopts
374 + walkopts
375 + formatteropts,
375 + formatteropts,
376 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
377 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 helpcategory=command.CATEGORY_FILE_CONTENTS,
378 helpbasic=True,
378 helpbasic=True,
379 inferrepo=True,
379 inferrepo=True,
380 )
380 )
381 def annotate(ui, repo, *pats, **opts):
381 def annotate(ui, repo, *pats, **opts):
382 """show changeset information by line for each file
382 """show changeset information by line for each file
383
383
384 List changes in files, showing the revision id responsible for
384 List changes in files, showing the revision id responsible for
385 each line.
385 each line.
386
386
387 This command is useful for discovering when a change was made and
387 This command is useful for discovering when a change was made and
388 by whom.
388 by whom.
389
389
390 If you include --file, --user, or --date, the revision number is
390 If you include --file, --user, or --date, the revision number is
391 suppressed unless you also include --number.
391 suppressed unless you also include --number.
392
392
393 Without the -a/--text option, annotate will avoid processing files
393 Without the -a/--text option, annotate will avoid processing files
394 it detects as binary. With -a, annotate will annotate the file
394 it detects as binary. With -a, annotate will annotate the file
395 anyway, although the results will probably be neither useful
395 anyway, although the results will probably be neither useful
396 nor desirable.
396 nor desirable.
397
397
398 .. container:: verbose
398 .. container:: verbose
399
399
400 Template:
400 Template:
401
401
402 The following keywords are supported in addition to the common template
402 The following keywords are supported in addition to the common template
403 keywords and functions. See also :hg:`help templates`.
403 keywords and functions. See also :hg:`help templates`.
404
404
405 :lines: List of lines with annotation data.
405 :lines: List of lines with annotation data.
406 :path: String. Repository-absolute path of the specified file.
406 :path: String. Repository-absolute path of the specified file.
407
407
408 And each entry of ``{lines}`` provides the following sub-keywords in
408 And each entry of ``{lines}`` provides the following sub-keywords in
409 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
410
410
411 :line: String. Line content.
411 :line: String. Line content.
412 :lineno: Integer. Line number at that revision.
412 :lineno: Integer. Line number at that revision.
413 :path: String. Repository-absolute path of the file at that revision.
413 :path: String. Repository-absolute path of the file at that revision.
414
414
415 See :hg:`help templates.operators` for the list expansion syntax.
415 See :hg:`help templates.operators` for the list expansion syntax.
416
416
417 Returns 0 on success.
417 Returns 0 on success.
418 """
418 """
419 opts = pycompat.byteskwargs(opts)
419 opts = pycompat.byteskwargs(opts)
420 if not pats:
420 if not pats:
421 raise error.InputError(
421 raise error.InputError(
422 _(b'at least one filename or pattern is required')
422 _(b'at least one filename or pattern is required')
423 )
423 )
424
424
425 if opts.get(b'follow'):
425 if opts.get(b'follow'):
426 # --follow is deprecated and now just an alias for -f/--file
426 # --follow is deprecated and now just an alias for -f/--file
427 # to mimic the behavior of Mercurial before version 1.5
427 # to mimic the behavior of Mercurial before version 1.5
428 opts[b'file'] = True
428 opts[b'file'] = True
429
429
430 if (
430 if (
431 not opts.get(b'user')
431 not opts.get(b'user')
432 and not opts.get(b'changeset')
432 and not opts.get(b'changeset')
433 and not opts.get(b'date')
433 and not opts.get(b'date')
434 and not opts.get(b'file')
434 and not opts.get(b'file')
435 ):
435 ):
436 opts[b'number'] = True
436 opts[b'number'] = True
437
437
438 linenumber = opts.get(b'line_number') is not None
438 linenumber = opts.get(b'line_number') is not None
439 if (
439 if (
440 linenumber
440 linenumber
441 and (not opts.get(b'changeset'))
441 and (not opts.get(b'changeset'))
442 and (not opts.get(b'number'))
442 and (not opts.get(b'number'))
443 ):
443 ):
444 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
444 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
445
445
446 rev = opts.get(b'rev')
446 rev = opts.get(b'rev')
447 if rev:
447 if rev:
448 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
448 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
449 ctx = logcmdutil.revsingle(repo, rev)
449 ctx = logcmdutil.revsingle(repo, rev)
450
450
451 ui.pager(b'annotate')
451 ui.pager(b'annotate')
452 rootfm = ui.formatter(b'annotate', opts)
452 rootfm = ui.formatter(b'annotate', opts)
453 if ui.debugflag:
453 if ui.debugflag:
454 shorthex = pycompat.identity
454 shorthex = pycompat.identity
455 else:
455 else:
456
456
457 def shorthex(h):
457 def shorthex(h):
458 return h[:12]
458 return h[:12]
459
459
460 if ui.quiet:
460 if ui.quiet:
461 datefunc = dateutil.shortdate
461 datefunc = dateutil.shortdate
462 else:
462 else:
463 datefunc = dateutil.datestr
463 datefunc = dateutil.datestr
464 if ctx.rev() is None:
464 if ctx.rev() is None:
465 if opts.get(b'changeset'):
465 if opts.get(b'changeset'):
466 # omit "+" suffix which is appended to node hex
466 # omit "+" suffix which is appended to node hex
467 def formatrev(rev):
467 def formatrev(rev):
468 if rev == wdirrev:
468 if rev == wdirrev:
469 return b'%d' % ctx.p1().rev()
469 return b'%d' % ctx.p1().rev()
470 else:
470 else:
471 return b'%d' % rev
471 return b'%d' % rev
472
472
473 else:
473 else:
474
474
475 def formatrev(rev):
475 def formatrev(rev):
476 if rev == wdirrev:
476 if rev == wdirrev:
477 return b'%d+' % ctx.p1().rev()
477 return b'%d+' % ctx.p1().rev()
478 else:
478 else:
479 return b'%d ' % rev
479 return b'%d ' % rev
480
480
481 def formathex(h):
481 def formathex(h):
482 if h == repo.nodeconstants.wdirhex:
482 if h == repo.nodeconstants.wdirhex:
483 return b'%s+' % shorthex(hex(ctx.p1().node()))
483 return b'%s+' % shorthex(hex(ctx.p1().node()))
484 else:
484 else:
485 return b'%s ' % shorthex(h)
485 return b'%s ' % shorthex(h)
486
486
487 else:
487 else:
488 formatrev = b'%d'.__mod__
488 formatrev = b'%d'.__mod__
489 formathex = shorthex
489 formathex = shorthex
490
490
491 opmap = [
491 opmap = [
492 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
492 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
493 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
493 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
494 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
494 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
495 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
495 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
496 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
496 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
497 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
497 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
498 ]
498 ]
499 opnamemap = {
499 opnamemap = {
500 b'rev': b'number',
500 b'rev': b'number',
501 b'node': b'changeset',
501 b'node': b'changeset',
502 b'path': b'file',
502 b'path': b'file',
503 b'lineno': b'line_number',
503 b'lineno': b'line_number',
504 }
504 }
505
505
506 if rootfm.isplain():
506 if rootfm.isplain():
507
507
508 def makefunc(get, fmt):
508 def makefunc(get, fmt):
509 return lambda x: fmt(get(x))
509 return lambda x: fmt(get(x))
510
510
511 else:
511 else:
512
512
513 def makefunc(get, fmt):
513 def makefunc(get, fmt):
514 return get
514 return get
515
515
516 datahint = rootfm.datahint()
516 datahint = rootfm.datahint()
517 funcmap = [
517 funcmap = [
518 (makefunc(get, fmt), sep)
518 (makefunc(get, fmt), sep)
519 for fn, sep, get, fmt in opmap
519 for fn, sep, get, fmt in opmap
520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
521 ]
521 ]
522 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
522 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
523 fields = b' '.join(
523 fields = b' '.join(
524 fn
524 fn
525 for fn, sep, get, fmt in opmap
525 for fn, sep, get, fmt in opmap
526 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
526 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
527 )
527 )
528
528
529 def bad(x, y):
529 def bad(x, y):
530 raise error.InputError(b"%s: %s" % (x, y))
530 raise error.InputError(b"%s: %s" % (x, y))
531
531
532 m = scmutil.match(ctx, pats, opts, badfn=bad)
532 m = scmutil.match(ctx, pats, opts, badfn=bad)
533
533
534 follow = not opts.get(b'no_follow')
534 follow = not opts.get(b'no_follow')
535 diffopts = patch.difffeatureopts(
535 diffopts = patch.difffeatureopts(
536 ui, opts, section=b'annotate', whitespace=True
536 ui, opts, section=b'annotate', whitespace=True
537 )
537 )
538 skiprevs = opts.get(b'skip')
538 skiprevs = opts.get(b'skip')
539 if skiprevs:
539 if skiprevs:
540 skiprevs = logcmdutil.revrange(repo, skiprevs)
540 skiprevs = logcmdutil.revrange(repo, skiprevs)
541
541
542 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
542 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
543 for abs in ctx.walk(m):
543 for abs in ctx.walk(m):
544 fctx = ctx[abs]
544 fctx = ctx[abs]
545 rootfm.startitem()
545 rootfm.startitem()
546 rootfm.data(path=abs)
546 rootfm.data(path=abs)
547 if not opts.get(b'text') and fctx.isbinary():
547 if not opts.get(b'text') and fctx.isbinary():
548 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
548 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
549 continue
549 continue
550
550
551 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
551 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
552 lines = fctx.annotate(
552 lines = fctx.annotate(
553 follow=follow, skiprevs=skiprevs, diffopts=diffopts
553 follow=follow, skiprevs=skiprevs, diffopts=diffopts
554 )
554 )
555 if not lines:
555 if not lines:
556 fm.end()
556 fm.end()
557 continue
557 continue
558 formats = []
558 formats = []
559 pieces = []
559 pieces = []
560
560
561 for f, sep in funcmap:
561 for f, sep in funcmap:
562 l = [f(n) for n in lines]
562 l = [f(n) for n in lines]
563 if fm.isplain():
563 if fm.isplain():
564 sizes = [encoding.colwidth(x) for x in l]
564 sizes = [encoding.colwidth(x) for x in l]
565 ml = max(sizes)
565 ml = max(sizes)
566 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
566 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
567 else:
567 else:
568 formats.append([b'%s'] * len(l))
568 formats.append([b'%s'] * len(l))
569 pieces.append(l)
569 pieces.append(l)
570
570
571 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
571 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
572 fm.startitem()
572 fm.startitem()
573 fm.context(fctx=n.fctx)
573 fm.context(fctx=n.fctx)
574 fm.write(fields, b"".join(f), *p)
574 fm.write(fields, b"".join(f), *p)
575 if n.skip:
575 if n.skip:
576 fmt = b"* %s"
576 fmt = b"* %s"
577 else:
577 else:
578 fmt = b": %s"
578 fmt = b": %s"
579 fm.write(b'line', fmt, n.text)
579 fm.write(b'line', fmt, n.text)
580
580
581 if not lines[-1].text.endswith(b'\n'):
581 if not lines[-1].text.endswith(b'\n'):
582 fm.plain(b'\n')
582 fm.plain(b'\n')
583 fm.end()
583 fm.end()
584
584
585 rootfm.end()
585 rootfm.end()
586
586
587
587
588 @command(
588 @command(
589 b'archive',
589 b'archive',
590 [
590 [
591 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
591 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
592 (
592 (
593 b'p',
593 b'p',
594 b'prefix',
594 b'prefix',
595 b'',
595 b'',
596 _(b'directory prefix for files in archive'),
596 _(b'directory prefix for files in archive'),
597 _(b'PREFIX'),
597 _(b'PREFIX'),
598 ),
598 ),
599 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
599 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
600 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
600 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
601 ]
601 ]
602 + subrepoopts
602 + subrepoopts
603 + walkopts,
603 + walkopts,
604 _(b'[OPTION]... DEST'),
604 _(b'[OPTION]... DEST'),
605 helpcategory=command.CATEGORY_IMPORT_EXPORT,
605 helpcategory=command.CATEGORY_IMPORT_EXPORT,
606 )
606 )
607 def archive(ui, repo, dest, **opts):
607 def archive(ui, repo, dest, **opts):
608 """create an unversioned archive of a repository revision
608 """create an unversioned archive of a repository revision
609
609
610 By default, the revision used is the parent of the working
610 By default, the revision used is the parent of the working
611 directory; use -r/--rev to specify a different revision.
611 directory; use -r/--rev to specify a different revision.
612
612
613 The archive type is automatically detected based on file
613 The archive type is automatically detected based on file
614 extension (to override, use -t/--type).
614 extension (to override, use -t/--type).
615
615
616 .. container:: verbose
616 .. container:: verbose
617
617
618 Examples:
618 Examples:
619
619
620 - create a zip file containing the 1.0 release::
620 - create a zip file containing the 1.0 release::
621
621
622 hg archive -r 1.0 project-1.0.zip
622 hg archive -r 1.0 project-1.0.zip
623
623
624 - create a tarball excluding .hg files::
624 - create a tarball excluding .hg files::
625
625
626 hg archive project.tar.gz -X ".hg*"
626 hg archive project.tar.gz -X ".hg*"
627
627
628 Valid types are:
628 Valid types are:
629
629
630 :``files``: a directory full of files (default)
630 :``files``: a directory full of files (default)
631 :``tar``: tar archive, uncompressed
631 :``tar``: tar archive, uncompressed
632 :``tbz2``: tar archive, compressed using bzip2
632 :``tbz2``: tar archive, compressed using bzip2
633 :``tgz``: tar archive, compressed using gzip
633 :``tgz``: tar archive, compressed using gzip
634 :``txz``: tar archive, compressed using lzma (only in Python 3)
634 :``txz``: tar archive, compressed using lzma (only in Python 3)
635 :``uzip``: zip archive, uncompressed
635 :``uzip``: zip archive, uncompressed
636 :``zip``: zip archive, compressed using deflate
636 :``zip``: zip archive, compressed using deflate
637
637
638 The exact name of the destination archive or directory is given
638 The exact name of the destination archive or directory is given
639 using a format string; see :hg:`help export` for details.
639 using a format string; see :hg:`help export` for details.
640
640
641 Each member added to an archive file has a directory prefix
641 Each member added to an archive file has a directory prefix
642 prepended. Use -p/--prefix to specify a format string for the
642 prepended. Use -p/--prefix to specify a format string for the
643 prefix. The default is the basename of the archive, with suffixes
643 prefix. The default is the basename of the archive, with suffixes
644 removed.
644 removed.
645
645
646 Returns 0 on success.
646 Returns 0 on success.
647 """
647 """
648
648
649 rev = opts.get('rev')
649 rev = opts.get('rev')
650 if rev:
650 if rev:
651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
652 ctx = logcmdutil.revsingle(repo, rev)
652 ctx = logcmdutil.revsingle(repo, rev)
653 if not ctx:
653 if not ctx:
654 raise error.InputError(
654 raise error.InputError(
655 _(b'no working directory: please specify a revision')
655 _(b'no working directory: please specify a revision')
656 )
656 )
657 node = ctx.node()
657 node = ctx.node()
658 dest = cmdutil.makefilename(ctx, dest)
658 dest = cmdutil.makefilename(ctx, dest)
659 if os.path.realpath(dest) == repo.root:
659 if os.path.realpath(dest) == repo.root:
660 raise error.InputError(_(b'repository root cannot be destination'))
660 raise error.InputError(_(b'repository root cannot be destination'))
661
661
662 kind = opts.get('type') or archival.guesskind(dest) or b'files'
662 kind = opts.get('type') or archival.guesskind(dest) or b'files'
663 prefix = opts.get('prefix')
663 prefix = opts.get('prefix')
664
664
665 if dest == b'-':
665 if dest == b'-':
666 if kind == b'files':
666 if kind == b'files':
667 raise error.InputError(_(b'cannot archive plain files to stdout'))
667 raise error.InputError(_(b'cannot archive plain files to stdout'))
668 dest = cmdutil.makefileobj(ctx, dest)
668 dest = cmdutil.makefileobj(ctx, dest)
669 if not prefix:
669 if not prefix:
670 prefix = os.path.basename(repo.root) + b'-%h'
670 prefix = os.path.basename(repo.root) + b'-%h'
671
671
672 prefix = cmdutil.makefilename(ctx, prefix)
672 prefix = cmdutil.makefilename(ctx, prefix)
673 match = scmutil.match(ctx, [], pycompat.byteskwargs(opts))
673 match = scmutil.match(ctx, [], pycompat.byteskwargs(opts))
674 archival.archive(
674 archival.archive(
675 repo,
675 repo,
676 dest,
676 dest,
677 node,
677 node,
678 kind,
678 kind,
679 not opts.get('no_decode'),
679 not opts.get('no_decode'),
680 match,
680 match,
681 prefix,
681 prefix,
682 subrepos=opts.get('subrepos'),
682 subrepos=opts.get('subrepos'),
683 )
683 )
684
684
685
685
686 @command(
686 @command(
687 b'backout',
687 b'backout',
688 [
688 [
689 (
689 (
690 b'',
690 b'',
691 b'merge',
691 b'merge',
692 None,
692 None,
693 _(b'merge with old dirstate parent after backout'),
693 _(b'merge with old dirstate parent after backout'),
694 ),
694 ),
695 (
695 (
696 b'',
696 b'',
697 b'commit',
697 b'commit',
698 None,
698 None,
699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
700 ),
700 ),
701 (b'', b'no-commit', None, _(b'do not commit')),
701 (b'', b'no-commit', None, _(b'do not commit')),
702 (
702 (
703 b'',
703 b'',
704 b'parent',
704 b'parent',
705 b'',
705 b'',
706 _(b'parent to choose when backing out merge (DEPRECATED)'),
706 _(b'parent to choose when backing out merge (DEPRECATED)'),
707 _(b'REV'),
707 _(b'REV'),
708 ),
708 ),
709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
711 ]
711 ]
712 + mergetoolopts
712 + mergetoolopts
713 + walkopts
713 + walkopts
714 + commitopts
714 + commitopts
715 + commitopts2,
715 + commitopts2,
716 _(b'[OPTION]... [-r] REV'),
716 _(b'[OPTION]... [-r] REV'),
717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
718 )
718 )
719 def backout(ui, repo, node=None, rev=None, **opts):
719 def backout(ui, repo, node=None, rev=None, **opts):
720 """reverse effect of earlier changeset
720 """reverse effect of earlier changeset
721
721
722 Prepare a new changeset with the effect of REV undone in the
722 Prepare a new changeset with the effect of REV undone in the
723 current working directory. If no conflicts were encountered,
723 current working directory. If no conflicts were encountered,
724 it will be committed immediately.
724 it will be committed immediately.
725
725
726 If REV is the parent of the working directory, then this new changeset
726 If REV is the parent of the working directory, then this new changeset
727 is committed automatically (unless --no-commit is specified).
727 is committed automatically (unless --no-commit is specified).
728
728
729 .. note::
729 .. note::
730
730
731 :hg:`backout` cannot be used to fix either an unwanted or
731 :hg:`backout` cannot be used to fix either an unwanted or
732 incorrect merge.
732 incorrect merge.
733
733
734 .. container:: verbose
734 .. container:: verbose
735
735
736 Examples:
736 Examples:
737
737
738 - Reverse the effect of the parent of the working directory.
738 - Reverse the effect of the parent of the working directory.
739 This backout will be committed immediately::
739 This backout will be committed immediately::
740
740
741 hg backout -r .
741 hg backout -r .
742
742
743 - Reverse the effect of previous bad revision 23::
743 - Reverse the effect of previous bad revision 23::
744
744
745 hg backout -r 23
745 hg backout -r 23
746
746
747 - Reverse the effect of previous bad revision 23 and
747 - Reverse the effect of previous bad revision 23 and
748 leave changes uncommitted::
748 leave changes uncommitted::
749
749
750 hg backout -r 23 --no-commit
750 hg backout -r 23 --no-commit
751 hg commit -m "Backout revision 23"
751 hg commit -m "Backout revision 23"
752
752
753 By default, the pending changeset will have one parent,
753 By default, the pending changeset will have one parent,
754 maintaining a linear history. With --merge, the pending
754 maintaining a linear history. With --merge, the pending
755 changeset will instead have two parents: the old parent of the
755 changeset will instead have two parents: the old parent of the
756 working directory and a new child of REV that simply undoes REV.
756 working directory and a new child of REV that simply undoes REV.
757
757
758 Before version 1.7, the behavior without --merge was equivalent
758 Before version 1.7, the behavior without --merge was equivalent
759 to specifying --merge followed by :hg:`update --clean .` to
759 to specifying --merge followed by :hg:`update --clean .` to
760 cancel the merge and leave the child of REV as a head to be
760 cancel the merge and leave the child of REV as a head to be
761 merged separately.
761 merged separately.
762
762
763 See :hg:`help dates` for a list of formats valid for -d/--date.
763 See :hg:`help dates` for a list of formats valid for -d/--date.
764
764
765 See :hg:`help revert` for a way to restore files to the state
765 See :hg:`help revert` for a way to restore files to the state
766 of another revision.
766 of another revision.
767
767
768 Returns 0 on success, 1 if nothing to backout or there are unresolved
768 Returns 0 on success, 1 if nothing to backout or there are unresolved
769 files.
769 files.
770 """
770 """
771 with repo.wlock(), repo.lock():
771 with repo.wlock(), repo.lock():
772 return _dobackout(ui, repo, node, rev, **opts)
772 return _dobackout(ui, repo, node, rev, **opts)
773
773
774
774
775 def _dobackout(ui, repo, node=None, rev=None, **opts):
775 def _dobackout(ui, repo, node=None, rev=None, **opts):
776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
777
777
778 if rev and node:
778 if rev and node:
779 raise error.InputError(_(b"please specify just one revision"))
779 raise error.InputError(_(b"please specify just one revision"))
780
780
781 if not rev:
781 if not rev:
782 rev = node
782 rev = node
783
783
784 if not rev:
784 if not rev:
785 raise error.InputError(_(b"please specify a revision to backout"))
785 raise error.InputError(_(b"please specify a revision to backout"))
786
786
787 date = opts.get('date')
787 date = opts.get('date')
788 if date:
788 if date:
789 opts['date'] = dateutil.parsedate(date)
789 opts['date'] = dateutil.parsedate(date)
790
790
791 cmdutil.checkunfinished(repo)
791 cmdutil.checkunfinished(repo)
792 cmdutil.bailifchanged(repo)
792 cmdutil.bailifchanged(repo)
793 ctx = logcmdutil.revsingle(repo, rev)
793 ctx = logcmdutil.revsingle(repo, rev)
794 node = ctx.node()
794 node = ctx.node()
795
795
796 op1, op2 = repo.dirstate.parents()
796 op1, op2 = repo.dirstate.parents()
797 if not repo.changelog.isancestor(node, op1):
797 if not repo.changelog.isancestor(node, op1):
798 raise error.InputError(
798 raise error.InputError(
799 _(b'cannot backout change that is not an ancestor')
799 _(b'cannot backout change that is not an ancestor')
800 )
800 )
801
801
802 p1, p2 = repo.changelog.parents(node)
802 p1, p2 = repo.changelog.parents(node)
803 if p1 == repo.nullid:
803 if p1 == repo.nullid:
804 raise error.InputError(_(b'cannot backout a change with no parents'))
804 raise error.InputError(_(b'cannot backout a change with no parents'))
805 if p2 != repo.nullid:
805 if p2 != repo.nullid:
806 if not opts.get('parent'):
806 if not opts.get('parent'):
807 raise error.InputError(_(b'cannot backout a merge changeset'))
807 raise error.InputError(_(b'cannot backout a merge changeset'))
808 p = repo.lookup(opts['parent'])
808 p = repo.lookup(opts['parent'])
809 if p not in (p1, p2):
809 if p not in (p1, p2):
810 raise error.InputError(
810 raise error.InputError(
811 _(b'%s is not a parent of %s') % (short(p), short(node))
811 _(b'%s is not a parent of %s') % (short(p), short(node))
812 )
812 )
813 parent = p
813 parent = p
814 else:
814 else:
815 if opts.get('parent'):
815 if opts.get('parent'):
816 raise error.InputError(
816 raise error.InputError(
817 _(b'cannot use --parent on non-merge changeset')
817 _(b'cannot use --parent on non-merge changeset')
818 )
818 )
819 parent = p1
819 parent = p1
820
820
821 # the backout should appear on the same branch
821 # the backout should appear on the same branch
822 branch = repo.dirstate.branch()
822 branch = repo.dirstate.branch()
823 bheads = repo.branchheads(branch)
823 bheads = repo.branchheads(branch)
824 rctx = scmutil.revsingle(repo, hex(parent))
824 rctx = scmutil.revsingle(repo, hex(parent))
825 if not opts.get('merge') and op1 != node:
825 if not opts.get('merge') and op1 != node:
826 with repo.transaction(b"backout"):
826 with repo.transaction(b"backout"):
827 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
827 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
828 with ui.configoverride(overrides, b'backout'):
828 with ui.configoverride(overrides, b'backout'):
829 stats = mergemod.back_out(ctx, parent=repo[parent])
829 stats = mergemod.back_out(ctx, parent=repo[parent])
830 repo.setparents(op1, op2)
830 repo.setparents(op1, op2)
831 hg._showstats(repo, stats)
831 hg._showstats(repo, stats)
832 if stats.unresolvedcount:
832 if stats.unresolvedcount:
833 repo.ui.status(
833 repo.ui.status(
834 _(b"use 'hg resolve' to retry unresolved file merges\n")
834 _(b"use 'hg resolve' to retry unresolved file merges\n")
835 )
835 )
836 return 1
836 return 1
837 else:
837 else:
838 hg.clean(repo, node, show_stats=False)
838 hg.clean(repo, node, show_stats=False)
839 repo.dirstate.setbranch(branch, repo.currenttransaction())
839 repo.dirstate.setbranch(branch, repo.currenttransaction())
840 cmdutil.revert(ui, repo, rctx)
840 cmdutil.revert(ui, repo, rctx)
841
841
842 if opts.get('no_commit'):
842 if opts.get('no_commit'):
843 msg = _(b"changeset %s backed out, don't forget to commit.\n")
843 msg = _(b"changeset %s backed out, don't forget to commit.\n")
844 ui.status(msg % short(node))
844 ui.status(msg % short(node))
845 return 0
845 return 0
846
846
847 def commitfunc(ui, repo, message, match, opts):
847 def commitfunc(ui, repo, message, match, opts):
848 editform = b'backout'
848 editform = b'backout'
849 e = cmdutil.getcommiteditor(
849 e = cmdutil.getcommiteditor(
850 editform=editform, **pycompat.strkwargs(opts)
850 editform=editform, **pycompat.strkwargs(opts)
851 )
851 )
852 if not message:
852 if not message:
853 # we don't translate commit messages
853 # we don't translate commit messages
854 message = b"Backed out changeset %s" % short(node)
854 message = b"Backed out changeset %s" % short(node)
855 e = cmdutil.getcommiteditor(edit=True, editform=editform)
855 e = cmdutil.getcommiteditor(edit=True, editform=editform)
856 return repo.commit(
856 return repo.commit(
857 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
857 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
858 )
858 )
859
859
860 # save to detect changes
860 # save to detect changes
861 tip = repo.changelog.tip()
861 tip = repo.changelog.tip()
862
862
863 newnode = cmdutil.commit(
863 newnode = cmdutil.commit(
864 ui, repo, commitfunc, [], pycompat.byteskwargs(opts)
864 ui, repo, commitfunc, [], pycompat.byteskwargs(opts)
865 )
865 )
866 if not newnode:
866 if not newnode:
867 ui.status(_(b"nothing changed\n"))
867 ui.status(_(b"nothing changed\n"))
868 return 1
868 return 1
869 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
869 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
870
870
871 def nice(node):
871 def nice(node):
872 return b'%d:%s' % (repo.changelog.rev(node), short(node))
872 return b'%d:%s' % (repo.changelog.rev(node), short(node))
873
873
874 ui.status(
874 ui.status(
875 _(b'changeset %s backs out changeset %s\n')
875 _(b'changeset %s backs out changeset %s\n')
876 % (nice(newnode), nice(node))
876 % (nice(newnode), nice(node))
877 )
877 )
878 if opts.get('merge') and op1 != node:
878 if opts.get('merge') and op1 != node:
879 hg.clean(repo, op1, show_stats=False)
879 hg.clean(repo, op1, show_stats=False)
880 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
880 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
881 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
881 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
882 with ui.configoverride(overrides, b'backout'):
882 with ui.configoverride(overrides, b'backout'):
883 return hg.merge(repo[b'tip'])
883 return hg.merge(repo[b'tip'])
884 return 0
884 return 0
885
885
886
886
887 @command(
887 @command(
888 b'bisect',
888 b'bisect',
889 [
889 [
890 (b'r', b'reset', False, _(b'reset bisect state')),
890 (b'r', b'reset', False, _(b'reset bisect state')),
891 (b'g', b'good', False, _(b'mark changeset good')),
891 (b'g', b'good', False, _(b'mark changeset good')),
892 (b'b', b'bad', False, _(b'mark changeset bad')),
892 (b'b', b'bad', False, _(b'mark changeset bad')),
893 (b's', b'skip', False, _(b'skip testing changeset')),
893 (b's', b'skip', False, _(b'skip testing changeset')),
894 (b'e', b'extend', False, _(b'extend the bisect range')),
894 (b'e', b'extend', False, _(b'extend the bisect range')),
895 (
895 (
896 b'c',
896 b'c',
897 b'command',
897 b'command',
898 b'',
898 b'',
899 _(b'use command to check changeset state'),
899 _(b'use command to check changeset state'),
900 _(b'CMD'),
900 _(b'CMD'),
901 ),
901 ),
902 (b'U', b'noupdate', False, _(b'do not update to target')),
902 (b'U', b'noupdate', False, _(b'do not update to target')),
903 ],
903 ],
904 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
904 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
905 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
905 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
906 )
906 )
907 def bisect(
907 def bisect(
908 ui,
908 ui,
909 repo,
909 repo,
910 positional_1=None,
910 positional_1=None,
911 positional_2=None,
911 positional_2=None,
912 command=None,
912 command=None,
913 reset=None,
913 reset=None,
914 good=None,
914 good=None,
915 bad=None,
915 bad=None,
916 skip=None,
916 skip=None,
917 extend=None,
917 extend=None,
918 noupdate=None,
918 noupdate=None,
919 ):
919 ):
920 """subdivision search of changesets
920 """subdivision search of changesets
921
921
922 This command helps to find changesets which introduce problems. To
922 This command helps to find changesets which introduce problems. To
923 use, mark the earliest changeset you know exhibits the problem as
923 use, mark the earliest changeset you know exhibits the problem as
924 bad, then mark the latest changeset which is free from the problem
924 bad, then mark the latest changeset which is free from the problem
925 as good. Bisect will update your working directory to a revision
925 as good. Bisect will update your working directory to a revision
926 for testing (unless the -U/--noupdate option is specified). Once
926 for testing (unless the -U/--noupdate option is specified). Once
927 you have performed tests, mark the working directory as good or
927 you have performed tests, mark the working directory as good or
928 bad, and bisect will either update to another candidate changeset
928 bad, and bisect will either update to another candidate changeset
929 or announce that it has found the bad revision.
929 or announce that it has found the bad revision.
930
930
931 As a shortcut, you can also use the revision argument to mark a
931 As a shortcut, you can also use the revision argument to mark a
932 revision as good or bad without checking it out first.
932 revision as good or bad without checking it out first.
933
933
934 If you supply a command, it will be used for automatic bisection.
934 If you supply a command, it will be used for automatic bisection.
935 The environment variable HG_NODE will contain the ID of the
935 The environment variable HG_NODE will contain the ID of the
936 changeset being tested. The exit status of the command will be
936 changeset being tested. The exit status of the command will be
937 used to mark revisions as good or bad: status 0 means good, 125
937 used to mark revisions as good or bad: status 0 means good, 125
938 means to skip the revision, 127 (command not found) will abort the
938 means to skip the revision, 127 (command not found) will abort the
939 bisection, and any other non-zero exit status means the revision
939 bisection, and any other non-zero exit status means the revision
940 is bad.
940 is bad.
941
941
942 .. container:: verbose
942 .. container:: verbose
943
943
944 Some examples:
944 Some examples:
945
945
946 - start a bisection with known bad revision 34, and good revision 12::
946 - start a bisection with known bad revision 34, and good revision 12::
947
947
948 hg bisect --bad 34
948 hg bisect --bad 34
949 hg bisect --good 12
949 hg bisect --good 12
950
950
951 - advance the current bisection by marking current revision as good or
951 - advance the current bisection by marking current revision as good or
952 bad::
952 bad::
953
953
954 hg bisect --good
954 hg bisect --good
955 hg bisect --bad
955 hg bisect --bad
956
956
957 - mark the current revision, or a known revision, to be skipped (e.g. if
957 - mark the current revision, or a known revision, to be skipped (e.g. if
958 that revision is not usable because of another issue)::
958 that revision is not usable because of another issue)::
959
959
960 hg bisect --skip
960 hg bisect --skip
961 hg bisect --skip 23
961 hg bisect --skip 23
962
962
963 - skip all revisions that do not touch directories ``foo`` or ``bar``::
963 - skip all revisions that do not touch directories ``foo`` or ``bar``::
964
964
965 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
965 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
966
966
967 - forget the current bisection::
967 - forget the current bisection::
968
968
969 hg bisect --reset
969 hg bisect --reset
970
970
971 - use 'make && make tests' to automatically find the first broken
971 - use 'make && make tests' to automatically find the first broken
972 revision::
972 revision::
973
973
974 hg bisect --reset
974 hg bisect --reset
975 hg bisect --bad 34
975 hg bisect --bad 34
976 hg bisect --good 12
976 hg bisect --good 12
977 hg bisect --command "make && make tests"
977 hg bisect --command "make && make tests"
978
978
979 - see all changesets whose states are already known in the current
979 - see all changesets whose states are already known in the current
980 bisection::
980 bisection::
981
981
982 hg log -r "bisect(pruned)"
982 hg log -r "bisect(pruned)"
983
983
984 - see the changeset currently being bisected (especially useful
984 - see the changeset currently being bisected (especially useful
985 if running with -U/--noupdate)::
985 if running with -U/--noupdate)::
986
986
987 hg log -r "bisect(current)"
987 hg log -r "bisect(current)"
988
988
989 - see all changesets that took part in the current bisection::
989 - see all changesets that took part in the current bisection::
990
990
991 hg log -r "bisect(range)"
991 hg log -r "bisect(range)"
992
992
993 - you can even get a nice graph::
993 - you can even get a nice graph::
994
994
995 hg log --graph -r "bisect(range)"
995 hg log --graph -r "bisect(range)"
996
996
997 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
997 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
998
998
999 Returns 0 on success.
999 Returns 0 on success.
1000 """
1000 """
1001 rev = []
1001 rev = []
1002 # backward compatibility
1002 # backward compatibility
1003 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1003 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1004 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1004 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1005 cmd = positional_1
1005 cmd = positional_1
1006 rev.append(positional_2)
1006 rev.append(positional_2)
1007 if cmd == b"good":
1007 if cmd == b"good":
1008 good = True
1008 good = True
1009 elif cmd == b"bad":
1009 elif cmd == b"bad":
1010 bad = True
1010 bad = True
1011 else:
1011 else:
1012 reset = True
1012 reset = True
1013 elif positional_2:
1013 elif positional_2:
1014 raise error.InputError(_(b'incompatible arguments'))
1014 raise error.InputError(_(b'incompatible arguments'))
1015 elif positional_1 is not None:
1015 elif positional_1 is not None:
1016 rev.append(positional_1)
1016 rev.append(positional_1)
1017
1017
1018 incompatibles = {
1018 incompatibles = {
1019 b'--bad': bad,
1019 b'--bad': bad,
1020 b'--command': bool(command),
1020 b'--command': bool(command),
1021 b'--extend': extend,
1021 b'--extend': extend,
1022 b'--good': good,
1022 b'--good': good,
1023 b'--reset': reset,
1023 b'--reset': reset,
1024 b'--skip': skip,
1024 b'--skip': skip,
1025 }
1025 }
1026
1026
1027 enabled = [x for x in incompatibles if incompatibles[x]]
1027 enabled = [x for x in incompatibles if incompatibles[x]]
1028
1028
1029 if len(enabled) > 1:
1029 if len(enabled) > 1:
1030 raise error.InputError(
1030 raise error.InputError(
1031 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1031 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1032 )
1032 )
1033
1033
1034 if reset:
1034 if reset:
1035 hbisect.resetstate(repo)
1035 hbisect.resetstate(repo)
1036 return
1036 return
1037
1037
1038 state = hbisect.load_state(repo)
1038 state = hbisect.load_state(repo)
1039
1039
1040 if rev:
1040 if rev:
1041 revs = logcmdutil.revrange(repo, rev)
1041 revs = logcmdutil.revrange(repo, rev)
1042 goodnodes = state[b'good']
1042 goodnodes = state[b'good']
1043 badnodes = state[b'bad']
1043 badnodes = state[b'bad']
1044 if goodnodes and badnodes:
1044 if goodnodes and badnodes:
1045 candidates = repo.revs(b'(%ln)::(%ln)', goodnodes, badnodes)
1045 candidates = repo.revs(b'(%ln)::(%ln)', goodnodes, badnodes)
1046 candidates += repo.revs(b'(%ln)::(%ln)', badnodes, goodnodes)
1046 candidates += repo.revs(b'(%ln)::(%ln)', badnodes, goodnodes)
1047 revs = candidates & revs
1047 revs = candidates & revs
1048 nodes = [repo.changelog.node(i) for i in revs]
1048 nodes = [repo.changelog.node(i) for i in revs]
1049 else:
1049 else:
1050 nodes = [repo.lookup(b'.')]
1050 nodes = [repo.lookup(b'.')]
1051
1051
1052 # update state
1052 # update state
1053 if good or bad or skip:
1053 if good or bad or skip:
1054 if good:
1054 if good:
1055 state[b'good'] += nodes
1055 state[b'good'] += nodes
1056 elif bad:
1056 elif bad:
1057 state[b'bad'] += nodes
1057 state[b'bad'] += nodes
1058 elif skip:
1058 elif skip:
1059 state[b'skip'] += nodes
1059 state[b'skip'] += nodes
1060 hbisect.save_state(repo, state)
1060 hbisect.save_state(repo, state)
1061 if not (state[b'good'] and state[b'bad']):
1061 if not (state[b'good'] and state[b'bad']):
1062 return
1062 return
1063
1063
1064 def mayupdate(repo, node, show_stats=True):
1064 def mayupdate(repo, node, show_stats=True):
1065 """common used update sequence"""
1065 """common used update sequence"""
1066 if noupdate:
1066 if noupdate:
1067 return
1067 return
1068 cmdutil.checkunfinished(repo)
1068 cmdutil.checkunfinished(repo)
1069 cmdutil.bailifchanged(repo)
1069 cmdutil.bailifchanged(repo)
1070 return hg.clean(repo, node, show_stats=show_stats)
1070 return hg.clean(repo, node, show_stats=show_stats)
1071
1071
1072 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1072 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1073
1073
1074 if command:
1074 if command:
1075 changesets = 1
1075 changesets = 1
1076 if noupdate:
1076 if noupdate:
1077 try:
1077 try:
1078 node = state[b'current'][0]
1078 node = state[b'current'][0]
1079 except LookupError:
1079 except LookupError:
1080 raise error.StateError(
1080 raise error.StateError(
1081 _(
1081 _(
1082 b'current bisect revision is unknown - '
1082 b'current bisect revision is unknown - '
1083 b'start a new bisect to fix'
1083 b'start a new bisect to fix'
1084 )
1084 )
1085 )
1085 )
1086 else:
1086 else:
1087 node, p2 = repo.dirstate.parents()
1087 node, p2 = repo.dirstate.parents()
1088 if p2 != repo.nullid:
1088 if p2 != repo.nullid:
1089 raise error.StateError(_(b'current bisect revision is a merge'))
1089 raise error.StateError(_(b'current bisect revision is a merge'))
1090 if rev:
1090 if rev:
1091 if not nodes:
1091 if not nodes:
1092 raise error.InputError(_(b'empty revision set'))
1092 raise error.InputError(_(b'empty revision set'))
1093 node = repo[nodes[-1]].node()
1093 node = repo[nodes[-1]].node()
1094 with hbisect.restore_state(repo, state, node):
1094 with hbisect.restore_state(repo, state, node):
1095 while changesets:
1095 while changesets:
1096 # update state
1096 # update state
1097 state[b'current'] = [node]
1097 state[b'current'] = [node]
1098 hbisect.save_state(repo, state)
1098 hbisect.save_state(repo, state)
1099 status = ui.system(
1099 status = ui.system(
1100 command,
1100 command,
1101 environ={b'HG_NODE': hex(node)},
1101 environ={b'HG_NODE': hex(node)},
1102 blockedtag=b'bisect_check',
1102 blockedtag=b'bisect_check',
1103 )
1103 )
1104 if status == 125:
1104 if status == 125:
1105 transition = b"skip"
1105 transition = b"skip"
1106 elif status == 0:
1106 elif status == 0:
1107 transition = b"good"
1107 transition = b"good"
1108 # status < 0 means process was killed
1108 # status < 0 means process was killed
1109 elif status == 127:
1109 elif status == 127:
1110 raise error.Abort(_(b"failed to execute %s") % command)
1110 raise error.Abort(_(b"failed to execute %s") % command)
1111 elif status < 0:
1111 elif status < 0:
1112 raise error.Abort(_(b"%s killed") % command)
1112 raise error.Abort(_(b"%s killed") % command)
1113 else:
1113 else:
1114 transition = b"bad"
1114 transition = b"bad"
1115 state[transition].append(node)
1115 state[transition].append(node)
1116 ctx = repo[node]
1116 ctx = repo[node]
1117 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1117 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1118 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1118 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1119 hbisect.checkstate(state)
1119 hbisect.checkstate(state)
1120 # bisect
1120 # bisect
1121 nodes, changesets, bgood = hbisect.bisect(repo, state)
1121 nodes, changesets, bgood = hbisect.bisect(repo, state)
1122 # update to next check
1122 # update to next check
1123 node = nodes[0]
1123 node = nodes[0]
1124 mayupdate(repo, node, show_stats=False)
1124 mayupdate(repo, node, show_stats=False)
1125 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1125 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1126 return
1126 return
1127
1127
1128 hbisect.checkstate(state)
1128 hbisect.checkstate(state)
1129
1129
1130 # actually bisect
1130 # actually bisect
1131 nodes, changesets, good = hbisect.bisect(repo, state)
1131 nodes, changesets, good = hbisect.bisect(repo, state)
1132 if extend:
1132 if extend:
1133 if not changesets:
1133 if not changesets:
1134 extendctx = hbisect.extendrange(repo, state, nodes, good)
1134 extendctx = hbisect.extendrange(repo, state, nodes, good)
1135 if extendctx is not None:
1135 if extendctx is not None:
1136 ui.write(
1136 ui.write(
1137 _(b"Extending search to changeset %s\n")
1137 _(b"Extending search to changeset %s\n")
1138 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1138 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1139 )
1139 )
1140 state[b'current'] = [extendctx.node()]
1140 state[b'current'] = [extendctx.node()]
1141 hbisect.save_state(repo, state)
1141 hbisect.save_state(repo, state)
1142 return mayupdate(repo, extendctx.node())
1142 return mayupdate(repo, extendctx.node())
1143 raise error.StateError(_(b"nothing to extend"))
1143 raise error.StateError(_(b"nothing to extend"))
1144
1144
1145 if changesets == 0:
1145 if changesets == 0:
1146 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1146 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1147 else:
1147 else:
1148 assert len(nodes) == 1 # only a single node can be tested next
1148 assert len(nodes) == 1 # only a single node can be tested next
1149 node = nodes[0]
1149 node = nodes[0]
1150 # compute the approximate number of remaining tests
1150 # compute the approximate number of remaining tests
1151 tests, size = 0, 2
1151 tests, size = 0, 2
1152 while size <= changesets:
1152 while size <= changesets:
1153 tests, size = tests + 1, size * 2
1153 tests, size = tests + 1, size * 2
1154 rev = repo.changelog.rev(node)
1154 rev = repo.changelog.rev(node)
1155 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1155 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1156 ui.write(
1156 ui.write(
1157 _(
1157 _(
1158 b"Testing changeset %s "
1158 b"Testing changeset %s "
1159 b"(%d changesets remaining, ~%d tests)\n"
1159 b"(%d changesets remaining, ~%d tests)\n"
1160 )
1160 )
1161 % (summary, changesets, tests)
1161 % (summary, changesets, tests)
1162 )
1162 )
1163 state[b'current'] = [node]
1163 state[b'current'] = [node]
1164 hbisect.save_state(repo, state)
1164 hbisect.save_state(repo, state)
1165 return mayupdate(repo, node)
1165 return mayupdate(repo, node)
1166
1166
1167
1167
1168 @command(
1168 @command(
1169 b'bookmarks|bookmark',
1169 b'bookmarks|bookmark',
1170 [
1170 [
1171 (b'f', b'force', False, _(b'force')),
1171 (b'f', b'force', False, _(b'force')),
1172 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1172 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1173 (b'd', b'delete', False, _(b'delete a given bookmark')),
1173 (b'd', b'delete', False, _(b'delete a given bookmark')),
1174 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1174 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1175 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1175 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1176 (b'l', b'list', False, _(b'list existing bookmarks')),
1176 (b'l', b'list', False, _(b'list existing bookmarks')),
1177 ]
1177 ]
1178 + formatteropts,
1178 + formatteropts,
1179 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1179 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1180 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1180 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1181 )
1181 )
1182 def bookmark(ui, repo, *names, **opts):
1182 def bookmark(ui, repo, *names, **opts):
1183 """create a new bookmark or list existing bookmarks
1183 """create a new bookmark or list existing bookmarks
1184
1184
1185 Bookmarks are labels on changesets to help track lines of development.
1185 Bookmarks are labels on changesets to help track lines of development.
1186 Bookmarks are unversioned and can be moved, renamed and deleted.
1186 Bookmarks are unversioned and can be moved, renamed and deleted.
1187 Deleting or moving a bookmark has no effect on the associated changesets.
1187 Deleting or moving a bookmark has no effect on the associated changesets.
1188
1188
1189 Creating or updating to a bookmark causes it to be marked as 'active'.
1189 Creating or updating to a bookmark causes it to be marked as 'active'.
1190 The active bookmark is indicated with a '*'.
1190 The active bookmark is indicated with a '*'.
1191 When a commit is made, the active bookmark will advance to the new commit.
1191 When a commit is made, the active bookmark will advance to the new commit.
1192 A plain :hg:`update` will also advance an active bookmark, if possible.
1192 A plain :hg:`update` will also advance an active bookmark, if possible.
1193 Updating away from a bookmark will cause it to be deactivated.
1193 Updating away from a bookmark will cause it to be deactivated.
1194
1194
1195 Bookmarks can be pushed and pulled between repositories (see
1195 Bookmarks can be pushed and pulled between repositories (see
1196 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1196 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1197 diverged, a new 'divergent bookmark' of the form 'name@path' will
1197 diverged, a new 'divergent bookmark' of the form 'name@path' will
1198 be created. Using :hg:`merge` will resolve the divergence.
1198 be created. Using :hg:`merge` will resolve the divergence.
1199
1199
1200 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1200 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1201 the active bookmark's name.
1201 the active bookmark's name.
1202
1202
1203 A bookmark named '@' has the special property that :hg:`clone` will
1203 A bookmark named '@' has the special property that :hg:`clone` will
1204 check it out by default if it exists.
1204 check it out by default if it exists.
1205
1205
1206 .. container:: verbose
1206 .. container:: verbose
1207
1207
1208 Template:
1208 Template:
1209
1209
1210 The following keywords are supported in addition to the common template
1210 The following keywords are supported in addition to the common template
1211 keywords and functions such as ``{bookmark}``. See also
1211 keywords and functions such as ``{bookmark}``. See also
1212 :hg:`help templates`.
1212 :hg:`help templates`.
1213
1213
1214 :active: Boolean. True if the bookmark is active.
1214 :active: Boolean. True if the bookmark is active.
1215
1215
1216 Examples:
1216 Examples:
1217
1217
1218 - create an active bookmark for a new line of development::
1218 - create an active bookmark for a new line of development::
1219
1219
1220 hg book new-feature
1220 hg book new-feature
1221
1221
1222 - create an inactive bookmark as a place marker::
1222 - create an inactive bookmark as a place marker::
1223
1223
1224 hg book -i reviewed
1224 hg book -i reviewed
1225
1225
1226 - create an inactive bookmark on another changeset::
1226 - create an inactive bookmark on another changeset::
1227
1227
1228 hg book -r .^ tested
1228 hg book -r .^ tested
1229
1229
1230 - rename bookmark turkey to dinner::
1230 - rename bookmark turkey to dinner::
1231
1231
1232 hg book -m turkey dinner
1232 hg book -m turkey dinner
1233
1233
1234 - move the '@' bookmark from another branch::
1234 - move the '@' bookmark from another branch::
1235
1235
1236 hg book -f @
1236 hg book -f @
1237
1237
1238 - print only the active bookmark name::
1238 - print only the active bookmark name::
1239
1239
1240 hg book -ql .
1240 hg book -ql .
1241 """
1241 """
1242 force = opts.get('force')
1242 force = opts.get('force')
1243 rev = opts.get('rev')
1243 rev = opts.get('rev')
1244 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1244 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1245
1245
1246 action = cmdutil.check_at_most_one_arg(opts, 'delete', 'rename', 'list')
1246 action = cmdutil.check_at_most_one_arg(opts, 'delete', 'rename', 'list')
1247 if action:
1247 if action:
1248 cmdutil.check_incompatible_arguments(opts, action, ['rev'])
1248 cmdutil.check_incompatible_arguments(opts, action, ['rev'])
1249 elif names or rev:
1249 elif names or rev:
1250 action = 'add'
1250 action = 'add'
1251 elif inactive:
1251 elif inactive:
1252 action = 'inactive' # meaning deactivate
1252 action = 'inactive' # meaning deactivate
1253 else:
1253 else:
1254 action = 'list'
1254 action = 'list'
1255
1255
1256 cmdutil.check_incompatible_arguments(opts, 'inactive', ['delete', 'list'])
1256 cmdutil.check_incompatible_arguments(opts, 'inactive', ['delete', 'list'])
1257 if not names and action in {'add', 'delete'}:
1257 if not names and action in {'add', 'delete'}:
1258 raise error.InputError(_(b"bookmark name required"))
1258 raise error.InputError(_(b"bookmark name required"))
1259
1259
1260 if action in {'add', 'delete', 'rename', 'inactive'}:
1260 if action in {'add', 'delete', 'rename', 'inactive'}:
1261 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1261 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1262 if action == 'delete':
1262 if action == 'delete':
1263 names = pycompat.maplist(repo._bookmarks.expandname, names)
1263 names = pycompat.maplist(repo._bookmarks.expandname, names)
1264 bookmarks.delete(repo, tr, names)
1264 bookmarks.delete(repo, tr, names)
1265 elif action == 'rename':
1265 elif action == 'rename':
1266 if not names:
1266 if not names:
1267 raise error.InputError(_(b"new bookmark name required"))
1267 raise error.InputError(_(b"new bookmark name required"))
1268 elif len(names) > 1:
1268 elif len(names) > 1:
1269 raise error.InputError(
1269 raise error.InputError(
1270 _(b"only one new bookmark name allowed")
1270 _(b"only one new bookmark name allowed")
1271 )
1271 )
1272 oldname = repo._bookmarks.expandname(opts['rename'])
1272 oldname = repo._bookmarks.expandname(opts['rename'])
1273 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1273 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1274 elif action == 'add':
1274 elif action == 'add':
1275 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1275 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1276 elif action == 'inactive':
1276 elif action == 'inactive':
1277 if len(repo._bookmarks) == 0:
1277 if len(repo._bookmarks) == 0:
1278 ui.status(_(b"no bookmarks set\n"))
1278 ui.status(_(b"no bookmarks set\n"))
1279 elif not repo._activebookmark:
1279 elif not repo._activebookmark:
1280 ui.status(_(b"no active bookmark\n"))
1280 ui.status(_(b"no active bookmark\n"))
1281 else:
1281 else:
1282 bookmarks.deactivate(repo)
1282 bookmarks.deactivate(repo)
1283 elif action == 'list':
1283 elif action == 'list':
1284 names = pycompat.maplist(repo._bookmarks.expandname, names)
1284 names = pycompat.maplist(repo._bookmarks.expandname, names)
1285 with ui.formatter(b'bookmarks', pycompat.byteskwargs(opts)) as fm:
1285 with ui.formatter(b'bookmarks', pycompat.byteskwargs(opts)) as fm:
1286 bookmarks.printbookmarks(ui, repo, fm, names)
1286 bookmarks.printbookmarks(ui, repo, fm, names)
1287 else:
1287 else:
1288 raise error.ProgrammingError(
1288 raise error.ProgrammingError(
1289 b'invalid action: %s' % pycompat.sysbytes(action)
1289 b'invalid action: %s' % pycompat.sysbytes(action)
1290 )
1290 )
1291
1291
1292
1292
1293 @command(
1293 @command(
1294 b'branch',
1294 b'branch',
1295 [
1295 [
1296 (
1296 (
1297 b'f',
1297 b'f',
1298 b'force',
1298 b'force',
1299 None,
1299 None,
1300 _(b'set branch name even if it shadows an existing branch'),
1300 _(b'set branch name even if it shadows an existing branch'),
1301 ),
1301 ),
1302 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1302 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1303 (
1303 (
1304 b'r',
1304 b'r',
1305 b'rev',
1305 b'rev',
1306 [],
1306 [],
1307 _(b'change branches of the given revs (EXPERIMENTAL)'),
1307 _(b'change branches of the given revs (EXPERIMENTAL)'),
1308 ),
1308 ),
1309 ],
1309 ],
1310 _(b'[-fC] [NAME]'),
1310 _(b'[-fC] [NAME]'),
1311 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1311 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1312 )
1312 )
1313 def branch(ui, repo, label=None, **opts):
1313 def branch(ui, repo, label=None, **opts):
1314 """set or show the current branch name
1314 """set or show the current branch name
1315
1315
1316 .. note::
1316 .. note::
1317
1317
1318 Branch names are permanent and global. Use :hg:`bookmark` to create a
1318 Branch names are permanent and global. Use :hg:`bookmark` to create a
1319 light-weight bookmark instead. See :hg:`help glossary` for more
1319 light-weight bookmark instead. See :hg:`help glossary` for more
1320 information about named branches and bookmarks.
1320 information about named branches and bookmarks.
1321
1321
1322 With no argument, show the current branch name. With one argument,
1322 With no argument, show the current branch name. With one argument,
1323 set the working directory branch name (the branch will not exist
1323 set the working directory branch name (the branch will not exist
1324 in the repository until the next commit). Standard practice
1324 in the repository until the next commit). Standard practice
1325 recommends that primary development take place on the 'default'
1325 recommends that primary development take place on the 'default'
1326 branch.
1326 branch.
1327
1327
1328 Unless -f/--force is specified, branch will not let you set a
1328 Unless -f/--force is specified, branch will not let you set a
1329 branch name that already exists.
1329 branch name that already exists.
1330
1330
1331 Use -C/--clean to reset the working directory branch to that of
1331 Use -C/--clean to reset the working directory branch to that of
1332 the parent of the working directory, negating a previous branch
1332 the parent of the working directory, negating a previous branch
1333 change.
1333 change.
1334
1334
1335 Use the command :hg:`update` to switch to an existing branch. Use
1335 Use the command :hg:`update` to switch to an existing branch. Use
1336 :hg:`commit --close-branch` to mark this branch head as closed.
1336 :hg:`commit --close-branch` to mark this branch head as closed.
1337 When all heads of a branch are closed, the branch will be
1337 When all heads of a branch are closed, the branch will be
1338 considered closed.
1338 considered closed.
1339
1339
1340 Returns 0 on success.
1340 Returns 0 on success.
1341 """
1341 """
1342 opts = pycompat.byteskwargs(opts)
1342 revs = opts.get('rev')
1343 revs = opts.get(b'rev')
1344 if label:
1343 if label:
1345 label = label.strip()
1344 label = label.strip()
1346
1345
1347 if not opts.get(b'clean') and not label:
1346 if not opts.get('clean') and not label:
1348 if revs:
1347 if revs:
1349 raise error.InputError(
1348 raise error.InputError(
1350 _(b"no branch name specified for the revisions")
1349 _(b"no branch name specified for the revisions")
1351 )
1350 )
1352 ui.write(b"%s\n" % repo.dirstate.branch())
1351 ui.write(b"%s\n" % repo.dirstate.branch())
1353 return
1352 return
1354
1353
1355 with repo.wlock():
1354 with repo.wlock():
1356 if opts.get(b'clean'):
1355 if opts.get('clean'):
1357 label = repo[b'.'].branch()
1356 label = repo[b'.'].branch()
1358 repo.dirstate.setbranch(label, repo.currenttransaction())
1357 repo.dirstate.setbranch(label, repo.currenttransaction())
1359 ui.status(_(b'reset working directory to branch %s\n') % label)
1358 ui.status(_(b'reset working directory to branch %s\n') % label)
1360 elif label:
1359 elif label:
1361
1360
1362 scmutil.checknewlabel(repo, label, b'branch')
1361 scmutil.checknewlabel(repo, label, b'branch')
1363 if revs:
1362 if revs:
1364 return cmdutil.changebranch(ui, repo, revs, label, opts)
1363 return cmdutil.changebranch(ui, repo, revs, label, **opts)
1365
1364
1366 if not opts.get(b'force') and label in repo.branchmap():
1365 if not opts.get('force') and label in repo.branchmap():
1367 if label not in [p.branch() for p in repo[None].parents()]:
1366 if label not in [p.branch() for p in repo[None].parents()]:
1368 raise error.InputError(
1367 raise error.InputError(
1369 _(b'a branch of the same name already exists'),
1368 _(b'a branch of the same name already exists'),
1370 # i18n: "it" refers to an existing branch
1369 # i18n: "it" refers to an existing branch
1371 hint=_(b"use 'hg update' to switch to it"),
1370 hint=_(b"use 'hg update' to switch to it"),
1372 )
1371 )
1373
1372
1374 repo.dirstate.setbranch(label, repo.currenttransaction())
1373 repo.dirstate.setbranch(label, repo.currenttransaction())
1375 ui.status(_(b'marked working directory as branch %s\n') % label)
1374 ui.status(_(b'marked working directory as branch %s\n') % label)
1376
1375
1377 # find any open named branches aside from default
1376 # find any open named branches aside from default
1378 for n, h, t, c in repo.branchmap().iterbranches():
1377 for n, h, t, c in repo.branchmap().iterbranches():
1379 if n != b"default" and not c:
1378 if n != b"default" and not c:
1380 return 0
1379 return 0
1381 ui.status(
1380 ui.status(
1382 _(
1381 _(
1383 b'(branches are permanent and global, '
1382 b'(branches are permanent and global, '
1384 b'did you want a bookmark?)\n'
1383 b'did you want a bookmark?)\n'
1385 )
1384 )
1386 )
1385 )
1387
1386
1388
1387
1389 @command(
1388 @command(
1390 b'branches',
1389 b'branches',
1391 [
1390 [
1392 (
1391 (
1393 b'a',
1392 b'a',
1394 b'active',
1393 b'active',
1395 False,
1394 False,
1396 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1395 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1397 ),
1396 ),
1398 (b'c', b'closed', False, _(b'show normal and closed branches')),
1397 (b'c', b'closed', False, _(b'show normal and closed branches')),
1399 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1398 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1400 ]
1399 ]
1401 + formatteropts,
1400 + formatteropts,
1402 _(b'[-c]'),
1401 _(b'[-c]'),
1403 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1402 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1404 intents={INTENT_READONLY},
1403 intents={INTENT_READONLY},
1405 )
1404 )
1406 def branches(ui, repo, active=False, closed=False, **opts):
1405 def branches(ui, repo, active=False, closed=False, **opts):
1407 """list repository named branches
1406 """list repository named branches
1408
1407
1409 List the repository's named branches, indicating which ones are
1408 List the repository's named branches, indicating which ones are
1410 inactive. If -c/--closed is specified, also list branches which have
1409 inactive. If -c/--closed is specified, also list branches which have
1411 been marked closed (see :hg:`commit --close-branch`).
1410 been marked closed (see :hg:`commit --close-branch`).
1412
1411
1413 Use the command :hg:`update` to switch to an existing branch.
1412 Use the command :hg:`update` to switch to an existing branch.
1414
1413
1415 .. container:: verbose
1414 .. container:: verbose
1416
1415
1417 Template:
1416 Template:
1418
1417
1419 The following keywords are supported in addition to the common template
1418 The following keywords are supported in addition to the common template
1420 keywords and functions such as ``{branch}``. See also
1419 keywords and functions such as ``{branch}``. See also
1421 :hg:`help templates`.
1420 :hg:`help templates`.
1422
1421
1423 :active: Boolean. True if the branch is active.
1422 :active: Boolean. True if the branch is active.
1424 :closed: Boolean. True if the branch is closed.
1423 :closed: Boolean. True if the branch is closed.
1425 :current: Boolean. True if it is the current branch.
1424 :current: Boolean. True if it is the current branch.
1426
1425
1427 Returns 0.
1426 Returns 0.
1428 """
1427 """
1429
1428
1430 opts = pycompat.byteskwargs(opts)
1429 opts = pycompat.byteskwargs(opts)
1431 revs = opts.get(b'rev')
1430 revs = opts.get(b'rev')
1432 selectedbranches = None
1431 selectedbranches = None
1433 if revs:
1432 if revs:
1434 revs = logcmdutil.revrange(repo, revs)
1433 revs = logcmdutil.revrange(repo, revs)
1435 getbi = repo.revbranchcache().branchinfo
1434 getbi = repo.revbranchcache().branchinfo
1436 selectedbranches = {getbi(r)[0] for r in revs}
1435 selectedbranches = {getbi(r)[0] for r in revs}
1437
1436
1438 ui.pager(b'branches')
1437 ui.pager(b'branches')
1439 fm = ui.formatter(b'branches', opts)
1438 fm = ui.formatter(b'branches', opts)
1440 hexfunc = fm.hexfunc
1439 hexfunc = fm.hexfunc
1441
1440
1442 allheads = set(repo.heads())
1441 allheads = set(repo.heads())
1443 branches = []
1442 branches = []
1444 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1443 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1445 if selectedbranches is not None and tag not in selectedbranches:
1444 if selectedbranches is not None and tag not in selectedbranches:
1446 continue
1445 continue
1447 isactive = False
1446 isactive = False
1448 if not isclosed:
1447 if not isclosed:
1449 openheads = set(repo.branchmap().iteropen(heads))
1448 openheads = set(repo.branchmap().iteropen(heads))
1450 isactive = bool(openheads & allheads)
1449 isactive = bool(openheads & allheads)
1451 branches.append((tag, repo[tip], isactive, not isclosed))
1450 branches.append((tag, repo[tip], isactive, not isclosed))
1452 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1451 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1453
1452
1454 for tag, ctx, isactive, isopen in branches:
1453 for tag, ctx, isactive, isopen in branches:
1455 if active and not isactive:
1454 if active and not isactive:
1456 continue
1455 continue
1457 if isactive:
1456 if isactive:
1458 label = b'branches.active'
1457 label = b'branches.active'
1459 notice = b''
1458 notice = b''
1460 elif not isopen:
1459 elif not isopen:
1461 if not closed:
1460 if not closed:
1462 continue
1461 continue
1463 label = b'branches.closed'
1462 label = b'branches.closed'
1464 notice = _(b' (closed)')
1463 notice = _(b' (closed)')
1465 else:
1464 else:
1466 label = b'branches.inactive'
1465 label = b'branches.inactive'
1467 notice = _(b' (inactive)')
1466 notice = _(b' (inactive)')
1468 current = tag == repo.dirstate.branch()
1467 current = tag == repo.dirstate.branch()
1469 if current:
1468 if current:
1470 label = b'branches.current'
1469 label = b'branches.current'
1471
1470
1472 fm.startitem()
1471 fm.startitem()
1473 fm.write(b'branch', b'%s', tag, label=label)
1472 fm.write(b'branch', b'%s', tag, label=label)
1474 rev = ctx.rev()
1473 rev = ctx.rev()
1475 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1474 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1476 fmt = b' ' * padsize + b' %d:%s'
1475 fmt = b' ' * padsize + b' %d:%s'
1477 fm.condwrite(
1476 fm.condwrite(
1478 not ui.quiet,
1477 not ui.quiet,
1479 b'rev node',
1478 b'rev node',
1480 fmt,
1479 fmt,
1481 rev,
1480 rev,
1482 hexfunc(ctx.node()),
1481 hexfunc(ctx.node()),
1483 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1482 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1484 )
1483 )
1485 fm.context(ctx=ctx)
1484 fm.context(ctx=ctx)
1486 fm.data(active=isactive, closed=not isopen, current=current)
1485 fm.data(active=isactive, closed=not isopen, current=current)
1487 if not ui.quiet:
1486 if not ui.quiet:
1488 fm.plain(notice)
1487 fm.plain(notice)
1489 fm.plain(b'\n')
1488 fm.plain(b'\n')
1490 fm.end()
1489 fm.end()
1491
1490
1492
1491
1493 @command(
1492 @command(
1494 b'bundle',
1493 b'bundle',
1495 [
1494 [
1496 (
1495 (
1497 b'',
1496 b'',
1498 b'exact',
1497 b'exact',
1499 None,
1498 None,
1500 _(b'compute the base from the revision specified'),
1499 _(b'compute the base from the revision specified'),
1501 ),
1500 ),
1502 (
1501 (
1503 b'f',
1502 b'f',
1504 b'force',
1503 b'force',
1505 None,
1504 None,
1506 _(b'run even when the destination is unrelated'),
1505 _(b'run even when the destination is unrelated'),
1507 ),
1506 ),
1508 (
1507 (
1509 b'r',
1508 b'r',
1510 b'rev',
1509 b'rev',
1511 [],
1510 [],
1512 _(b'a changeset intended to be added to the destination'),
1511 _(b'a changeset intended to be added to the destination'),
1513 _(b'REV'),
1512 _(b'REV'),
1514 ),
1513 ),
1515 (
1514 (
1516 b'b',
1515 b'b',
1517 b'branch',
1516 b'branch',
1518 [],
1517 [],
1519 _(b'a specific branch you would like to bundle'),
1518 _(b'a specific branch you would like to bundle'),
1520 _(b'BRANCH'),
1519 _(b'BRANCH'),
1521 ),
1520 ),
1522 (
1521 (
1523 b'',
1522 b'',
1524 b'base',
1523 b'base',
1525 [],
1524 [],
1526 _(b'a base changeset assumed to be available at the destination'),
1525 _(b'a base changeset assumed to be available at the destination'),
1527 _(b'REV'),
1526 _(b'REV'),
1528 ),
1527 ),
1529 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1528 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1530 (
1529 (
1531 b't',
1530 b't',
1532 b'type',
1531 b'type',
1533 b'bzip2',
1532 b'bzip2',
1534 _(b'bundle compression type to use'),
1533 _(b'bundle compression type to use'),
1535 _(b'TYPE'),
1534 _(b'TYPE'),
1536 ),
1535 ),
1537 ]
1536 ]
1538 + remoteopts,
1537 + remoteopts,
1539 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1538 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1540 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1539 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1541 )
1540 )
1542 def bundle(ui, repo, fname, *dests, **opts):
1541 def bundle(ui, repo, fname, *dests, **opts):
1543 """create a bundle file
1542 """create a bundle file
1544
1543
1545 Generate a bundle file containing data to be transferred to another
1544 Generate a bundle file containing data to be transferred to another
1546 repository.
1545 repository.
1547
1546
1548 To create a bundle containing all changesets, use -a/--all
1547 To create a bundle containing all changesets, use -a/--all
1549 (or --base null). Otherwise, hg assumes the destination will have
1548 (or --base null). Otherwise, hg assumes the destination will have
1550 all the nodes you specify with --base parameters. Otherwise, hg
1549 all the nodes you specify with --base parameters. Otherwise, hg
1551 will assume the repository has all the nodes in destination, or
1550 will assume the repository has all the nodes in destination, or
1552 default-push/default if no destination is specified, where destination
1551 default-push/default if no destination is specified, where destination
1553 is the repositories you provide through DEST option.
1552 is the repositories you provide through DEST option.
1554
1553
1555 You can change bundle format with the -t/--type option. See
1554 You can change bundle format with the -t/--type option. See
1556 :hg:`help bundlespec` for documentation on this format. By default,
1555 :hg:`help bundlespec` for documentation on this format. By default,
1557 the most appropriate format is used and compression defaults to
1556 the most appropriate format is used and compression defaults to
1558 bzip2.
1557 bzip2.
1559
1558
1560 The bundle file can then be transferred using conventional means
1559 The bundle file can then be transferred using conventional means
1561 and applied to another repository with the unbundle or pull
1560 and applied to another repository with the unbundle or pull
1562 command. This is useful when direct push and pull are not
1561 command. This is useful when direct push and pull are not
1563 available or when exporting an entire repository is undesirable.
1562 available or when exporting an entire repository is undesirable.
1564
1563
1565 Applying bundles preserves all changeset contents including
1564 Applying bundles preserves all changeset contents including
1566 permissions, copy/rename information, and revision history.
1565 permissions, copy/rename information, and revision history.
1567
1566
1568 Returns 0 on success, 1 if no changes found.
1567 Returns 0 on success, 1 if no changes found.
1569 """
1568 """
1570 opts = pycompat.byteskwargs(opts)
1569 opts = pycompat.byteskwargs(opts)
1571
1570
1572 revs = None
1571 revs = None
1573 if b'rev' in opts:
1572 if b'rev' in opts:
1574 revstrings = opts[b'rev']
1573 revstrings = opts[b'rev']
1575 revs = logcmdutil.revrange(repo, revstrings)
1574 revs = logcmdutil.revrange(repo, revstrings)
1576 if revstrings and not revs:
1575 if revstrings and not revs:
1577 raise error.InputError(_(b'no commits to bundle'))
1576 raise error.InputError(_(b'no commits to bundle'))
1578
1577
1579 bundletype = opts.get(b'type', b'bzip2').lower()
1578 bundletype = opts.get(b'type', b'bzip2').lower()
1580 try:
1579 try:
1581 bundlespec = bundlecaches.parsebundlespec(
1580 bundlespec = bundlecaches.parsebundlespec(
1582 repo, bundletype, strict=False
1581 repo, bundletype, strict=False
1583 )
1582 )
1584 except error.UnsupportedBundleSpecification as e:
1583 except error.UnsupportedBundleSpecification as e:
1585 raise error.InputError(
1584 raise error.InputError(
1586 pycompat.bytestr(e),
1585 pycompat.bytestr(e),
1587 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1586 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1588 )
1587 )
1589 cgversion = bundlespec.params[b"cg.version"]
1588 cgversion = bundlespec.params[b"cg.version"]
1590
1589
1591 # Packed bundles are a pseudo bundle format for now.
1590 # Packed bundles are a pseudo bundle format for now.
1592 if cgversion == b's1':
1591 if cgversion == b's1':
1593 raise error.InputError(
1592 raise error.InputError(
1594 _(b'packed bundles cannot be produced by "hg bundle"'),
1593 _(b'packed bundles cannot be produced by "hg bundle"'),
1595 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1594 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1596 )
1595 )
1597
1596
1598 if opts.get(b'all'):
1597 if opts.get(b'all'):
1599 if dests:
1598 if dests:
1600 raise error.InputError(
1599 raise error.InputError(
1601 _(b"--all is incompatible with specifying destinations")
1600 _(b"--all is incompatible with specifying destinations")
1602 )
1601 )
1603 if opts.get(b'base'):
1602 if opts.get(b'base'):
1604 ui.warn(_(b"ignoring --base because --all was specified\n"))
1603 ui.warn(_(b"ignoring --base because --all was specified\n"))
1605 if opts.get(b'exact'):
1604 if opts.get(b'exact'):
1606 ui.warn(_(b"ignoring --exact because --all was specified\n"))
1605 ui.warn(_(b"ignoring --exact because --all was specified\n"))
1607 base = [nullrev]
1606 base = [nullrev]
1608 elif opts.get(b'exact'):
1607 elif opts.get(b'exact'):
1609 if dests:
1608 if dests:
1610 raise error.InputError(
1609 raise error.InputError(
1611 _(b"--exact is incompatible with specifying destinations")
1610 _(b"--exact is incompatible with specifying destinations")
1612 )
1611 )
1613 if opts.get(b'base'):
1612 if opts.get(b'base'):
1614 ui.warn(_(b"ignoring --base because --exact was specified\n"))
1613 ui.warn(_(b"ignoring --base because --exact was specified\n"))
1615 base = repo.revs(b'parents(%ld) - %ld', revs, revs)
1614 base = repo.revs(b'parents(%ld) - %ld', revs, revs)
1616 if not base:
1615 if not base:
1617 base = [nullrev]
1616 base = [nullrev]
1618 else:
1617 else:
1619 base = logcmdutil.revrange(repo, opts.get(b'base'))
1618 base = logcmdutil.revrange(repo, opts.get(b'base'))
1620 if cgversion not in changegroup.supportedoutgoingversions(repo):
1619 if cgversion not in changegroup.supportedoutgoingversions(repo):
1621 raise error.Abort(
1620 raise error.Abort(
1622 _(b"repository does not support bundle version %s") % cgversion
1621 _(b"repository does not support bundle version %s") % cgversion
1623 )
1622 )
1624
1623
1625 if base:
1624 if base:
1626 if dests:
1625 if dests:
1627 raise error.InputError(
1626 raise error.InputError(
1628 _(b"--base is incompatible with specifying destinations")
1627 _(b"--base is incompatible with specifying destinations")
1629 )
1628 )
1630 cl = repo.changelog
1629 cl = repo.changelog
1631 common = [cl.node(rev) for rev in base]
1630 common = [cl.node(rev) for rev in base]
1632 heads = [cl.node(r) for r in revs] if revs else None
1631 heads = [cl.node(r) for r in revs] if revs else None
1633 outgoing = discovery.outgoing(repo, common, heads)
1632 outgoing = discovery.outgoing(repo, common, heads)
1634 missing = outgoing.missing
1633 missing = outgoing.missing
1635 excluded = outgoing.excluded
1634 excluded = outgoing.excluded
1636 else:
1635 else:
1637 missing = set()
1636 missing = set()
1638 excluded = set()
1637 excluded = set()
1639 for path in urlutil.get_push_paths(repo, ui, dests):
1638 for path in urlutil.get_push_paths(repo, ui, dests):
1640 other = hg.peer(repo, opts, path)
1639 other = hg.peer(repo, opts, path)
1641 if revs is not None:
1640 if revs is not None:
1642 hex_revs = [repo[r].hex() for r in revs]
1641 hex_revs = [repo[r].hex() for r in revs]
1643 else:
1642 else:
1644 hex_revs = None
1643 hex_revs = None
1645 branches = (path.branch, [])
1644 branches = (path.branch, [])
1646 head_revs, checkout = hg.addbranchrevs(
1645 head_revs, checkout = hg.addbranchrevs(
1647 repo, repo, branches, hex_revs
1646 repo, repo, branches, hex_revs
1648 )
1647 )
1649 heads = (
1648 heads = (
1650 head_revs
1649 head_revs
1651 and pycompat.maplist(repo.lookup, head_revs)
1650 and pycompat.maplist(repo.lookup, head_revs)
1652 or head_revs
1651 or head_revs
1653 )
1652 )
1654 outgoing = discovery.findcommonoutgoing(
1653 outgoing = discovery.findcommonoutgoing(
1655 repo,
1654 repo,
1656 other,
1655 other,
1657 onlyheads=heads,
1656 onlyheads=heads,
1658 force=opts.get(b'force'),
1657 force=opts.get(b'force'),
1659 portable=True,
1658 portable=True,
1660 )
1659 )
1661 missing.update(outgoing.missing)
1660 missing.update(outgoing.missing)
1662 excluded.update(outgoing.excluded)
1661 excluded.update(outgoing.excluded)
1663
1662
1664 if not missing:
1663 if not missing:
1665 scmutil.nochangesfound(ui, repo, not base and excluded)
1664 scmutil.nochangesfound(ui, repo, not base and excluded)
1666 return 1
1665 return 1
1667
1666
1668 # internal changeset are internal implementation details that should not
1667 # internal changeset are internal implementation details that should not
1669 # leave the repository. Bundling with `hg bundle` create such risk.
1668 # leave the repository. Bundling with `hg bundle` create such risk.
1670 bundled_internal = repo.revs(b"%ln and _internal()", missing)
1669 bundled_internal = repo.revs(b"%ln and _internal()", missing)
1671 if bundled_internal:
1670 if bundled_internal:
1672 msg = _(b"cannot bundle internal changesets")
1671 msg = _(b"cannot bundle internal changesets")
1673 hint = _(b"%d internal changesets selected") % len(bundled_internal)
1672 hint = _(b"%d internal changesets selected") % len(bundled_internal)
1674 raise error.Abort(msg, hint=hint)
1673 raise error.Abort(msg, hint=hint)
1675
1674
1676 if heads:
1675 if heads:
1677 outgoing = discovery.outgoing(
1676 outgoing = discovery.outgoing(
1678 repo, missingroots=missing, ancestorsof=heads
1677 repo, missingroots=missing, ancestorsof=heads
1679 )
1678 )
1680 else:
1679 else:
1681 outgoing = discovery.outgoing(repo, missingroots=missing)
1680 outgoing = discovery.outgoing(repo, missingroots=missing)
1682 outgoing.excluded = sorted(excluded)
1681 outgoing.excluded = sorted(excluded)
1683
1682
1684 if cgversion == b'01': # bundle1
1683 if cgversion == b'01': # bundle1
1685 bversion = b'HG10' + bundlespec.wirecompression
1684 bversion = b'HG10' + bundlespec.wirecompression
1686 bcompression = None
1685 bcompression = None
1687 elif cgversion in (b'02', b'03'):
1686 elif cgversion in (b'02', b'03'):
1688 bversion = b'HG20'
1687 bversion = b'HG20'
1689 bcompression = bundlespec.wirecompression
1688 bcompression = bundlespec.wirecompression
1690 else:
1689 else:
1691 raise error.ProgrammingError(
1690 raise error.ProgrammingError(
1692 b'bundle: unexpected changegroup version %s' % cgversion
1691 b'bundle: unexpected changegroup version %s' % cgversion
1693 )
1692 )
1694
1693
1695 # TODO compression options should be derived from bundlespec parsing.
1694 # TODO compression options should be derived from bundlespec parsing.
1696 # This is a temporary hack to allow adjusting bundle compression
1695 # This is a temporary hack to allow adjusting bundle compression
1697 # level without a) formalizing the bundlespec changes to declare it
1696 # level without a) formalizing the bundlespec changes to declare it
1698 # b) introducing a command flag.
1697 # b) introducing a command flag.
1699 compopts = {}
1698 compopts = {}
1700 complevel = ui.configint(
1699 complevel = ui.configint(
1701 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1700 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1702 )
1701 )
1703 if complevel is None:
1702 if complevel is None:
1704 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1703 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1705 if complevel is not None:
1704 if complevel is not None:
1706 compopts[b'level'] = complevel
1705 compopts[b'level'] = complevel
1707
1706
1708 compthreads = ui.configint(
1707 compthreads = ui.configint(
1709 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1708 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1710 )
1709 )
1711 if compthreads is None:
1710 if compthreads is None:
1712 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1711 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1713 if compthreads is not None:
1712 if compthreads is not None:
1714 compopts[b'threads'] = compthreads
1713 compopts[b'threads'] = compthreads
1715
1714
1716 # Bundling of obsmarker and phases is optional as not all clients
1715 # Bundling of obsmarker and phases is optional as not all clients
1717 # support the necessary features.
1716 # support the necessary features.
1718 cfg = ui.configbool
1717 cfg = ui.configbool
1719 obsolescence_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker')
1718 obsolescence_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker')
1720 bundlespec.set_param(b'obsolescence', obsolescence_cfg, overwrite=False)
1719 bundlespec.set_param(b'obsolescence', obsolescence_cfg, overwrite=False)
1721 obs_mand_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker:mandatory')
1720 obs_mand_cfg = cfg(b'experimental', b'evolution.bundle-obsmarker:mandatory')
1722 bundlespec.set_param(
1721 bundlespec.set_param(
1723 b'obsolescence-mandatory', obs_mand_cfg, overwrite=False
1722 b'obsolescence-mandatory', obs_mand_cfg, overwrite=False
1724 )
1723 )
1725 if not bundlespec.params.get(b'phases', False):
1724 if not bundlespec.params.get(b'phases', False):
1726 phases_cfg = cfg(b'experimental', b'bundle-phases')
1725 phases_cfg = cfg(b'experimental', b'bundle-phases')
1727 bundlespec.set_param(b'phases', phases_cfg, overwrite=False)
1726 bundlespec.set_param(b'phases', phases_cfg, overwrite=False)
1728
1727
1729 bundle2.writenewbundle(
1728 bundle2.writenewbundle(
1730 ui,
1729 ui,
1731 repo,
1730 repo,
1732 b'bundle',
1731 b'bundle',
1733 fname,
1732 fname,
1734 bversion,
1733 bversion,
1735 outgoing,
1734 outgoing,
1736 bundlespec.params,
1735 bundlespec.params,
1737 compression=bcompression,
1736 compression=bcompression,
1738 compopts=compopts,
1737 compopts=compopts,
1739 )
1738 )
1740
1739
1741
1740
1742 @command(
1741 @command(
1743 b'cat',
1742 b'cat',
1744 [
1743 [
1745 (
1744 (
1746 b'o',
1745 b'o',
1747 b'output',
1746 b'output',
1748 b'',
1747 b'',
1749 _(b'print output to file with formatted name'),
1748 _(b'print output to file with formatted name'),
1750 _(b'FORMAT'),
1749 _(b'FORMAT'),
1751 ),
1750 ),
1752 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1751 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1753 (b'', b'decode', None, _(b'apply any matching decode filter')),
1752 (b'', b'decode', None, _(b'apply any matching decode filter')),
1754 ]
1753 ]
1755 + walkopts
1754 + walkopts
1756 + formatteropts,
1755 + formatteropts,
1757 _(b'[OPTION]... FILE...'),
1756 _(b'[OPTION]... FILE...'),
1758 helpcategory=command.CATEGORY_FILE_CONTENTS,
1757 helpcategory=command.CATEGORY_FILE_CONTENTS,
1759 inferrepo=True,
1758 inferrepo=True,
1760 intents={INTENT_READONLY},
1759 intents={INTENT_READONLY},
1761 )
1760 )
1762 def cat(ui, repo, file1, *pats, **opts):
1761 def cat(ui, repo, file1, *pats, **opts):
1763 """output the current or given revision of files
1762 """output the current or given revision of files
1764
1763
1765 Print the specified files as they were at the given revision. If
1764 Print the specified files as they were at the given revision. If
1766 no revision is given, the parent of the working directory is used.
1765 no revision is given, the parent of the working directory is used.
1767
1766
1768 Output may be to a file, in which case the name of the file is
1767 Output may be to a file, in which case the name of the file is
1769 given using a template string. See :hg:`help templates`. In addition
1768 given using a template string. See :hg:`help templates`. In addition
1770 to the common template keywords, the following formatting rules are
1769 to the common template keywords, the following formatting rules are
1771 supported:
1770 supported:
1772
1771
1773 :``%%``: literal "%" character
1772 :``%%``: literal "%" character
1774 :``%s``: basename of file being printed
1773 :``%s``: basename of file being printed
1775 :``%d``: dirname of file being printed, or '.' if in repository root
1774 :``%d``: dirname of file being printed, or '.' if in repository root
1776 :``%p``: root-relative path name of file being printed
1775 :``%p``: root-relative path name of file being printed
1777 :``%H``: changeset hash (40 hexadecimal digits)
1776 :``%H``: changeset hash (40 hexadecimal digits)
1778 :``%R``: changeset revision number
1777 :``%R``: changeset revision number
1779 :``%h``: short-form changeset hash (12 hexadecimal digits)
1778 :``%h``: short-form changeset hash (12 hexadecimal digits)
1780 :``%r``: zero-padded changeset revision number
1779 :``%r``: zero-padded changeset revision number
1781 :``%b``: basename of the exporting repository
1780 :``%b``: basename of the exporting repository
1782 :``\\``: literal "\\" character
1781 :``\\``: literal "\\" character
1783
1782
1784 .. container:: verbose
1783 .. container:: verbose
1785
1784
1786 Template:
1785 Template:
1787
1786
1788 The following keywords are supported in addition to the common template
1787 The following keywords are supported in addition to the common template
1789 keywords and functions. See also :hg:`help templates`.
1788 keywords and functions. See also :hg:`help templates`.
1790
1789
1791 :data: String. File content.
1790 :data: String. File content.
1792 :path: String. Repository-absolute path of the file.
1791 :path: String. Repository-absolute path of the file.
1793
1792
1794 Returns 0 on success.
1793 Returns 0 on success.
1795 """
1794 """
1796 rev = opts.get('rev')
1795 rev = opts.get('rev')
1797 if rev:
1796 if rev:
1798 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1797 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1799 ctx = logcmdutil.revsingle(repo, rev)
1798 ctx = logcmdutil.revsingle(repo, rev)
1800 m = scmutil.match(ctx, (file1,) + pats, pycompat.byteskwargs(opts))
1799 m = scmutil.match(ctx, (file1,) + pats, pycompat.byteskwargs(opts))
1801 fntemplate = opts.pop('output', b'')
1800 fntemplate = opts.pop('output', b'')
1802 if cmdutil.isstdiofilename(fntemplate):
1801 if cmdutil.isstdiofilename(fntemplate):
1803 fntemplate = b''
1802 fntemplate = b''
1804
1803
1805 if fntemplate:
1804 if fntemplate:
1806 fm = formatter.nullformatter(ui, b'cat', pycompat.byteskwargs(opts))
1805 fm = formatter.nullformatter(ui, b'cat', pycompat.byteskwargs(opts))
1807 else:
1806 else:
1808 ui.pager(b'cat')
1807 ui.pager(b'cat')
1809 fm = ui.formatter(b'cat', pycompat.byteskwargs(opts))
1808 fm = ui.formatter(b'cat', pycompat.byteskwargs(opts))
1810 with fm:
1809 with fm:
1811 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, b'', **opts)
1810 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, b'', **opts)
1812
1811
1813
1812
1814 @command(
1813 @command(
1815 b'clone',
1814 b'clone',
1816 [
1815 [
1817 (
1816 (
1818 b'U',
1817 b'U',
1819 b'noupdate',
1818 b'noupdate',
1820 None,
1819 None,
1821 _(
1820 _(
1822 b'the clone will include an empty working '
1821 b'the clone will include an empty working '
1823 b'directory (only a repository)'
1822 b'directory (only a repository)'
1824 ),
1823 ),
1825 ),
1824 ),
1826 (
1825 (
1827 b'u',
1826 b'u',
1828 b'updaterev',
1827 b'updaterev',
1829 b'',
1828 b'',
1830 _(b'revision, tag, or branch to check out'),
1829 _(b'revision, tag, or branch to check out'),
1831 _(b'REV'),
1830 _(b'REV'),
1832 ),
1831 ),
1833 (
1832 (
1834 b'r',
1833 b'r',
1835 b'rev',
1834 b'rev',
1836 [],
1835 [],
1837 _(
1836 _(
1838 b'do not clone everything, but include this changeset'
1837 b'do not clone everything, but include this changeset'
1839 b' and its ancestors'
1838 b' and its ancestors'
1840 ),
1839 ),
1841 _(b'REV'),
1840 _(b'REV'),
1842 ),
1841 ),
1843 (
1842 (
1844 b'b',
1843 b'b',
1845 b'branch',
1844 b'branch',
1846 [],
1845 [],
1847 _(
1846 _(
1848 b'do not clone everything, but include this branch\'s'
1847 b'do not clone everything, but include this branch\'s'
1849 b' changesets and their ancestors'
1848 b' changesets and their ancestors'
1850 ),
1849 ),
1851 _(b'BRANCH'),
1850 _(b'BRANCH'),
1852 ),
1851 ),
1853 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1852 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1854 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1853 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1855 (b'', b'stream', None, _(b'clone with minimal data processing')),
1854 (b'', b'stream', None, _(b'clone with minimal data processing')),
1856 ]
1855 ]
1857 + remoteopts,
1856 + remoteopts,
1858 _(b'[OPTION]... SOURCE [DEST]'),
1857 _(b'[OPTION]... SOURCE [DEST]'),
1859 helpcategory=command.CATEGORY_REPO_CREATION,
1858 helpcategory=command.CATEGORY_REPO_CREATION,
1860 helpbasic=True,
1859 helpbasic=True,
1861 norepo=True,
1860 norepo=True,
1862 )
1861 )
1863 def clone(ui, source, dest=None, **opts):
1862 def clone(ui, source, dest=None, **opts):
1864 """make a copy of an existing repository
1863 """make a copy of an existing repository
1865
1864
1866 Create a copy of an existing repository in a new directory.
1865 Create a copy of an existing repository in a new directory.
1867
1866
1868 If no destination directory name is specified, it defaults to the
1867 If no destination directory name is specified, it defaults to the
1869 basename of the source.
1868 basename of the source.
1870
1869
1871 The location of the source is added to the new repository's
1870 The location of the source is added to the new repository's
1872 ``.hg/hgrc`` file, as the default to be used for future pulls.
1871 ``.hg/hgrc`` file, as the default to be used for future pulls.
1873
1872
1874 Only local paths and ``ssh://`` URLs are supported as
1873 Only local paths and ``ssh://`` URLs are supported as
1875 destinations. For ``ssh://`` destinations, no working directory or
1874 destinations. For ``ssh://`` destinations, no working directory or
1876 ``.hg/hgrc`` will be created on the remote side.
1875 ``.hg/hgrc`` will be created on the remote side.
1877
1876
1878 If the source repository has a bookmark called '@' set, that
1877 If the source repository has a bookmark called '@' set, that
1879 revision will be checked out in the new repository by default.
1878 revision will be checked out in the new repository by default.
1880
1879
1881 To check out a particular version, use -u/--update, or
1880 To check out a particular version, use -u/--update, or
1882 -U/--noupdate to create a clone with no working directory.
1881 -U/--noupdate to create a clone with no working directory.
1883
1882
1884 To pull only a subset of changesets, specify one or more revisions
1883 To pull only a subset of changesets, specify one or more revisions
1885 identifiers with -r/--rev or branches with -b/--branch. The
1884 identifiers with -r/--rev or branches with -b/--branch. The
1886 resulting clone will contain only the specified changesets and
1885 resulting clone will contain only the specified changesets and
1887 their ancestors. These options (or 'clone src#rev dest') imply
1886 their ancestors. These options (or 'clone src#rev dest') imply
1888 --pull, even for local source repositories.
1887 --pull, even for local source repositories.
1889
1888
1890 In normal clone mode, the remote normalizes repository data into a common
1889 In normal clone mode, the remote normalizes repository data into a common
1891 exchange format and the receiving end translates this data into its local
1890 exchange format and the receiving end translates this data into its local
1892 storage format. --stream activates a different clone mode that essentially
1891 storage format. --stream activates a different clone mode that essentially
1893 copies repository files from the remote with minimal data processing. This
1892 copies repository files from the remote with minimal data processing. This
1894 significantly reduces the CPU cost of a clone both remotely and locally.
1893 significantly reduces the CPU cost of a clone both remotely and locally.
1895 However, it often increases the transferred data size by 30-40%. This can
1894 However, it often increases the transferred data size by 30-40%. This can
1896 result in substantially faster clones where I/O throughput is plentiful,
1895 result in substantially faster clones where I/O throughput is plentiful,
1897 especially for larger repositories. A side-effect of --stream clones is
1896 especially for larger repositories. A side-effect of --stream clones is
1898 that storage settings and requirements on the remote are applied locally:
1897 that storage settings and requirements on the remote are applied locally:
1899 a modern client may inherit legacy or inefficient storage used by the
1898 a modern client may inherit legacy or inefficient storage used by the
1900 remote or a legacy Mercurial client may not be able to clone from a
1899 remote or a legacy Mercurial client may not be able to clone from a
1901 modern Mercurial remote.
1900 modern Mercurial remote.
1902
1901
1903 .. note::
1902 .. note::
1904
1903
1905 Specifying a tag will include the tagged changeset but not the
1904 Specifying a tag will include the tagged changeset but not the
1906 changeset containing the tag.
1905 changeset containing the tag.
1907
1906
1908 .. container:: verbose
1907 .. container:: verbose
1909
1908
1910 For efficiency, hardlinks are used for cloning whenever the
1909 For efficiency, hardlinks are used for cloning whenever the
1911 source and destination are on the same filesystem (note this
1910 source and destination are on the same filesystem (note this
1912 applies only to the repository data, not to the working
1911 applies only to the repository data, not to the working
1913 directory). Some filesystems, such as AFS, implement hardlinking
1912 directory). Some filesystems, such as AFS, implement hardlinking
1914 incorrectly, but do not report errors. In these cases, use the
1913 incorrectly, but do not report errors. In these cases, use the
1915 --pull option to avoid hardlinking.
1914 --pull option to avoid hardlinking.
1916
1915
1917 Mercurial will update the working directory to the first applicable
1916 Mercurial will update the working directory to the first applicable
1918 revision from this list:
1917 revision from this list:
1919
1918
1920 a) null if -U or the source repository has no changesets
1919 a) null if -U or the source repository has no changesets
1921 b) if -u . and the source repository is local, the first parent of
1920 b) if -u . and the source repository is local, the first parent of
1922 the source repository's working directory
1921 the source repository's working directory
1923 c) the changeset specified with -u (if a branch name, this means the
1922 c) the changeset specified with -u (if a branch name, this means the
1924 latest head of that branch)
1923 latest head of that branch)
1925 d) the changeset specified with -r
1924 d) the changeset specified with -r
1926 e) the tipmost head specified with -b
1925 e) the tipmost head specified with -b
1927 f) the tipmost head specified with the url#branch source syntax
1926 f) the tipmost head specified with the url#branch source syntax
1928 g) the revision marked with the '@' bookmark, if present
1927 g) the revision marked with the '@' bookmark, if present
1929 h) the tipmost head of the default branch
1928 h) the tipmost head of the default branch
1930 i) tip
1929 i) tip
1931
1930
1932 When cloning from servers that support it, Mercurial may fetch
1931 When cloning from servers that support it, Mercurial may fetch
1933 pre-generated data from a server-advertised URL or inline from the
1932 pre-generated data from a server-advertised URL or inline from the
1934 same stream. When this is done, hooks operating on incoming changesets
1933 same stream. When this is done, hooks operating on incoming changesets
1935 and changegroups may fire more than once, once for each pre-generated
1934 and changegroups may fire more than once, once for each pre-generated
1936 bundle and as well as for any additional remaining data. In addition,
1935 bundle and as well as for any additional remaining data. In addition,
1937 if an error occurs, the repository may be rolled back to a partial
1936 if an error occurs, the repository may be rolled back to a partial
1938 clone. This behavior may change in future releases.
1937 clone. This behavior may change in future releases.
1939 See :hg:`help -e clonebundles` for more.
1938 See :hg:`help -e clonebundles` for more.
1940
1939
1941 Examples:
1940 Examples:
1942
1941
1943 - clone a remote repository to a new directory named hg/::
1942 - clone a remote repository to a new directory named hg/::
1944
1943
1945 hg clone https://www.mercurial-scm.org/repo/hg/
1944 hg clone https://www.mercurial-scm.org/repo/hg/
1946
1945
1947 - create a lightweight local clone::
1946 - create a lightweight local clone::
1948
1947
1949 hg clone project/ project-feature/
1948 hg clone project/ project-feature/
1950
1949
1951 - clone from an absolute path on an ssh server (note double-slash)::
1950 - clone from an absolute path on an ssh server (note double-slash)::
1952
1951
1953 hg clone ssh://user@server//home/projects/alpha/
1952 hg clone ssh://user@server//home/projects/alpha/
1954
1953
1955 - do a streaming clone while checking out a specified version::
1954 - do a streaming clone while checking out a specified version::
1956
1955
1957 hg clone --stream http://server/repo -u 1.5
1956 hg clone --stream http://server/repo -u 1.5
1958
1957
1959 - create a repository without changesets after a particular revision::
1958 - create a repository without changesets after a particular revision::
1960
1959
1961 hg clone -r 04e544 experimental/ good/
1960 hg clone -r 04e544 experimental/ good/
1962
1961
1963 - clone (and track) a particular named branch::
1962 - clone (and track) a particular named branch::
1964
1963
1965 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1964 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1966
1965
1967 See :hg:`help urls` for details on specifying URLs.
1966 See :hg:`help urls` for details on specifying URLs.
1968
1967
1969 Returns 0 on success.
1968 Returns 0 on success.
1970 """
1969 """
1971 opts = pycompat.byteskwargs(opts)
1970 opts = pycompat.byteskwargs(opts)
1972 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1971 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1973
1972
1974 # --include/--exclude can come from narrow or sparse.
1973 # --include/--exclude can come from narrow or sparse.
1975 includepats, excludepats = None, None
1974 includepats, excludepats = None, None
1976
1975
1977 # hg.clone() differentiates between None and an empty set. So make sure
1976 # hg.clone() differentiates between None and an empty set. So make sure
1978 # patterns are sets if narrow is requested without patterns.
1977 # patterns are sets if narrow is requested without patterns.
1979 if opts.get(b'narrow'):
1978 if opts.get(b'narrow'):
1980 includepats = set()
1979 includepats = set()
1981 excludepats = set()
1980 excludepats = set()
1982
1981
1983 if opts.get(b'include'):
1982 if opts.get(b'include'):
1984 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1983 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1985 if opts.get(b'exclude'):
1984 if opts.get(b'exclude'):
1986 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1985 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1987
1986
1988 r = hg.clone(
1987 r = hg.clone(
1989 ui,
1988 ui,
1990 opts,
1989 opts,
1991 source,
1990 source,
1992 dest,
1991 dest,
1993 pull=opts.get(b'pull'),
1992 pull=opts.get(b'pull'),
1994 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1993 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1995 revs=opts.get(b'rev'),
1994 revs=opts.get(b'rev'),
1996 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1995 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1997 branch=opts.get(b'branch'),
1996 branch=opts.get(b'branch'),
1998 shareopts=opts.get(b'shareopts'),
1997 shareopts=opts.get(b'shareopts'),
1999 storeincludepats=includepats,
1998 storeincludepats=includepats,
2000 storeexcludepats=excludepats,
1999 storeexcludepats=excludepats,
2001 depth=opts.get(b'depth') or None,
2000 depth=opts.get(b'depth') or None,
2002 )
2001 )
2003
2002
2004 return r is None
2003 return r is None
2005
2004
2006
2005
2007 @command(
2006 @command(
2008 b'commit|ci',
2007 b'commit|ci',
2009 [
2008 [
2010 (
2009 (
2011 b'A',
2010 b'A',
2012 b'addremove',
2011 b'addremove',
2013 None,
2012 None,
2014 _(b'mark new/missing files as added/removed before committing'),
2013 _(b'mark new/missing files as added/removed before committing'),
2015 ),
2014 ),
2016 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
2015 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
2017 (b'', b'amend', None, _(b'amend the parent of the working directory')),
2016 (b'', b'amend', None, _(b'amend the parent of the working directory')),
2018 (b's', b'secret', None, _(b'use the secret phase for committing')),
2017 (b's', b'secret', None, _(b'use the secret phase for committing')),
2019 (b'', b'draft', None, _(b'use the draft phase for committing')),
2018 (b'', b'draft', None, _(b'use the draft phase for committing')),
2020 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
2019 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
2021 (
2020 (
2022 b'',
2021 b'',
2023 b'force-close-branch',
2022 b'force-close-branch',
2024 None,
2023 None,
2025 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
2024 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
2026 ),
2025 ),
2027 (b'i', b'interactive', None, _(b'use interactive mode')),
2026 (b'i', b'interactive', None, _(b'use interactive mode')),
2028 ]
2027 ]
2029 + walkopts
2028 + walkopts
2030 + commitopts
2029 + commitopts
2031 + commitopts2
2030 + commitopts2
2032 + subrepoopts,
2031 + subrepoopts,
2033 _(b'[OPTION]... [FILE]...'),
2032 _(b'[OPTION]... [FILE]...'),
2034 helpcategory=command.CATEGORY_COMMITTING,
2033 helpcategory=command.CATEGORY_COMMITTING,
2035 helpbasic=True,
2034 helpbasic=True,
2036 inferrepo=True,
2035 inferrepo=True,
2037 )
2036 )
2038 def commit(ui, repo, *pats, **opts):
2037 def commit(ui, repo, *pats, **opts):
2039 """commit the specified files or all outstanding changes
2038 """commit the specified files or all outstanding changes
2040
2039
2041 Commit changes to the given files into the repository. Unlike a
2040 Commit changes to the given files into the repository. Unlike a
2042 centralized SCM, this operation is a local operation. See
2041 centralized SCM, this operation is a local operation. See
2043 :hg:`push` for a way to actively distribute your changes.
2042 :hg:`push` for a way to actively distribute your changes.
2044
2043
2045 If a list of files is omitted, all changes reported by :hg:`status`
2044 If a list of files is omitted, all changes reported by :hg:`status`
2046 will be committed.
2045 will be committed.
2047
2046
2048 If you are committing the result of a merge, do not provide any
2047 If you are committing the result of a merge, do not provide any
2049 filenames or -I/-X filters.
2048 filenames or -I/-X filters.
2050
2049
2051 If no commit message is specified, Mercurial starts your
2050 If no commit message is specified, Mercurial starts your
2052 configured editor where you can enter a message. In case your
2051 configured editor where you can enter a message. In case your
2053 commit fails, you will find a backup of your message in
2052 commit fails, you will find a backup of your message in
2054 ``.hg/last-message.txt``.
2053 ``.hg/last-message.txt``.
2055
2054
2056 The --close-branch flag can be used to mark the current branch
2055 The --close-branch flag can be used to mark the current branch
2057 head closed. When all heads of a branch are closed, the branch
2056 head closed. When all heads of a branch are closed, the branch
2058 will be considered closed and no longer listed.
2057 will be considered closed and no longer listed.
2059
2058
2060 The --amend flag can be used to amend the parent of the
2059 The --amend flag can be used to amend the parent of the
2061 working directory with a new commit that contains the changes
2060 working directory with a new commit that contains the changes
2062 in the parent in addition to those currently reported by :hg:`status`,
2061 in the parent in addition to those currently reported by :hg:`status`,
2063 if there are any. The old commit is stored in a backup bundle in
2062 if there are any. The old commit is stored in a backup bundle in
2064 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2063 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2065 on how to restore it).
2064 on how to restore it).
2066
2065
2067 Message, user and date are taken from the amended commit unless
2066 Message, user and date are taken from the amended commit unless
2068 specified. When a message isn't specified on the command line,
2067 specified. When a message isn't specified on the command line,
2069 the editor will open with the message of the amended commit.
2068 the editor will open with the message of the amended commit.
2070
2069
2071 It is not possible to amend public changesets (see :hg:`help phases`)
2070 It is not possible to amend public changesets (see :hg:`help phases`)
2072 or changesets that have children.
2071 or changesets that have children.
2073
2072
2074 See :hg:`help dates` for a list of formats valid for -d/--date.
2073 See :hg:`help dates` for a list of formats valid for -d/--date.
2075
2074
2076 Returns 0 on success, 1 if nothing changed.
2075 Returns 0 on success, 1 if nothing changed.
2077
2076
2078 .. container:: verbose
2077 .. container:: verbose
2079
2078
2080 Examples:
2079 Examples:
2081
2080
2082 - commit all files ending in .py::
2081 - commit all files ending in .py::
2083
2082
2084 hg commit --include "set:**.py"
2083 hg commit --include "set:**.py"
2085
2084
2086 - commit all non-binary files::
2085 - commit all non-binary files::
2087
2086
2088 hg commit --exclude "set:binary()"
2087 hg commit --exclude "set:binary()"
2089
2088
2090 - amend the current commit and set the date to now::
2089 - amend the current commit and set the date to now::
2091
2090
2092 hg commit --amend --date now
2091 hg commit --amend --date now
2093 """
2092 """
2094 cmdutil.check_at_most_one_arg(opts, 'draft', 'secret')
2093 cmdutil.check_at_most_one_arg(opts, 'draft', 'secret')
2095 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2094 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2096 with repo.wlock(), repo.lock():
2095 with repo.wlock(), repo.lock():
2097 return _docommit(ui, repo, *pats, **opts)
2096 return _docommit(ui, repo, *pats, **opts)
2098
2097
2099
2098
2100 def _docommit(ui, repo, *pats, **opts):
2099 def _docommit(ui, repo, *pats, **opts):
2101 if opts.get('interactive'):
2100 if opts.get('interactive'):
2102 opts.pop('interactive')
2101 opts.pop('interactive')
2103 ret = cmdutil.dorecord(
2102 ret = cmdutil.dorecord(
2104 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2103 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2105 )
2104 )
2106 # ret can be 0 (no changes to record) or the value returned by
2105 # ret can be 0 (no changes to record) or the value returned by
2107 # commit(), 1 if nothing changed or None on success.
2106 # commit(), 1 if nothing changed or None on success.
2108 return 1 if ret == 0 else ret
2107 return 1 if ret == 0 else ret
2109
2108
2110 if opts.get('subrepos'):
2109 if opts.get('subrepos'):
2111 # Let --subrepos on the command line override config setting.
2110 # Let --subrepos on the command line override config setting.
2112 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2111 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2113
2112
2114 cmdutil.checkunfinished(repo, commit=True)
2113 cmdutil.checkunfinished(repo, commit=True)
2115
2114
2116 branch = repo[None].branch()
2115 branch = repo[None].branch()
2117 bheads = repo.branchheads(branch)
2116 bheads = repo.branchheads(branch)
2118 tip = repo.changelog.tip()
2117 tip = repo.changelog.tip()
2119
2118
2120 extra = {}
2119 extra = {}
2121 if opts.get('close_branch') or opts.get('force_close_branch'):
2120 if opts.get('close_branch') or opts.get('force_close_branch'):
2122 extra[b'close'] = b'1'
2121 extra[b'close'] = b'1'
2123
2122
2124 if repo[b'.'].closesbranch():
2123 if repo[b'.'].closesbranch():
2125 # Not ideal, but let us do an extra status early to prevent early
2124 # Not ideal, but let us do an extra status early to prevent early
2126 # bail out.
2125 # bail out.
2127 matcher = scmutil.match(
2126 matcher = scmutil.match(
2128 repo[None], pats, pycompat.byteskwargs(opts)
2127 repo[None], pats, pycompat.byteskwargs(opts)
2129 )
2128 )
2130 s = repo.status(match=matcher)
2129 s = repo.status(match=matcher)
2131 if s.modified or s.added or s.removed:
2130 if s.modified or s.added or s.removed:
2132 bheads = repo.branchheads(branch, closed=True)
2131 bheads = repo.branchheads(branch, closed=True)
2133 else:
2132 else:
2134 msg = _(b'current revision is already a branch closing head')
2133 msg = _(b'current revision is already a branch closing head')
2135 raise error.InputError(msg)
2134 raise error.InputError(msg)
2136
2135
2137 if not bheads:
2136 if not bheads:
2138 raise error.InputError(
2137 raise error.InputError(
2139 _(b'branch "%s" has no heads to close') % branch
2138 _(b'branch "%s" has no heads to close') % branch
2140 )
2139 )
2141 elif (
2140 elif (
2142 branch == repo[b'.'].branch()
2141 branch == repo[b'.'].branch()
2143 and repo[b'.'].node() not in bheads
2142 and repo[b'.'].node() not in bheads
2144 and not opts.get('force_close_branch')
2143 and not opts.get('force_close_branch')
2145 ):
2144 ):
2146 hint = _(
2145 hint = _(
2147 b'use --force-close-branch to close branch from a non-head'
2146 b'use --force-close-branch to close branch from a non-head'
2148 b' changeset'
2147 b' changeset'
2149 )
2148 )
2150 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2149 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2151 elif opts.get('amend'):
2150 elif opts.get('amend'):
2152 if (
2151 if (
2153 repo[b'.'].p1().branch() != branch
2152 repo[b'.'].p1().branch() != branch
2154 and repo[b'.'].p2().branch() != branch
2153 and repo[b'.'].p2().branch() != branch
2155 ):
2154 ):
2156 raise error.InputError(_(b'can only close branch heads'))
2155 raise error.InputError(_(b'can only close branch heads'))
2157
2156
2158 if opts.get('amend'):
2157 if opts.get('amend'):
2159 if ui.configbool(b'ui', b'commitsubrepos'):
2158 if ui.configbool(b'ui', b'commitsubrepos'):
2160 raise error.InputError(
2159 raise error.InputError(
2161 _(b'cannot amend with ui.commitsubrepos enabled')
2160 _(b'cannot amend with ui.commitsubrepos enabled')
2162 )
2161 )
2163
2162
2164 old = repo[b'.']
2163 old = repo[b'.']
2165 rewriteutil.precheck(repo, [old.rev()], b'amend')
2164 rewriteutil.precheck(repo, [old.rev()], b'amend')
2166
2165
2167 # Currently histedit gets confused if an amend happens while histedit
2166 # Currently histedit gets confused if an amend happens while histedit
2168 # is in progress. Since we have a checkunfinished command, we are
2167 # is in progress. Since we have a checkunfinished command, we are
2169 # temporarily honoring it.
2168 # temporarily honoring it.
2170 #
2169 #
2171 # Note: eventually this guard will be removed. Please do not expect
2170 # Note: eventually this guard will be removed. Please do not expect
2172 # this behavior to remain.
2171 # this behavior to remain.
2173 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2172 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2174 cmdutil.checkunfinished(repo)
2173 cmdutil.checkunfinished(repo)
2175
2174
2176 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2175 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2177 if node == old.node():
2176 if node == old.node():
2178 ui.status(_(b"nothing changed\n"))
2177 ui.status(_(b"nothing changed\n"))
2179 return 1
2178 return 1
2180 else:
2179 else:
2181
2180
2182 def commitfunc(ui, repo, message, match, opts):
2181 def commitfunc(ui, repo, message, match, opts):
2183 overrides = {}
2182 overrides = {}
2184 if opts.get(b'secret'):
2183 if opts.get(b'secret'):
2185 overrides[(b'phases', b'new-commit')] = b'secret'
2184 overrides[(b'phases', b'new-commit')] = b'secret'
2186 elif opts.get(b'draft'):
2185 elif opts.get(b'draft'):
2187 overrides[(b'phases', b'new-commit')] = b'draft'
2186 overrides[(b'phases', b'new-commit')] = b'draft'
2188
2187
2189 baseui = repo.baseui
2188 baseui = repo.baseui
2190 with baseui.configoverride(overrides, b'commit'):
2189 with baseui.configoverride(overrides, b'commit'):
2191 with ui.configoverride(overrides, b'commit'):
2190 with ui.configoverride(overrides, b'commit'):
2192 editform = cmdutil.mergeeditform(
2191 editform = cmdutil.mergeeditform(
2193 repo[None], b'commit.normal'
2192 repo[None], b'commit.normal'
2194 )
2193 )
2195 editor = cmdutil.getcommiteditor(
2194 editor = cmdutil.getcommiteditor(
2196 editform=editform, **pycompat.strkwargs(opts)
2195 editform=editform, **pycompat.strkwargs(opts)
2197 )
2196 )
2198 return repo.commit(
2197 return repo.commit(
2199 message,
2198 message,
2200 opts.get(b'user'),
2199 opts.get(b'user'),
2201 opts.get(b'date'),
2200 opts.get(b'date'),
2202 match,
2201 match,
2203 editor=editor,
2202 editor=editor,
2204 extra=extra,
2203 extra=extra,
2205 )
2204 )
2206
2205
2207 node = cmdutil.commit(
2206 node = cmdutil.commit(
2208 ui, repo, commitfunc, pats, pycompat.byteskwargs(opts)
2207 ui, repo, commitfunc, pats, pycompat.byteskwargs(opts)
2209 )
2208 )
2210
2209
2211 if not node:
2210 if not node:
2212 stat = cmdutil.postcommitstatus(
2211 stat = cmdutil.postcommitstatus(
2213 repo, pats, pycompat.byteskwargs(opts)
2212 repo, pats, pycompat.byteskwargs(opts)
2214 )
2213 )
2215 if stat.deleted:
2214 if stat.deleted:
2216 ui.status(
2215 ui.status(
2217 _(
2216 _(
2218 b"nothing changed (%d missing files, see "
2217 b"nothing changed (%d missing files, see "
2219 b"'hg status')\n"
2218 b"'hg status')\n"
2220 )
2219 )
2221 % len(stat.deleted)
2220 % len(stat.deleted)
2222 )
2221 )
2223 else:
2222 else:
2224 ui.status(_(b"nothing changed\n"))
2223 ui.status(_(b"nothing changed\n"))
2225 return 1
2224 return 1
2226
2225
2227 cmdutil.commitstatus(repo, node, branch, bheads, tip, **opts)
2226 cmdutil.commitstatus(repo, node, branch, bheads, tip, **opts)
2228
2227
2229 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2228 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2230 status(
2229 status(
2231 ui,
2230 ui,
2232 repo,
2231 repo,
2233 modified=True,
2232 modified=True,
2234 added=True,
2233 added=True,
2235 removed=True,
2234 removed=True,
2236 deleted=True,
2235 deleted=True,
2237 unknown=True,
2236 unknown=True,
2238 subrepos=opts.get('subrepos'),
2237 subrepos=opts.get('subrepos'),
2239 )
2238 )
2240
2239
2241
2240
2242 @command(
2241 @command(
2243 b'config|showconfig|debugconfig',
2242 b'config|showconfig|debugconfig',
2244 [
2243 [
2245 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2244 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2246 # This is experimental because we need
2245 # This is experimental because we need
2247 # * reasonable behavior around aliases,
2246 # * reasonable behavior around aliases,
2248 # * decide if we display [debug] [experimental] and [devel] section par
2247 # * decide if we display [debug] [experimental] and [devel] section par
2249 # default
2248 # default
2250 # * some way to display "generic" config entry (the one matching
2249 # * some way to display "generic" config entry (the one matching
2251 # regexp,
2250 # regexp,
2252 # * proper display of the different value type
2251 # * proper display of the different value type
2253 # * a better way to handle <DYNAMIC> values (and variable types),
2252 # * a better way to handle <DYNAMIC> values (and variable types),
2254 # * maybe some type information ?
2253 # * maybe some type information ?
2255 (
2254 (
2256 b'',
2255 b'',
2257 b'exp-all-known',
2256 b'exp-all-known',
2258 None,
2257 None,
2259 _(b'show all known config option (EXPERIMENTAL)'),
2258 _(b'show all known config option (EXPERIMENTAL)'),
2260 ),
2259 ),
2261 (b'e', b'edit', None, _(b'edit user config')),
2260 (b'e', b'edit', None, _(b'edit user config')),
2262 (b'l', b'local', None, _(b'edit repository config')),
2261 (b'l', b'local', None, _(b'edit repository config')),
2263 (b'', b'source', None, _(b'show source of configuration value')),
2262 (b'', b'source', None, _(b'show source of configuration value')),
2264 (
2263 (
2265 b'',
2264 b'',
2266 b'shared',
2265 b'shared',
2267 None,
2266 None,
2268 _(b'edit shared source repository config (EXPERIMENTAL)'),
2267 _(b'edit shared source repository config (EXPERIMENTAL)'),
2269 ),
2268 ),
2270 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2269 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2271 (b'g', b'global', None, _(b'edit global config')),
2270 (b'g', b'global', None, _(b'edit global config')),
2272 ]
2271 ]
2273 + formatteropts,
2272 + formatteropts,
2274 _(b'[-u] [NAME]...'),
2273 _(b'[-u] [NAME]...'),
2275 helpcategory=command.CATEGORY_HELP,
2274 helpcategory=command.CATEGORY_HELP,
2276 optionalrepo=True,
2275 optionalrepo=True,
2277 intents={INTENT_READONLY},
2276 intents={INTENT_READONLY},
2278 )
2277 )
2279 def config(ui, repo, *values, **opts):
2278 def config(ui, repo, *values, **opts):
2280 """show combined config settings from all hgrc files
2279 """show combined config settings from all hgrc files
2281
2280
2282 With no arguments, print names and values of all config items.
2281 With no arguments, print names and values of all config items.
2283
2282
2284 With one argument of the form section.name, print just the value
2283 With one argument of the form section.name, print just the value
2285 of that config item.
2284 of that config item.
2286
2285
2287 With multiple arguments, print names and values of all config
2286 With multiple arguments, print names and values of all config
2288 items with matching section names or section.names.
2287 items with matching section names or section.names.
2289
2288
2290 With --edit, start an editor on the user-level config file. With
2289 With --edit, start an editor on the user-level config file. With
2291 --global, edit the system-wide config file. With --local, edit the
2290 --global, edit the system-wide config file. With --local, edit the
2292 repository-level config file.
2291 repository-level config file.
2293
2292
2294 With --source, the source (filename and line number) is printed
2293 With --source, the source (filename and line number) is printed
2295 for each config item.
2294 for each config item.
2296
2295
2297 See :hg:`help config` for more information about config files.
2296 See :hg:`help config` for more information about config files.
2298
2297
2299 .. container:: verbose
2298 .. container:: verbose
2300
2299
2301 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2300 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2302 This file is not shared across shares when in share-safe mode.
2301 This file is not shared across shares when in share-safe mode.
2303
2302
2304 Template:
2303 Template:
2305
2304
2306 The following keywords are supported. See also :hg:`help templates`.
2305 The following keywords are supported. See also :hg:`help templates`.
2307
2306
2308 :name: String. Config name.
2307 :name: String. Config name.
2309 :source: String. Filename and line number where the item is defined.
2308 :source: String. Filename and line number where the item is defined.
2310 :value: String. Config value.
2309 :value: String. Config value.
2311
2310
2312 The --shared flag can be used to edit the config file of shared source
2311 The --shared flag can be used to edit the config file of shared source
2313 repository. It only works when you have shared using the experimental
2312 repository. It only works when you have shared using the experimental
2314 share safe feature.
2313 share safe feature.
2315
2314
2316 Returns 0 on success, 1 if NAME does not exist.
2315 Returns 0 on success, 1 if NAME does not exist.
2317
2316
2318 """
2317 """
2319
2318
2320 opts = pycompat.byteskwargs(opts)
2319 opts = pycompat.byteskwargs(opts)
2321 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2320 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2322 if any(opts.get(o) for o in editopts):
2321 if any(opts.get(o) for o in editopts):
2323 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2322 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2324 if opts.get(b'local'):
2323 if opts.get(b'local'):
2325 if not repo:
2324 if not repo:
2326 raise error.InputError(
2325 raise error.InputError(
2327 _(b"can't use --local outside a repository")
2326 _(b"can't use --local outside a repository")
2328 )
2327 )
2329 paths = [repo.vfs.join(b'hgrc')]
2328 paths = [repo.vfs.join(b'hgrc')]
2330 elif opts.get(b'global'):
2329 elif opts.get(b'global'):
2331 paths = rcutil.systemrcpath()
2330 paths = rcutil.systemrcpath()
2332 elif opts.get(b'shared'):
2331 elif opts.get(b'shared'):
2333 if not repo.shared():
2332 if not repo.shared():
2334 raise error.InputError(
2333 raise error.InputError(
2335 _(b"repository is not shared; can't use --shared")
2334 _(b"repository is not shared; can't use --shared")
2336 )
2335 )
2337 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2336 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2338 raise error.InputError(
2337 raise error.InputError(
2339 _(
2338 _(
2340 b"share safe feature not enabled; "
2339 b"share safe feature not enabled; "
2341 b"unable to edit shared source repository config"
2340 b"unable to edit shared source repository config"
2342 )
2341 )
2343 )
2342 )
2344 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2343 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2345 elif opts.get(b'non_shared'):
2344 elif opts.get(b'non_shared'):
2346 paths = [repo.vfs.join(b'hgrc-not-shared')]
2345 paths = [repo.vfs.join(b'hgrc-not-shared')]
2347 else:
2346 else:
2348 paths = rcutil.userrcpath()
2347 paths = rcutil.userrcpath()
2349
2348
2350 for f in paths:
2349 for f in paths:
2351 if os.path.exists(f):
2350 if os.path.exists(f):
2352 break
2351 break
2353 else:
2352 else:
2354 if opts.get(b'global'):
2353 if opts.get(b'global'):
2355 samplehgrc = uimod.samplehgrcs[b'global']
2354 samplehgrc = uimod.samplehgrcs[b'global']
2356 elif opts.get(b'local'):
2355 elif opts.get(b'local'):
2357 samplehgrc = uimod.samplehgrcs[b'local']
2356 samplehgrc = uimod.samplehgrcs[b'local']
2358 else:
2357 else:
2359 samplehgrc = uimod.samplehgrcs[b'user']
2358 samplehgrc = uimod.samplehgrcs[b'user']
2360
2359
2361 f = paths[0]
2360 f = paths[0]
2362 fp = open(f, b"wb")
2361 fp = open(f, b"wb")
2363 fp.write(util.tonativeeol(samplehgrc))
2362 fp.write(util.tonativeeol(samplehgrc))
2364 fp.close()
2363 fp.close()
2365
2364
2366 editor = ui.geteditor()
2365 editor = ui.geteditor()
2367 ui.system(
2366 ui.system(
2368 b"%s \"%s\"" % (editor, f),
2367 b"%s \"%s\"" % (editor, f),
2369 onerr=error.InputError,
2368 onerr=error.InputError,
2370 errprefix=_(b"edit failed"),
2369 errprefix=_(b"edit failed"),
2371 blockedtag=b'config_edit',
2370 blockedtag=b'config_edit',
2372 )
2371 )
2373 return
2372 return
2374 ui.pager(b'config')
2373 ui.pager(b'config')
2375 fm = ui.formatter(b'config', opts)
2374 fm = ui.formatter(b'config', opts)
2376 for t, f in rcutil.rccomponents():
2375 for t, f in rcutil.rccomponents():
2377 if t == b'path':
2376 if t == b'path':
2378 ui.debug(b'read config from: %s\n' % f)
2377 ui.debug(b'read config from: %s\n' % f)
2379 elif t == b'resource':
2378 elif t == b'resource':
2380 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2379 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2381 elif t == b'items':
2380 elif t == b'items':
2382 # Don't print anything for 'items'.
2381 # Don't print anything for 'items'.
2383 pass
2382 pass
2384 else:
2383 else:
2385 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2384 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2386 untrusted = bool(opts.get(b'untrusted'))
2385 untrusted = bool(opts.get(b'untrusted'))
2387
2386
2388 selsections = selentries = []
2387 selsections = selentries = []
2389 if values:
2388 if values:
2390 selsections = [v for v in values if b'.' not in v]
2389 selsections = [v for v in values if b'.' not in v]
2391 selentries = [v for v in values if b'.' in v]
2390 selentries = [v for v in values if b'.' in v]
2392 uniquesel = len(selentries) == 1 and not selsections
2391 uniquesel = len(selentries) == 1 and not selsections
2393 selsections = set(selsections)
2392 selsections = set(selsections)
2394 selentries = set(selentries)
2393 selentries = set(selentries)
2395
2394
2396 matched = False
2395 matched = False
2397 all_known = opts[b'exp_all_known']
2396 all_known = opts[b'exp_all_known']
2398 show_source = ui.debugflag or opts.get(b'source')
2397 show_source = ui.debugflag or opts.get(b'source')
2399 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2398 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2400 for section, name, value in entries:
2399 for section, name, value in entries:
2401 source = ui.configsource(section, name, untrusted)
2400 source = ui.configsource(section, name, untrusted)
2402 value = pycompat.bytestr(value)
2401 value = pycompat.bytestr(value)
2403 defaultvalue = ui.configdefault(section, name)
2402 defaultvalue = ui.configdefault(section, name)
2404 if fm.isplain():
2403 if fm.isplain():
2405 source = source or b'none'
2404 source = source or b'none'
2406 value = value.replace(b'\n', b'\\n')
2405 value = value.replace(b'\n', b'\\n')
2407 entryname = section + b'.' + name
2406 entryname = section + b'.' + name
2408 if values and not (section in selsections or entryname in selentries):
2407 if values and not (section in selsections or entryname in selentries):
2409 continue
2408 continue
2410 fm.startitem()
2409 fm.startitem()
2411 fm.condwrite(show_source, b'source', b'%s: ', source)
2410 fm.condwrite(show_source, b'source', b'%s: ', source)
2412 if uniquesel:
2411 if uniquesel:
2413 fm.data(name=entryname)
2412 fm.data(name=entryname)
2414 fm.write(b'value', b'%s\n', value)
2413 fm.write(b'value', b'%s\n', value)
2415 else:
2414 else:
2416 fm.write(b'name value', b'%s=%s\n', entryname, value)
2415 fm.write(b'name value', b'%s=%s\n', entryname, value)
2417 if formatter.isprintable(defaultvalue):
2416 if formatter.isprintable(defaultvalue):
2418 fm.data(defaultvalue=defaultvalue)
2417 fm.data(defaultvalue=defaultvalue)
2419 elif isinstance(defaultvalue, list) and all(
2418 elif isinstance(defaultvalue, list) and all(
2420 formatter.isprintable(e) for e in defaultvalue
2419 formatter.isprintable(e) for e in defaultvalue
2421 ):
2420 ):
2422 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2421 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2423 # TODO: no idea how to process unsupported defaultvalue types
2422 # TODO: no idea how to process unsupported defaultvalue types
2424 matched = True
2423 matched = True
2425 fm.end()
2424 fm.end()
2426 if matched:
2425 if matched:
2427 return 0
2426 return 0
2428 return 1
2427 return 1
2429
2428
2430
2429
2431 @command(
2430 @command(
2432 b'continue',
2431 b'continue',
2433 dryrunopts,
2432 dryrunopts,
2434 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2433 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2435 helpbasic=True,
2434 helpbasic=True,
2436 )
2435 )
2437 def continuecmd(ui, repo, **opts):
2436 def continuecmd(ui, repo, **opts):
2438 """resumes an interrupted operation (EXPERIMENTAL)
2437 """resumes an interrupted operation (EXPERIMENTAL)
2439
2438
2440 Finishes a multistep operation like graft, histedit, rebase, merge,
2439 Finishes a multistep operation like graft, histedit, rebase, merge,
2441 and unshelve if they are in an interrupted state.
2440 and unshelve if they are in an interrupted state.
2442
2441
2443 use --dry-run/-n to dry run the command.
2442 use --dry-run/-n to dry run the command.
2444 """
2443 """
2445 dryrun = opts.get('dry_run')
2444 dryrun = opts.get('dry_run')
2446 contstate = cmdutil.getunfinishedstate(repo)
2445 contstate = cmdutil.getunfinishedstate(repo)
2447 if not contstate:
2446 if not contstate:
2448 raise error.StateError(_(b'no operation in progress'))
2447 raise error.StateError(_(b'no operation in progress'))
2449 if not contstate.continuefunc:
2448 if not contstate.continuefunc:
2450 raise error.StateError(
2449 raise error.StateError(
2451 (
2450 (
2452 _(b"%s in progress but does not support 'hg continue'")
2451 _(b"%s in progress but does not support 'hg continue'")
2453 % (contstate._opname)
2452 % (contstate._opname)
2454 ),
2453 ),
2455 hint=contstate.continuemsg(),
2454 hint=contstate.continuemsg(),
2456 )
2455 )
2457 if dryrun:
2456 if dryrun:
2458 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2457 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2459 return
2458 return
2460 return contstate.continuefunc(ui, repo)
2459 return contstate.continuefunc(ui, repo)
2461
2460
2462
2461
2463 @command(
2462 @command(
2464 b'copy|cp',
2463 b'copy|cp',
2465 [
2464 [
2466 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2465 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2467 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2466 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2468 (
2467 (
2469 b'',
2468 b'',
2470 b'at-rev',
2469 b'at-rev',
2471 b'',
2470 b'',
2472 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2471 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2473 _(b'REV'),
2472 _(b'REV'),
2474 ),
2473 ),
2475 (
2474 (
2476 b'f',
2475 b'f',
2477 b'force',
2476 b'force',
2478 None,
2477 None,
2479 _(b'forcibly copy over an existing managed file'),
2478 _(b'forcibly copy over an existing managed file'),
2480 ),
2479 ),
2481 ]
2480 ]
2482 + walkopts
2481 + walkopts
2483 + dryrunopts,
2482 + dryrunopts,
2484 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2483 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2485 helpcategory=command.CATEGORY_FILE_CONTENTS,
2484 helpcategory=command.CATEGORY_FILE_CONTENTS,
2486 )
2485 )
2487 def copy(ui, repo, *pats, **opts):
2486 def copy(ui, repo, *pats, **opts):
2488 """mark files as copied for the next commit
2487 """mark files as copied for the next commit
2489
2488
2490 Mark dest as having copies of source files. If dest is a
2489 Mark dest as having copies of source files. If dest is a
2491 directory, copies are put in that directory. If dest is a file,
2490 directory, copies are put in that directory. If dest is a file,
2492 the source must be a single file.
2491 the source must be a single file.
2493
2492
2494 By default, this command copies the contents of files as they
2493 By default, this command copies the contents of files as they
2495 exist in the working directory. If invoked with -A/--after, the
2494 exist in the working directory. If invoked with -A/--after, the
2496 operation is recorded, but no copying is performed.
2495 operation is recorded, but no copying is performed.
2497
2496
2498 To undo marking a destination file as copied, use --forget. With that
2497 To undo marking a destination file as copied, use --forget. With that
2499 option, all given (positional) arguments are unmarked as copies. The
2498 option, all given (positional) arguments are unmarked as copies. The
2500 destination file(s) will be left in place (still tracked). Note that
2499 destination file(s) will be left in place (still tracked). Note that
2501 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2500 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2502
2501
2503 This command takes effect with the next commit by default.
2502 This command takes effect with the next commit by default.
2504
2503
2505 Returns 0 on success, 1 if errors are encountered.
2504 Returns 0 on success, 1 if errors are encountered.
2506 """
2505 """
2507 opts = pycompat.byteskwargs(opts)
2506 opts = pycompat.byteskwargs(opts)
2508
2507
2509 context = lambda repo: repo.dirstate.changing_files(repo)
2508 context = lambda repo: repo.dirstate.changing_files(repo)
2510 rev = opts.get(b'at_rev')
2509 rev = opts.get(b'at_rev')
2511 ctx = None
2510 ctx = None
2512 if rev:
2511 if rev:
2513 ctx = logcmdutil.revsingle(repo, rev)
2512 ctx = logcmdutil.revsingle(repo, rev)
2514 if ctx.rev() is not None:
2513 if ctx.rev() is not None:
2515
2514
2516 def context(repo):
2515 def context(repo):
2517 return util.nullcontextmanager()
2516 return util.nullcontextmanager()
2518
2517
2519 opts[b'at_rev'] = ctx.rev()
2518 opts[b'at_rev'] = ctx.rev()
2520 with repo.wlock(), context(repo):
2519 with repo.wlock(), context(repo):
2521 return cmdutil.copy(ui, repo, pats, opts)
2520 return cmdutil.copy(ui, repo, pats, opts)
2522
2521
2523
2522
2524 @command(
2523 @command(
2525 b'debugcommands',
2524 b'debugcommands',
2526 [],
2525 [],
2527 _(b'[COMMAND]'),
2526 _(b'[COMMAND]'),
2528 helpcategory=command.CATEGORY_HELP,
2527 helpcategory=command.CATEGORY_HELP,
2529 norepo=True,
2528 norepo=True,
2530 )
2529 )
2531 def debugcommands(ui, cmd=b'', *args):
2530 def debugcommands(ui, cmd=b'', *args):
2532 """list all available commands and options"""
2531 """list all available commands and options"""
2533 for cmd, vals in sorted(table.items()):
2532 for cmd, vals in sorted(table.items()):
2534 cmd = cmd.split(b'|')[0]
2533 cmd = cmd.split(b'|')[0]
2535 opts = b', '.join([i[1] for i in vals[1]])
2534 opts = b', '.join([i[1] for i in vals[1]])
2536 ui.write(b'%s: %s\n' % (cmd, opts))
2535 ui.write(b'%s: %s\n' % (cmd, opts))
2537
2536
2538
2537
2539 @command(
2538 @command(
2540 b'debugcomplete',
2539 b'debugcomplete',
2541 [(b'o', b'options', None, _(b'show the command options'))],
2540 [(b'o', b'options', None, _(b'show the command options'))],
2542 _(b'[-o] CMD'),
2541 _(b'[-o] CMD'),
2543 helpcategory=command.CATEGORY_HELP,
2542 helpcategory=command.CATEGORY_HELP,
2544 norepo=True,
2543 norepo=True,
2545 )
2544 )
2546 def debugcomplete(ui, cmd=b'', **opts):
2545 def debugcomplete(ui, cmd=b'', **opts):
2547 """returns the completion list associated with the given command"""
2546 """returns the completion list associated with the given command"""
2548
2547
2549 if opts.get('options'):
2548 if opts.get('options'):
2550 options = []
2549 options = []
2551 otables = [globalopts]
2550 otables = [globalopts]
2552 if cmd:
2551 if cmd:
2553 aliases, entry = cmdutil.findcmd(cmd, table, False)
2552 aliases, entry = cmdutil.findcmd(cmd, table, False)
2554 otables.append(entry[1])
2553 otables.append(entry[1])
2555 for t in otables:
2554 for t in otables:
2556 for o in t:
2555 for o in t:
2557 if b"(DEPRECATED)" in o[3]:
2556 if b"(DEPRECATED)" in o[3]:
2558 continue
2557 continue
2559 if o[0]:
2558 if o[0]:
2560 options.append(b'-%s' % o[0])
2559 options.append(b'-%s' % o[0])
2561 options.append(b'--%s' % o[1])
2560 options.append(b'--%s' % o[1])
2562 ui.write(b"%s\n" % b"\n".join(options))
2561 ui.write(b"%s\n" % b"\n".join(options))
2563 return
2562 return
2564
2563
2565 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2564 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2566 if ui.verbose:
2565 if ui.verbose:
2567 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2566 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2568 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2567 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2569
2568
2570
2569
2571 @command(
2570 @command(
2572 b'diff',
2571 b'diff',
2573 [
2572 [
2574 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2573 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2575 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2574 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2576 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2575 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2577 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2576 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2578 ]
2577 ]
2579 + diffopts
2578 + diffopts
2580 + diffopts2
2579 + diffopts2
2581 + walkopts
2580 + walkopts
2582 + subrepoopts,
2581 + subrepoopts,
2583 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2582 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2584 helpcategory=command.CATEGORY_FILE_CONTENTS,
2583 helpcategory=command.CATEGORY_FILE_CONTENTS,
2585 helpbasic=True,
2584 helpbasic=True,
2586 inferrepo=True,
2585 inferrepo=True,
2587 intents={INTENT_READONLY},
2586 intents={INTENT_READONLY},
2588 )
2587 )
2589 def diff(ui, repo, *pats, **opts):
2588 def diff(ui, repo, *pats, **opts):
2590 """diff repository (or selected files)
2589 """diff repository (or selected files)
2591
2590
2592 Show differences between revisions for the specified files.
2591 Show differences between revisions for the specified files.
2593
2592
2594 Differences between files are shown using the unified diff format.
2593 Differences between files are shown using the unified diff format.
2595
2594
2596 .. note::
2595 .. note::
2597
2596
2598 :hg:`diff` may generate unexpected results for merges, as it will
2597 :hg:`diff` may generate unexpected results for merges, as it will
2599 default to comparing against the working directory's first
2598 default to comparing against the working directory's first
2600 parent changeset if no revisions are specified. To diff against the
2599 parent changeset if no revisions are specified. To diff against the
2601 conflict regions, you can use `--config diff.merge=yes`.
2600 conflict regions, you can use `--config diff.merge=yes`.
2602
2601
2603 By default, the working directory files are compared to its first parent. To
2602 By default, the working directory files are compared to its first parent. To
2604 see the differences from another revision, use --from. To see the difference
2603 see the differences from another revision, use --from. To see the difference
2605 to another revision, use --to. For example, :hg:`diff --from .^` will show
2604 to another revision, use --to. For example, :hg:`diff --from .^` will show
2606 the differences from the working copy's grandparent to the working copy,
2605 the differences from the working copy's grandparent to the working copy,
2607 :hg:`diff --to .` will show the diff from the working copy to its parent
2606 :hg:`diff --to .` will show the diff from the working copy to its parent
2608 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2607 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2609 show the diff between those two revisions.
2608 show the diff between those two revisions.
2610
2609
2611 Alternatively you can specify -c/--change with a revision to see the changes
2610 Alternatively you can specify -c/--change with a revision to see the changes
2612 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2611 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2613 equivalent to :hg:`diff --from 42^ --to 42`)
2612 equivalent to :hg:`diff --from 42^ --to 42`)
2614
2613
2615 Without the -a/--text option, diff will avoid generating diffs of
2614 Without the -a/--text option, diff will avoid generating diffs of
2616 files it detects as binary. With -a, diff will generate a diff
2615 files it detects as binary. With -a, diff will generate a diff
2617 anyway, probably with undesirable results.
2616 anyway, probably with undesirable results.
2618
2617
2619 Use the -g/--git option to generate diffs in the git extended diff
2618 Use the -g/--git option to generate diffs in the git extended diff
2620 format. For more information, read :hg:`help diffs`.
2619 format. For more information, read :hg:`help diffs`.
2621
2620
2622 .. container:: verbose
2621 .. container:: verbose
2623
2622
2624 Examples:
2623 Examples:
2625
2624
2626 - compare a file in the current working directory to its parent::
2625 - compare a file in the current working directory to its parent::
2627
2626
2628 hg diff foo.c
2627 hg diff foo.c
2629
2628
2630 - compare two historical versions of a directory, with rename info::
2629 - compare two historical versions of a directory, with rename info::
2631
2630
2632 hg diff --git --from 1.0 --to 1.2 lib/
2631 hg diff --git --from 1.0 --to 1.2 lib/
2633
2632
2634 - get change stats relative to the last change on some date::
2633 - get change stats relative to the last change on some date::
2635
2634
2636 hg diff --stat --from "date('may 2')"
2635 hg diff --stat --from "date('may 2')"
2637
2636
2638 - diff all newly-added files that contain a keyword::
2637 - diff all newly-added files that contain a keyword::
2639
2638
2640 hg diff "set:added() and grep(GNU)"
2639 hg diff "set:added() and grep(GNU)"
2641
2640
2642 - compare a revision and its parents::
2641 - compare a revision and its parents::
2643
2642
2644 hg diff -c 9353 # compare against first parent
2643 hg diff -c 9353 # compare against first parent
2645 hg diff --from 9353^ --to 9353 # same using revset syntax
2644 hg diff --from 9353^ --to 9353 # same using revset syntax
2646 hg diff --from 9353^2 --to 9353 # compare against the second parent
2645 hg diff --from 9353^2 --to 9353 # compare against the second parent
2647
2646
2648 Returns 0 on success.
2647 Returns 0 on success.
2649 """
2648 """
2650
2649
2651 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2650 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2652 opts = pycompat.byteskwargs(opts)
2651 opts = pycompat.byteskwargs(opts)
2653 revs = opts.get(b'rev')
2652 revs = opts.get(b'rev')
2654 change = opts.get(b'change')
2653 change = opts.get(b'change')
2655 from_rev = opts.get(b'from')
2654 from_rev = opts.get(b'from')
2656 to_rev = opts.get(b'to')
2655 to_rev = opts.get(b'to')
2657 stat = opts.get(b'stat')
2656 stat = opts.get(b'stat')
2658 reverse = opts.get(b'reverse')
2657 reverse = opts.get(b'reverse')
2659
2658
2660 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2659 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2661 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2660 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2662 if change:
2661 if change:
2663 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2662 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2664 ctx2 = logcmdutil.revsingle(repo, change, None)
2663 ctx2 = logcmdutil.revsingle(repo, change, None)
2665 ctx1 = logcmdutil.diff_parent(ctx2)
2664 ctx1 = logcmdutil.diff_parent(ctx2)
2666 elif from_rev or to_rev:
2665 elif from_rev or to_rev:
2667 repo = scmutil.unhidehashlikerevs(
2666 repo = scmutil.unhidehashlikerevs(
2668 repo, [from_rev] + [to_rev], b'nowarn'
2667 repo, [from_rev] + [to_rev], b'nowarn'
2669 )
2668 )
2670 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2669 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2671 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2670 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2672 else:
2671 else:
2673 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2672 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2674 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2673 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2675
2674
2676 if reverse:
2675 if reverse:
2677 ctxleft = ctx2
2676 ctxleft = ctx2
2678 ctxright = ctx1
2677 ctxright = ctx1
2679 else:
2678 else:
2680 ctxleft = ctx1
2679 ctxleft = ctx1
2681 ctxright = ctx2
2680 ctxright = ctx2
2682
2681
2683 diffopts = patch.diffallopts(ui, opts)
2682 diffopts = patch.diffallopts(ui, opts)
2684 m = scmutil.match(ctx2, pats, opts)
2683 m = scmutil.match(ctx2, pats, opts)
2685 m = repo.narrowmatch(m)
2684 m = repo.narrowmatch(m)
2686 ui.pager(b'diff')
2685 ui.pager(b'diff')
2687 logcmdutil.diffordiffstat(
2686 logcmdutil.diffordiffstat(
2688 ui,
2687 ui,
2689 repo,
2688 repo,
2690 diffopts,
2689 diffopts,
2691 ctxleft,
2690 ctxleft,
2692 ctxright,
2691 ctxright,
2693 m,
2692 m,
2694 stat=stat,
2693 stat=stat,
2695 listsubrepos=opts.get(b'subrepos'),
2694 listsubrepos=opts.get(b'subrepos'),
2696 root=opts.get(b'root'),
2695 root=opts.get(b'root'),
2697 )
2696 )
2698
2697
2699
2698
2700 @command(
2699 @command(
2701 b'export',
2700 b'export',
2702 [
2701 [
2703 (
2702 (
2704 b'B',
2703 b'B',
2705 b'bookmark',
2704 b'bookmark',
2706 b'',
2705 b'',
2707 _(b'export changes only reachable by given bookmark'),
2706 _(b'export changes only reachable by given bookmark'),
2708 _(b'BOOKMARK'),
2707 _(b'BOOKMARK'),
2709 ),
2708 ),
2710 (
2709 (
2711 b'o',
2710 b'o',
2712 b'output',
2711 b'output',
2713 b'',
2712 b'',
2714 _(b'print output to file with formatted name'),
2713 _(b'print output to file with formatted name'),
2715 _(b'FORMAT'),
2714 _(b'FORMAT'),
2716 ),
2715 ),
2717 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2716 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2718 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2717 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2719 ]
2718 ]
2720 + diffopts
2719 + diffopts
2721 + formatteropts,
2720 + formatteropts,
2722 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2721 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2723 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2722 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2724 helpbasic=True,
2723 helpbasic=True,
2725 intents={INTENT_READONLY},
2724 intents={INTENT_READONLY},
2726 )
2725 )
2727 def export(ui, repo, *changesets, **opts):
2726 def export(ui, repo, *changesets, **opts):
2728 """dump the header and diffs for one or more changesets
2727 """dump the header and diffs for one or more changesets
2729
2728
2730 Print the changeset header and diffs for one or more revisions.
2729 Print the changeset header and diffs for one or more revisions.
2731 If no revision is given, the parent of the working directory is used.
2730 If no revision is given, the parent of the working directory is used.
2732
2731
2733 The information shown in the changeset header is: author, date,
2732 The information shown in the changeset header is: author, date,
2734 branch name (if non-default), changeset hash, parent(s) and commit
2733 branch name (if non-default), changeset hash, parent(s) and commit
2735 comment.
2734 comment.
2736
2735
2737 .. note::
2736 .. note::
2738
2737
2739 :hg:`export` may generate unexpected diff output for merge
2738 :hg:`export` may generate unexpected diff output for merge
2740 changesets, as it will compare the merge changeset against its
2739 changesets, as it will compare the merge changeset against its
2741 first parent only.
2740 first parent only.
2742
2741
2743 Output may be to a file, in which case the name of the file is
2742 Output may be to a file, in which case the name of the file is
2744 given using a template string. See :hg:`help templates`. In addition
2743 given using a template string. See :hg:`help templates`. In addition
2745 to the common template keywords, the following formatting rules are
2744 to the common template keywords, the following formatting rules are
2746 supported:
2745 supported:
2747
2746
2748 :``%%``: literal "%" character
2747 :``%%``: literal "%" character
2749 :``%H``: changeset hash (40 hexadecimal digits)
2748 :``%H``: changeset hash (40 hexadecimal digits)
2750 :``%N``: number of patches being generated
2749 :``%N``: number of patches being generated
2751 :``%R``: changeset revision number
2750 :``%R``: changeset revision number
2752 :``%b``: basename of the exporting repository
2751 :``%b``: basename of the exporting repository
2753 :``%h``: short-form changeset hash (12 hexadecimal digits)
2752 :``%h``: short-form changeset hash (12 hexadecimal digits)
2754 :``%m``: first line of the commit message (only alphanumeric characters)
2753 :``%m``: first line of the commit message (only alphanumeric characters)
2755 :``%n``: zero-padded sequence number, starting at 1
2754 :``%n``: zero-padded sequence number, starting at 1
2756 :``%r``: zero-padded changeset revision number
2755 :``%r``: zero-padded changeset revision number
2757 :``\\``: literal "\\" character
2756 :``\\``: literal "\\" character
2758
2757
2759 Without the -a/--text option, export will avoid generating diffs
2758 Without the -a/--text option, export will avoid generating diffs
2760 of files it detects as binary. With -a, export will generate a
2759 of files it detects as binary. With -a, export will generate a
2761 diff anyway, probably with undesirable results.
2760 diff anyway, probably with undesirable results.
2762
2761
2763 With -B/--bookmark changesets reachable by the given bookmark are
2762 With -B/--bookmark changesets reachable by the given bookmark are
2764 selected.
2763 selected.
2765
2764
2766 Use the -g/--git option to generate diffs in the git extended diff
2765 Use the -g/--git option to generate diffs in the git extended diff
2767 format. See :hg:`help diffs` for more information.
2766 format. See :hg:`help diffs` for more information.
2768
2767
2769 With the --switch-parent option, the diff will be against the
2768 With the --switch-parent option, the diff will be against the
2770 second parent. It can be useful to review a merge.
2769 second parent. It can be useful to review a merge.
2771
2770
2772 .. container:: verbose
2771 .. container:: verbose
2773
2772
2774 Template:
2773 Template:
2775
2774
2776 The following keywords are supported in addition to the common template
2775 The following keywords are supported in addition to the common template
2777 keywords and functions. See also :hg:`help templates`.
2776 keywords and functions. See also :hg:`help templates`.
2778
2777
2779 :diff: String. Diff content.
2778 :diff: String. Diff content.
2780 :parents: List of strings. Parent nodes of the changeset.
2779 :parents: List of strings. Parent nodes of the changeset.
2781
2780
2782 Examples:
2781 Examples:
2783
2782
2784 - use export and import to transplant a bugfix to the current
2783 - use export and import to transplant a bugfix to the current
2785 branch::
2784 branch::
2786
2785
2787 hg export -r 9353 | hg import -
2786 hg export -r 9353 | hg import -
2788
2787
2789 - export all the changesets between two revisions to a file with
2788 - export all the changesets between two revisions to a file with
2790 rename information::
2789 rename information::
2791
2790
2792 hg export --git -r 123:150 > changes.txt
2791 hg export --git -r 123:150 > changes.txt
2793
2792
2794 - split outgoing changes into a series of patches with
2793 - split outgoing changes into a series of patches with
2795 descriptive names::
2794 descriptive names::
2796
2795
2797 hg export -r "outgoing()" -o "%n-%m.patch"
2796 hg export -r "outgoing()" -o "%n-%m.patch"
2798
2797
2799 Returns 0 on success.
2798 Returns 0 on success.
2800 """
2799 """
2801 opts = pycompat.byteskwargs(opts)
2800 opts = pycompat.byteskwargs(opts)
2802 bookmark = opts.get(b'bookmark')
2801 bookmark = opts.get(b'bookmark')
2803 changesets += tuple(opts.get(b'rev', []))
2802 changesets += tuple(opts.get(b'rev', []))
2804
2803
2805 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2804 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2806
2805
2807 if bookmark:
2806 if bookmark:
2808 if bookmark not in repo._bookmarks:
2807 if bookmark not in repo._bookmarks:
2809 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2808 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2810
2809
2811 revs = scmutil.bookmarkrevs(repo, bookmark)
2810 revs = scmutil.bookmarkrevs(repo, bookmark)
2812 else:
2811 else:
2813 if not changesets:
2812 if not changesets:
2814 changesets = [b'.']
2813 changesets = [b'.']
2815
2814
2816 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2815 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2817 revs = logcmdutil.revrange(repo, changesets)
2816 revs = logcmdutil.revrange(repo, changesets)
2818
2817
2819 if not revs:
2818 if not revs:
2820 raise error.InputError(_(b"export requires at least one changeset"))
2819 raise error.InputError(_(b"export requires at least one changeset"))
2821 if len(revs) > 1:
2820 if len(revs) > 1:
2822 ui.note(_(b'exporting patches:\n'))
2821 ui.note(_(b'exporting patches:\n'))
2823 else:
2822 else:
2824 ui.note(_(b'exporting patch:\n'))
2823 ui.note(_(b'exporting patch:\n'))
2825
2824
2826 fntemplate = opts.get(b'output')
2825 fntemplate = opts.get(b'output')
2827 if cmdutil.isstdiofilename(fntemplate):
2826 if cmdutil.isstdiofilename(fntemplate):
2828 fntemplate = b''
2827 fntemplate = b''
2829
2828
2830 if fntemplate:
2829 if fntemplate:
2831 fm = formatter.nullformatter(ui, b'export', opts)
2830 fm = formatter.nullformatter(ui, b'export', opts)
2832 else:
2831 else:
2833 ui.pager(b'export')
2832 ui.pager(b'export')
2834 fm = ui.formatter(b'export', opts)
2833 fm = ui.formatter(b'export', opts)
2835 with fm:
2834 with fm:
2836 cmdutil.export(
2835 cmdutil.export(
2837 repo,
2836 repo,
2838 revs,
2837 revs,
2839 fm,
2838 fm,
2840 fntemplate=fntemplate,
2839 fntemplate=fntemplate,
2841 switch_parent=opts.get(b'switch_parent'),
2840 switch_parent=opts.get(b'switch_parent'),
2842 opts=patch.diffallopts(ui, opts),
2841 opts=patch.diffallopts(ui, opts),
2843 )
2842 )
2844
2843
2845
2844
2846 @command(
2845 @command(
2847 b'files',
2846 b'files',
2848 [
2847 [
2849 (
2848 (
2850 b'r',
2849 b'r',
2851 b'rev',
2850 b'rev',
2852 b'',
2851 b'',
2853 _(b'search the repository as it is in REV'),
2852 _(b'search the repository as it is in REV'),
2854 _(b'REV'),
2853 _(b'REV'),
2855 ),
2854 ),
2856 (
2855 (
2857 b'0',
2856 b'0',
2858 b'print0',
2857 b'print0',
2859 None,
2858 None,
2860 _(b'end filenames with NUL, for use with xargs'),
2859 _(b'end filenames with NUL, for use with xargs'),
2861 ),
2860 ),
2862 ]
2861 ]
2863 + walkopts
2862 + walkopts
2864 + formatteropts
2863 + formatteropts
2865 + subrepoopts,
2864 + subrepoopts,
2866 _(b'[OPTION]... [FILE]...'),
2865 _(b'[OPTION]... [FILE]...'),
2867 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2866 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2868 intents={INTENT_READONLY},
2867 intents={INTENT_READONLY},
2869 )
2868 )
2870 def files(ui, repo, *pats, **opts):
2869 def files(ui, repo, *pats, **opts):
2871 """list tracked files
2870 """list tracked files
2872
2871
2873 Print files under Mercurial control in the working directory or
2872 Print files under Mercurial control in the working directory or
2874 specified revision for given files (excluding removed files).
2873 specified revision for given files (excluding removed files).
2875 Files can be specified as filenames or filesets.
2874 Files can be specified as filenames or filesets.
2876
2875
2877 If no files are given to match, this command prints the names
2876 If no files are given to match, this command prints the names
2878 of all files under Mercurial control.
2877 of all files under Mercurial control.
2879
2878
2880 .. container:: verbose
2879 .. container:: verbose
2881
2880
2882 Template:
2881 Template:
2883
2882
2884 The following keywords are supported in addition to the common template
2883 The following keywords are supported in addition to the common template
2885 keywords and functions. See also :hg:`help templates`.
2884 keywords and functions. See also :hg:`help templates`.
2886
2885
2887 :flags: String. Character denoting file's symlink and executable bits.
2886 :flags: String. Character denoting file's symlink and executable bits.
2888 :path: String. Repository-absolute path of the file.
2887 :path: String. Repository-absolute path of the file.
2889 :size: Integer. Size of the file in bytes.
2888 :size: Integer. Size of the file in bytes.
2890
2889
2891 Examples:
2890 Examples:
2892
2891
2893 - list all files under the current directory::
2892 - list all files under the current directory::
2894
2893
2895 hg files .
2894 hg files .
2896
2895
2897 - shows sizes and flags for current revision::
2896 - shows sizes and flags for current revision::
2898
2897
2899 hg files -vr .
2898 hg files -vr .
2900
2899
2901 - list all files named README::
2900 - list all files named README::
2902
2901
2903 hg files -I "**/README"
2902 hg files -I "**/README"
2904
2903
2905 - list all binary files::
2904 - list all binary files::
2906
2905
2907 hg files "set:binary()"
2906 hg files "set:binary()"
2908
2907
2909 - find files containing a regular expression::
2908 - find files containing a regular expression::
2910
2909
2911 hg files "set:grep('bob')"
2910 hg files "set:grep('bob')"
2912
2911
2913 - search tracked file contents with xargs and grep::
2912 - search tracked file contents with xargs and grep::
2914
2913
2915 hg files -0 | xargs -0 grep foo
2914 hg files -0 | xargs -0 grep foo
2916
2915
2917 See :hg:`help patterns` and :hg:`help filesets` for more information
2916 See :hg:`help patterns` and :hg:`help filesets` for more information
2918 on specifying file patterns.
2917 on specifying file patterns.
2919
2918
2920 Returns 0 if a match is found, 1 otherwise.
2919 Returns 0 if a match is found, 1 otherwise.
2921
2920
2922 """
2921 """
2923
2922
2924 opts = pycompat.byteskwargs(opts)
2923 opts = pycompat.byteskwargs(opts)
2925 rev = opts.get(b'rev')
2924 rev = opts.get(b'rev')
2926 if rev:
2925 if rev:
2927 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2926 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2928 ctx = logcmdutil.revsingle(repo, rev, None)
2927 ctx = logcmdutil.revsingle(repo, rev, None)
2929
2928
2930 end = b'\n'
2929 end = b'\n'
2931 if opts.get(b'print0'):
2930 if opts.get(b'print0'):
2932 end = b'\0'
2931 end = b'\0'
2933 fmt = b'%s' + end
2932 fmt = b'%s' + end
2934
2933
2935 m = scmutil.match(ctx, pats, opts)
2934 m = scmutil.match(ctx, pats, opts)
2936 ui.pager(b'files')
2935 ui.pager(b'files')
2937 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2936 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2938 with ui.formatter(b'files', opts) as fm:
2937 with ui.formatter(b'files', opts) as fm:
2939 return cmdutil.files(
2938 return cmdutil.files(
2940 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2939 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2941 )
2940 )
2942
2941
2943
2942
2944 @command(
2943 @command(
2945 b'forget',
2944 b'forget',
2946 [
2945 [
2947 (b'i', b'interactive', None, _(b'use interactive mode')),
2946 (b'i', b'interactive', None, _(b'use interactive mode')),
2948 ]
2947 ]
2949 + walkopts
2948 + walkopts
2950 + dryrunopts,
2949 + dryrunopts,
2951 _(b'[OPTION]... FILE...'),
2950 _(b'[OPTION]... FILE...'),
2952 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2951 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2953 helpbasic=True,
2952 helpbasic=True,
2954 inferrepo=True,
2953 inferrepo=True,
2955 )
2954 )
2956 def forget(ui, repo, *pats, **opts):
2955 def forget(ui, repo, *pats, **opts):
2957 """forget the specified files on the next commit
2956 """forget the specified files on the next commit
2958
2957
2959 Mark the specified files so they will no longer be tracked
2958 Mark the specified files so they will no longer be tracked
2960 after the next commit.
2959 after the next commit.
2961
2960
2962 This only removes files from the current branch, not from the
2961 This only removes files from the current branch, not from the
2963 entire project history, and it does not delete them from the
2962 entire project history, and it does not delete them from the
2964 working directory.
2963 working directory.
2965
2964
2966 To delete the file from the working directory, see :hg:`remove`.
2965 To delete the file from the working directory, see :hg:`remove`.
2967
2966
2968 To undo a forget before the next commit, see :hg:`add`.
2967 To undo a forget before the next commit, see :hg:`add`.
2969
2968
2970 .. container:: verbose
2969 .. container:: verbose
2971
2970
2972 Examples:
2971 Examples:
2973
2972
2974 - forget newly-added binary files::
2973 - forget newly-added binary files::
2975
2974
2976 hg forget "set:added() and binary()"
2975 hg forget "set:added() and binary()"
2977
2976
2978 - forget files that would be excluded by .hgignore::
2977 - forget files that would be excluded by .hgignore::
2979
2978
2980 hg forget "set:hgignore()"
2979 hg forget "set:hgignore()"
2981
2980
2982 Returns 0 on success.
2981 Returns 0 on success.
2983 """
2982 """
2984
2983
2985 opts = pycompat.byteskwargs(opts)
2984 opts = pycompat.byteskwargs(opts)
2986 if not pats:
2985 if not pats:
2987 raise error.InputError(_(b'no files specified'))
2986 raise error.InputError(_(b'no files specified'))
2988
2987
2989 with repo.wlock(), repo.dirstate.changing_files(repo):
2988 with repo.wlock(), repo.dirstate.changing_files(repo):
2990 m = scmutil.match(repo[None], pats, opts)
2989 m = scmutil.match(repo[None], pats, opts)
2991 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2990 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2992 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2991 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2993 rejected = cmdutil.forget(
2992 rejected = cmdutil.forget(
2994 ui,
2993 ui,
2995 repo,
2994 repo,
2996 m,
2995 m,
2997 prefix=b"",
2996 prefix=b"",
2998 uipathfn=uipathfn,
2997 uipathfn=uipathfn,
2999 explicitonly=False,
2998 explicitonly=False,
3000 dryrun=dryrun,
2999 dryrun=dryrun,
3001 interactive=interactive,
3000 interactive=interactive,
3002 )[0]
3001 )[0]
3003 return rejected and 1 or 0
3002 return rejected and 1 or 0
3004
3003
3005
3004
3006 @command(
3005 @command(
3007 b'graft',
3006 b'graft',
3008 [
3007 [
3009 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
3008 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
3010 (
3009 (
3011 b'',
3010 b'',
3012 b'base',
3011 b'base',
3013 b'',
3012 b'',
3014 _(b'base revision when doing the graft merge (ADVANCED)'),
3013 _(b'base revision when doing the graft merge (ADVANCED)'),
3015 _(b'REV'),
3014 _(b'REV'),
3016 ),
3015 ),
3017 (b'c', b'continue', False, _(b'resume interrupted graft')),
3016 (b'c', b'continue', False, _(b'resume interrupted graft')),
3018 (b'', b'stop', False, _(b'stop interrupted graft')),
3017 (b'', b'stop', False, _(b'stop interrupted graft')),
3019 (b'', b'abort', False, _(b'abort interrupted graft')),
3018 (b'', b'abort', False, _(b'abort interrupted graft')),
3020 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
3019 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
3021 (b'', b'log', None, _(b'append graft info to log message')),
3020 (b'', b'log', None, _(b'append graft info to log message')),
3022 (
3021 (
3023 b'',
3022 b'',
3024 b'no-commit',
3023 b'no-commit',
3025 None,
3024 None,
3026 _(b"don't commit, just apply the changes in working directory"),
3025 _(b"don't commit, just apply the changes in working directory"),
3027 ),
3026 ),
3028 (b'f', b'force', False, _(b'force graft')),
3027 (b'f', b'force', False, _(b'force graft')),
3029 (
3028 (
3030 b'D',
3029 b'D',
3031 b'currentdate',
3030 b'currentdate',
3032 False,
3031 False,
3033 _(b'record the current date as commit date'),
3032 _(b'record the current date as commit date'),
3034 ),
3033 ),
3035 (
3034 (
3036 b'U',
3035 b'U',
3037 b'currentuser',
3036 b'currentuser',
3038 False,
3037 False,
3039 _(b'record the current user as committer'),
3038 _(b'record the current user as committer'),
3040 ),
3039 ),
3041 ]
3040 ]
3042 + commitopts2
3041 + commitopts2
3043 + mergetoolopts
3042 + mergetoolopts
3044 + dryrunopts,
3043 + dryrunopts,
3045 _(b'[OPTION]... [-r REV]... REV...'),
3044 _(b'[OPTION]... [-r REV]... REV...'),
3046 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3045 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3047 )
3046 )
3048 def graft(ui, repo, *revs, **opts):
3047 def graft(ui, repo, *revs, **opts):
3049 """copy changes from other branches onto the current branch
3048 """copy changes from other branches onto the current branch
3050
3049
3051 This command uses Mercurial's merge logic to copy individual
3050 This command uses Mercurial's merge logic to copy individual
3052 changes from other branches without merging branches in the
3051 changes from other branches without merging branches in the
3053 history graph. This is sometimes known as 'backporting' or
3052 history graph. This is sometimes known as 'backporting' or
3054 'cherry-picking'. By default, graft will copy user, date, and
3053 'cherry-picking'. By default, graft will copy user, date, and
3055 description from the source changesets.
3054 description from the source changesets.
3056
3055
3057 Changesets that are ancestors of the current revision, that have
3056 Changesets that are ancestors of the current revision, that have
3058 already been grafted, or that are merges will be skipped.
3057 already been grafted, or that are merges will be skipped.
3059
3058
3060 If --log is specified, log messages will have a comment appended
3059 If --log is specified, log messages will have a comment appended
3061 of the form::
3060 of the form::
3062
3061
3063 (grafted from CHANGESETHASH)
3062 (grafted from CHANGESETHASH)
3064
3063
3065 If --force is specified, revisions will be grafted even if they
3064 If --force is specified, revisions will be grafted even if they
3066 are already ancestors of, or have been grafted to, the destination.
3065 are already ancestors of, or have been grafted to, the destination.
3067 This is useful when the revisions have since been backed out.
3066 This is useful when the revisions have since been backed out.
3068
3067
3069 If a graft merge results in conflicts, the graft process is
3068 If a graft merge results in conflicts, the graft process is
3070 interrupted so that the current merge can be manually resolved.
3069 interrupted so that the current merge can be manually resolved.
3071 Once all conflicts are addressed, the graft process can be
3070 Once all conflicts are addressed, the graft process can be
3072 continued with the -c/--continue option.
3071 continued with the -c/--continue option.
3073
3072
3074 The -c/--continue option reapplies all the earlier options.
3073 The -c/--continue option reapplies all the earlier options.
3075
3074
3076 .. container:: verbose
3075 .. container:: verbose
3077
3076
3078 The --base option exposes more of how graft internally uses merge with a
3077 The --base option exposes more of how graft internally uses merge with a
3079 custom base revision. --base can be used to specify another ancestor than
3078 custom base revision. --base can be used to specify another ancestor than
3080 the first and only parent.
3079 the first and only parent.
3081
3080
3082 The command::
3081 The command::
3083
3082
3084 hg graft -r 345 --base 234
3083 hg graft -r 345 --base 234
3085
3084
3086 is thus pretty much the same as::
3085 is thus pretty much the same as::
3087
3086
3088 hg diff --from 234 --to 345 | hg import
3087 hg diff --from 234 --to 345 | hg import
3089
3088
3090 but using merge to resolve conflicts and track moved files.
3089 but using merge to resolve conflicts and track moved files.
3091
3090
3092 The result of a merge can thus be backported as a single commit by
3091 The result of a merge can thus be backported as a single commit by
3093 specifying one of the merge parents as base, and thus effectively
3092 specifying one of the merge parents as base, and thus effectively
3094 grafting the changes from the other side.
3093 grafting the changes from the other side.
3095
3094
3096 It is also possible to collapse multiple changesets and clean up history
3095 It is also possible to collapse multiple changesets and clean up history
3097 by specifying another ancestor as base, much like rebase --collapse
3096 by specifying another ancestor as base, much like rebase --collapse
3098 --keep.
3097 --keep.
3099
3098
3100 The commit message can be tweaked after the fact using commit --amend .
3099 The commit message can be tweaked after the fact using commit --amend .
3101
3100
3102 For using non-ancestors as the base to backout changes, see the backout
3101 For using non-ancestors as the base to backout changes, see the backout
3103 command and the hidden --parent option.
3102 command and the hidden --parent option.
3104
3103
3105 .. container:: verbose
3104 .. container:: verbose
3106
3105
3107 Examples:
3106 Examples:
3108
3107
3109 - copy a single change to the stable branch and edit its description::
3108 - copy a single change to the stable branch and edit its description::
3110
3109
3111 hg update stable
3110 hg update stable
3112 hg graft --edit 9393
3111 hg graft --edit 9393
3113
3112
3114 - graft a range of changesets with one exception, updating dates::
3113 - graft a range of changesets with one exception, updating dates::
3115
3114
3116 hg graft -D "2085::2093 and not 2091"
3115 hg graft -D "2085::2093 and not 2091"
3117
3116
3118 - continue a graft after resolving conflicts::
3117 - continue a graft after resolving conflicts::
3119
3118
3120 hg graft -c
3119 hg graft -c
3121
3120
3122 - show the source of a grafted changeset::
3121 - show the source of a grafted changeset::
3123
3122
3124 hg log --debug -r .
3123 hg log --debug -r .
3125
3124
3126 - show revisions sorted by date::
3125 - show revisions sorted by date::
3127
3126
3128 hg log -r "sort(all(), date)"
3127 hg log -r "sort(all(), date)"
3129
3128
3130 - backport the result of a merge as a single commit::
3129 - backport the result of a merge as a single commit::
3131
3130
3132 hg graft -r 123 --base 123^
3131 hg graft -r 123 --base 123^
3133
3132
3134 - land a feature branch as one changeset::
3133 - land a feature branch as one changeset::
3135
3134
3136 hg up -cr default
3135 hg up -cr default
3137 hg graft -r featureX --base "ancestor('featureX', 'default')"
3136 hg graft -r featureX --base "ancestor('featureX', 'default')"
3138
3137
3139 See :hg:`help revisions` for more about specifying revisions.
3138 See :hg:`help revisions` for more about specifying revisions.
3140
3139
3141 Returns 0 on successful completion, 1 if there are unresolved files.
3140 Returns 0 on successful completion, 1 if there are unresolved files.
3142 """
3141 """
3143 with repo.wlock():
3142 with repo.wlock():
3144 return _dograft(ui, repo, *revs, **opts)
3143 return _dograft(ui, repo, *revs, **opts)
3145
3144
3146
3145
3147 def _dograft(ui, repo, *revs, **opts):
3146 def _dograft(ui, repo, *revs, **opts):
3148 if revs and opts.get('rev'):
3147 if revs and opts.get('rev'):
3149 ui.warn(
3148 ui.warn(
3150 _(
3149 _(
3151 b'warning: inconsistent use of --rev might give unexpected '
3150 b'warning: inconsistent use of --rev might give unexpected '
3152 b'revision ordering!\n'
3151 b'revision ordering!\n'
3153 )
3152 )
3154 )
3153 )
3155
3154
3156 revs = list(revs)
3155 revs = list(revs)
3157 revs.extend(opts.get('rev'))
3156 revs.extend(opts.get('rev'))
3158 # a dict of data to be stored in state file
3157 # a dict of data to be stored in state file
3159 statedata = {}
3158 statedata = {}
3160 # list of new nodes created by ongoing graft
3159 # list of new nodes created by ongoing graft
3161 statedata[b'newnodes'] = []
3160 statedata[b'newnodes'] = []
3162
3161
3163 cmdutil.resolve_commit_options(ui, opts)
3162 cmdutil.resolve_commit_options(ui, opts)
3164
3163
3165 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3164 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3166
3165
3167 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3166 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3168
3167
3169 cont = False
3168 cont = False
3170 if opts.get('no_commit'):
3169 if opts.get('no_commit'):
3171 cmdutil.check_incompatible_arguments(
3170 cmdutil.check_incompatible_arguments(
3172 opts,
3171 opts,
3173 'no_commit',
3172 'no_commit',
3174 ['edit', 'currentuser', 'currentdate', 'log'],
3173 ['edit', 'currentuser', 'currentdate', 'log'],
3175 )
3174 )
3176
3175
3177 graftstate = statemod.cmdstate(repo, b'graftstate')
3176 graftstate = statemod.cmdstate(repo, b'graftstate')
3178
3177
3179 if opts.get('stop'):
3178 if opts.get('stop'):
3180 cmdutil.check_incompatible_arguments(
3179 cmdutil.check_incompatible_arguments(
3181 opts,
3180 opts,
3182 'stop',
3181 'stop',
3183 [
3182 [
3184 'edit',
3183 'edit',
3185 'log',
3184 'log',
3186 'user',
3185 'user',
3187 'date',
3186 'date',
3188 'currentdate',
3187 'currentdate',
3189 'currentuser',
3188 'currentuser',
3190 'rev',
3189 'rev',
3191 ],
3190 ],
3192 )
3191 )
3193 return _stopgraft(ui, repo, graftstate)
3192 return _stopgraft(ui, repo, graftstate)
3194 elif opts.get('abort'):
3193 elif opts.get('abort'):
3195 cmdutil.check_incompatible_arguments(
3194 cmdutil.check_incompatible_arguments(
3196 opts,
3195 opts,
3197 'abort',
3196 'abort',
3198 [
3197 [
3199 'edit',
3198 'edit',
3200 'log',
3199 'log',
3201 'user',
3200 'user',
3202 'date',
3201 'date',
3203 'currentdate',
3202 'currentdate',
3204 'currentuser',
3203 'currentuser',
3205 'rev',
3204 'rev',
3206 ],
3205 ],
3207 )
3206 )
3208 return cmdutil.abortgraft(ui, repo, graftstate)
3207 return cmdutil.abortgraft(ui, repo, graftstate)
3209 elif opts.get('continue'):
3208 elif opts.get('continue'):
3210 cont = True
3209 cont = True
3211 if revs:
3210 if revs:
3212 raise error.InputError(_(b"can't specify --continue and revisions"))
3211 raise error.InputError(_(b"can't specify --continue and revisions"))
3213 # read in unfinished revisions
3212 # read in unfinished revisions
3214 if graftstate.exists():
3213 if graftstate.exists():
3215 statedata = cmdutil.readgraftstate(repo, graftstate)
3214 statedata = cmdutil.readgraftstate(repo, graftstate)
3216 if statedata.get(b'date'):
3215 if statedata.get(b'date'):
3217 opts['date'] = statedata[b'date']
3216 opts['date'] = statedata[b'date']
3218 if statedata.get(b'user'):
3217 if statedata.get(b'user'):
3219 opts['user'] = statedata[b'user']
3218 opts['user'] = statedata[b'user']
3220 if statedata.get(b'log'):
3219 if statedata.get(b'log'):
3221 opts['log'] = True
3220 opts['log'] = True
3222 if statedata.get(b'no_commit'):
3221 if statedata.get(b'no_commit'):
3223 opts['no_commit'] = statedata.get(b'no_commit')
3222 opts['no_commit'] = statedata.get(b'no_commit')
3224 if statedata.get(b'base'):
3223 if statedata.get(b'base'):
3225 opts['base'] = statedata.get(b'base')
3224 opts['base'] = statedata.get(b'base')
3226 nodes = statedata[b'nodes']
3225 nodes = statedata[b'nodes']
3227 revs = [repo[node].rev() for node in nodes]
3226 revs = [repo[node].rev() for node in nodes]
3228 else:
3227 else:
3229 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3228 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3230 else:
3229 else:
3231 if not revs:
3230 if not revs:
3232 raise error.InputError(_(b'no revisions specified'))
3231 raise error.InputError(_(b'no revisions specified'))
3233 cmdutil.checkunfinished(repo)
3232 cmdutil.checkunfinished(repo)
3234 cmdutil.bailifchanged(repo)
3233 cmdutil.bailifchanged(repo)
3235 revs = logcmdutil.revrange(repo, revs)
3234 revs = logcmdutil.revrange(repo, revs)
3236
3235
3237 skipped = set()
3236 skipped = set()
3238 basectx = None
3237 basectx = None
3239 if opts.get('base'):
3238 if opts.get('base'):
3240 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3239 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3241 if basectx is None:
3240 if basectx is None:
3242 # check for merges
3241 # check for merges
3243 for rev in repo.revs(b'%ld and merge()', revs):
3242 for rev in repo.revs(b'%ld and merge()', revs):
3244 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3243 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3245 skipped.add(rev)
3244 skipped.add(rev)
3246 revs = [r for r in revs if r not in skipped]
3245 revs = [r for r in revs if r not in skipped]
3247 if not revs:
3246 if not revs:
3248 return -1
3247 return -1
3249 if basectx is not None and len(revs) != 1:
3248 if basectx is not None and len(revs) != 1:
3250 raise error.InputError(_(b'only one revision allowed with --base '))
3249 raise error.InputError(_(b'only one revision allowed with --base '))
3251
3250
3252 # Don't check in the --continue case, in effect retaining --force across
3251 # Don't check in the --continue case, in effect retaining --force across
3253 # --continues. That's because without --force, any revisions we decided to
3252 # --continues. That's because without --force, any revisions we decided to
3254 # skip would have been filtered out here, so they wouldn't have made their
3253 # skip would have been filtered out here, so they wouldn't have made their
3255 # way to the graftstate. With --force, any revisions we would have otherwise
3254 # way to the graftstate. With --force, any revisions we would have otherwise
3256 # skipped would not have been filtered out, and if they hadn't been applied
3255 # skipped would not have been filtered out, and if they hadn't been applied
3257 # already, they'd have been in the graftstate.
3256 # already, they'd have been in the graftstate.
3258 if not (cont or opts.get('force')) and basectx is None:
3257 if not (cont or opts.get('force')) and basectx is None:
3259 # check for ancestors of dest branch
3258 # check for ancestors of dest branch
3260 ancestors = repo.revs(b'%ld & (::.)', revs)
3259 ancestors = repo.revs(b'%ld & (::.)', revs)
3261 for rev in ancestors:
3260 for rev in ancestors:
3262 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3261 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3263
3262
3264 revs = [r for r in revs if r not in ancestors]
3263 revs = [r for r in revs if r not in ancestors]
3265
3264
3266 if not revs:
3265 if not revs:
3267 return -1
3266 return -1
3268
3267
3269 # analyze revs for earlier grafts
3268 # analyze revs for earlier grafts
3270 ids = {}
3269 ids = {}
3271 for ctx in repo.set(b"%ld", revs):
3270 for ctx in repo.set(b"%ld", revs):
3272 ids[ctx.hex()] = ctx.rev()
3271 ids[ctx.hex()] = ctx.rev()
3273 n = ctx.extra().get(b'source')
3272 n = ctx.extra().get(b'source')
3274 if n:
3273 if n:
3275 ids[n] = ctx.rev()
3274 ids[n] = ctx.rev()
3276
3275
3277 # check ancestors for earlier grafts
3276 # check ancestors for earlier grafts
3278 ui.debug(b'scanning for duplicate grafts\n')
3277 ui.debug(b'scanning for duplicate grafts\n')
3279
3278
3280 # The only changesets we can be sure doesn't contain grafts of any
3279 # The only changesets we can be sure doesn't contain grafts of any
3281 # revs, are the ones that are common ancestors of *all* revs:
3280 # revs, are the ones that are common ancestors of *all* revs:
3282 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3281 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3283 ctx = repo[rev]
3282 ctx = repo[rev]
3284 n = ctx.extra().get(b'source')
3283 n = ctx.extra().get(b'source')
3285 if n in ids:
3284 if n in ids:
3286 try:
3285 try:
3287 r = repo[n].rev()
3286 r = repo[n].rev()
3288 except error.RepoLookupError:
3287 except error.RepoLookupError:
3289 r = None
3288 r = None
3290 if r in revs:
3289 if r in revs:
3291 ui.warn(
3290 ui.warn(
3292 _(
3291 _(
3293 b'skipping revision %d:%s '
3292 b'skipping revision %d:%s '
3294 b'(already grafted to %d:%s)\n'
3293 b'(already grafted to %d:%s)\n'
3295 )
3294 )
3296 % (r, repo[r], rev, ctx)
3295 % (r, repo[r], rev, ctx)
3297 )
3296 )
3298 revs.remove(r)
3297 revs.remove(r)
3299 elif ids[n] in revs:
3298 elif ids[n] in revs:
3300 if r is None:
3299 if r is None:
3301 ui.warn(
3300 ui.warn(
3302 _(
3301 _(
3303 b'skipping already grafted revision %d:%s '
3302 b'skipping already grafted revision %d:%s '
3304 b'(%d:%s also has unknown origin %s)\n'
3303 b'(%d:%s also has unknown origin %s)\n'
3305 )
3304 )
3306 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3305 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3307 )
3306 )
3308 else:
3307 else:
3309 ui.warn(
3308 ui.warn(
3310 _(
3309 _(
3311 b'skipping already grafted revision %d:%s '
3310 b'skipping already grafted revision %d:%s '
3312 b'(%d:%s also has origin %d:%s)\n'
3311 b'(%d:%s also has origin %d:%s)\n'
3313 )
3312 )
3314 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3313 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3315 )
3314 )
3316 revs.remove(ids[n])
3315 revs.remove(ids[n])
3317 elif ctx.hex() in ids:
3316 elif ctx.hex() in ids:
3318 r = ids[ctx.hex()]
3317 r = ids[ctx.hex()]
3319 if r in revs:
3318 if r in revs:
3320 ui.warn(
3319 ui.warn(
3321 _(
3320 _(
3322 b'skipping already grafted revision %d:%s '
3321 b'skipping already grafted revision %d:%s '
3323 b'(was grafted from %d:%s)\n'
3322 b'(was grafted from %d:%s)\n'
3324 )
3323 )
3325 % (r, repo[r], rev, ctx)
3324 % (r, repo[r], rev, ctx)
3326 )
3325 )
3327 revs.remove(r)
3326 revs.remove(r)
3328 if not revs:
3327 if not revs:
3329 return -1
3328 return -1
3330
3329
3331 if opts.get('no_commit'):
3330 if opts.get('no_commit'):
3332 statedata[b'no_commit'] = True
3331 statedata[b'no_commit'] = True
3333 if opts.get('base'):
3332 if opts.get('base'):
3334 statedata[b'base'] = opts['base']
3333 statedata[b'base'] = opts['base']
3335 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3334 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3336 desc = b'%d:%s "%s"' % (
3335 desc = b'%d:%s "%s"' % (
3337 ctx.rev(),
3336 ctx.rev(),
3338 ctx,
3337 ctx,
3339 ctx.description().split(b'\n', 1)[0],
3338 ctx.description().split(b'\n', 1)[0],
3340 )
3339 )
3341 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3340 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3342 if names:
3341 if names:
3343 desc += b' (%s)' % b' '.join(names)
3342 desc += b' (%s)' % b' '.join(names)
3344 ui.status(_(b'grafting %s\n') % desc)
3343 ui.status(_(b'grafting %s\n') % desc)
3345 if opts.get('dry_run'):
3344 if opts.get('dry_run'):
3346 continue
3345 continue
3347
3346
3348 source = ctx.extra().get(b'source')
3347 source = ctx.extra().get(b'source')
3349 extra = {}
3348 extra = {}
3350 if source:
3349 if source:
3351 extra[b'source'] = source
3350 extra[b'source'] = source
3352 extra[b'intermediate-source'] = ctx.hex()
3351 extra[b'intermediate-source'] = ctx.hex()
3353 else:
3352 else:
3354 extra[b'source'] = ctx.hex()
3353 extra[b'source'] = ctx.hex()
3355 user = ctx.user()
3354 user = ctx.user()
3356 if opts.get('user'):
3355 if opts.get('user'):
3357 user = opts['user']
3356 user = opts['user']
3358 statedata[b'user'] = user
3357 statedata[b'user'] = user
3359 date = ctx.date()
3358 date = ctx.date()
3360 if opts.get('date'):
3359 if opts.get('date'):
3361 date = opts['date']
3360 date = opts['date']
3362 statedata[b'date'] = date
3361 statedata[b'date'] = date
3363 message = ctx.description()
3362 message = ctx.description()
3364 if opts.get('log'):
3363 if opts.get('log'):
3365 message += b'\n(grafted from %s)' % ctx.hex()
3364 message += b'\n(grafted from %s)' % ctx.hex()
3366 statedata[b'log'] = True
3365 statedata[b'log'] = True
3367
3366
3368 # we don't merge the first commit when continuing
3367 # we don't merge the first commit when continuing
3369 if not cont:
3368 if not cont:
3370 # perform the graft merge with p1(rev) as 'ancestor'
3369 # perform the graft merge with p1(rev) as 'ancestor'
3371 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3370 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3372 base = ctx.p1() if basectx is None else basectx
3371 base = ctx.p1() if basectx is None else basectx
3373 with ui.configoverride(overrides, b'graft'):
3372 with ui.configoverride(overrides, b'graft'):
3374 stats = mergemod.graft(
3373 stats = mergemod.graft(
3375 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3374 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3376 )
3375 )
3377 # report any conflicts
3376 # report any conflicts
3378 if stats.unresolvedcount > 0:
3377 if stats.unresolvedcount > 0:
3379 # write out state for --continue
3378 # write out state for --continue
3380 nodes = [repo[rev].hex() for rev in revs[pos:]]
3379 nodes = [repo[rev].hex() for rev in revs[pos:]]
3381 statedata[b'nodes'] = nodes
3380 statedata[b'nodes'] = nodes
3382 stateversion = 1
3381 stateversion = 1
3383 graftstate.save(stateversion, statedata)
3382 graftstate.save(stateversion, statedata)
3384 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3383 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3385 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3384 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3386 return 1
3385 return 1
3387 else:
3386 else:
3388 cont = False
3387 cont = False
3389
3388
3390 # commit if --no-commit is false
3389 # commit if --no-commit is false
3391 if not opts.get('no_commit'):
3390 if not opts.get('no_commit'):
3392 node = repo.commit(
3391 node = repo.commit(
3393 text=message, user=user, date=date, extra=extra, editor=editor
3392 text=message, user=user, date=date, extra=extra, editor=editor
3394 )
3393 )
3395 if node is None:
3394 if node is None:
3396 ui.warn(
3395 ui.warn(
3397 _(b'note: graft of %d:%s created no changes to commit\n')
3396 _(b'note: graft of %d:%s created no changes to commit\n')
3398 % (ctx.rev(), ctx)
3397 % (ctx.rev(), ctx)
3399 )
3398 )
3400 # checking that newnodes exist because old state files won't have it
3399 # checking that newnodes exist because old state files won't have it
3401 elif statedata.get(b'newnodes') is not None:
3400 elif statedata.get(b'newnodes') is not None:
3402 nn = statedata[b'newnodes']
3401 nn = statedata[b'newnodes']
3403 assert isinstance(nn, list) # list of bytes
3402 assert isinstance(nn, list) # list of bytes
3404 nn.append(node)
3403 nn.append(node)
3405
3404
3406 # remove state when we complete successfully
3405 # remove state when we complete successfully
3407 if not opts.get('dry_run'):
3406 if not opts.get('dry_run'):
3408 graftstate.delete()
3407 graftstate.delete()
3409
3408
3410 return 0
3409 return 0
3411
3410
3412
3411
3413 def _stopgraft(ui, repo, graftstate):
3412 def _stopgraft(ui, repo, graftstate):
3414 """stop the interrupted graft"""
3413 """stop the interrupted graft"""
3415 if not graftstate.exists():
3414 if not graftstate.exists():
3416 raise error.StateError(_(b"no interrupted graft found"))
3415 raise error.StateError(_(b"no interrupted graft found"))
3417 pctx = repo[b'.']
3416 pctx = repo[b'.']
3418 mergemod.clean_update(pctx)
3417 mergemod.clean_update(pctx)
3419 graftstate.delete()
3418 graftstate.delete()
3420 ui.status(_(b"stopped the interrupted graft\n"))
3419 ui.status(_(b"stopped the interrupted graft\n"))
3421 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3420 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3422 return 0
3421 return 0
3423
3422
3424
3423
3425 statemod.addunfinished(
3424 statemod.addunfinished(
3426 b'graft',
3425 b'graft',
3427 fname=b'graftstate',
3426 fname=b'graftstate',
3428 clearable=True,
3427 clearable=True,
3429 stopflag=True,
3428 stopflag=True,
3430 continueflag=True,
3429 continueflag=True,
3431 abortfunc=cmdutil.hgabortgraft,
3430 abortfunc=cmdutil.hgabortgraft,
3432 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3431 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3433 )
3432 )
3434
3433
3435
3434
3436 @command(
3435 @command(
3437 b'grep',
3436 b'grep',
3438 [
3437 [
3439 (b'0', b'print0', None, _(b'end fields with NUL')),
3438 (b'0', b'print0', None, _(b'end fields with NUL')),
3440 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3439 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3441 (
3440 (
3442 b'',
3441 b'',
3443 b'diff',
3442 b'diff',
3444 None,
3443 None,
3445 _(
3444 _(
3446 b'search revision differences for when the pattern was added '
3445 b'search revision differences for when the pattern was added '
3447 b'or removed'
3446 b'or removed'
3448 ),
3447 ),
3449 ),
3448 ),
3450 (b'a', b'text', None, _(b'treat all files as text')),
3449 (b'a', b'text', None, _(b'treat all files as text')),
3451 (
3450 (
3452 b'f',
3451 b'f',
3453 b'follow',
3452 b'follow',
3454 None,
3453 None,
3455 _(
3454 _(
3456 b'follow changeset history,'
3455 b'follow changeset history,'
3457 b' or file history across copies and renames'
3456 b' or file history across copies and renames'
3458 ),
3457 ),
3459 ),
3458 ),
3460 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3459 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3461 (
3460 (
3462 b'l',
3461 b'l',
3463 b'files-with-matches',
3462 b'files-with-matches',
3464 None,
3463 None,
3465 _(b'print only filenames and revisions that match'),
3464 _(b'print only filenames and revisions that match'),
3466 ),
3465 ),
3467 (b'n', b'line-number', None, _(b'print matching line numbers')),
3466 (b'n', b'line-number', None, _(b'print matching line numbers')),
3468 (
3467 (
3469 b'r',
3468 b'r',
3470 b'rev',
3469 b'rev',
3471 [],
3470 [],
3472 _(b'search files changed within revision range'),
3471 _(b'search files changed within revision range'),
3473 _(b'REV'),
3472 _(b'REV'),
3474 ),
3473 ),
3475 (
3474 (
3476 b'',
3475 b'',
3477 b'all-files',
3476 b'all-files',
3478 None,
3477 None,
3479 _(
3478 _(
3480 b'include all files in the changeset while grepping (DEPRECATED)'
3479 b'include all files in the changeset while grepping (DEPRECATED)'
3481 ),
3480 ),
3482 ),
3481 ),
3483 (b'u', b'user', None, _(b'list the author (long with -v)')),
3482 (b'u', b'user', None, _(b'list the author (long with -v)')),
3484 (b'd', b'date', None, _(b'list the date (short with -q)')),
3483 (b'd', b'date', None, _(b'list the date (short with -q)')),
3485 ]
3484 ]
3486 + formatteropts
3485 + formatteropts
3487 + walkopts,
3486 + walkopts,
3488 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3487 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3489 helpcategory=command.CATEGORY_FILE_CONTENTS,
3488 helpcategory=command.CATEGORY_FILE_CONTENTS,
3490 inferrepo=True,
3489 inferrepo=True,
3491 intents={INTENT_READONLY},
3490 intents={INTENT_READONLY},
3492 )
3491 )
3493 def grep(ui, repo, pattern, *pats, **opts):
3492 def grep(ui, repo, pattern, *pats, **opts):
3494 """search for a pattern in specified files
3493 """search for a pattern in specified files
3495
3494
3496 Search the working directory or revision history for a regular
3495 Search the working directory or revision history for a regular
3497 expression in the specified files for the entire repository.
3496 expression in the specified files for the entire repository.
3498
3497
3499 By default, grep searches the repository files in the working
3498 By default, grep searches the repository files in the working
3500 directory and prints the files where it finds a match. To specify
3499 directory and prints the files where it finds a match. To specify
3501 historical revisions instead of the working directory, use the
3500 historical revisions instead of the working directory, use the
3502 --rev flag.
3501 --rev flag.
3503
3502
3504 To search instead historical revision differences that contains a
3503 To search instead historical revision differences that contains a
3505 change in match status ("-" for a match that becomes a non-match,
3504 change in match status ("-" for a match that becomes a non-match,
3506 or "+" for a non-match that becomes a match), use the --diff flag.
3505 or "+" for a non-match that becomes a match), use the --diff flag.
3507
3506
3508 PATTERN can be any Python (roughly Perl-compatible) regular
3507 PATTERN can be any Python (roughly Perl-compatible) regular
3509 expression.
3508 expression.
3510
3509
3511 If no FILEs are specified and the --rev flag isn't supplied, all
3510 If no FILEs are specified and the --rev flag isn't supplied, all
3512 files in the working directory are searched. When using the --rev
3511 files in the working directory are searched. When using the --rev
3513 flag and specifying FILEs, use the --follow argument to also
3512 flag and specifying FILEs, use the --follow argument to also
3514 follow the specified FILEs across renames and copies.
3513 follow the specified FILEs across renames and copies.
3515
3514
3516 .. container:: verbose
3515 .. container:: verbose
3517
3516
3518 Template:
3517 Template:
3519
3518
3520 The following keywords are supported in addition to the common template
3519 The following keywords are supported in addition to the common template
3521 keywords and functions. See also :hg:`help templates`.
3520 keywords and functions. See also :hg:`help templates`.
3522
3521
3523 :change: String. Character denoting insertion ``+`` or removal ``-``.
3522 :change: String. Character denoting insertion ``+`` or removal ``-``.
3524 Available if ``--diff`` is specified.
3523 Available if ``--diff`` is specified.
3525 :lineno: Integer. Line number of the match.
3524 :lineno: Integer. Line number of the match.
3526 :path: String. Repository-absolute path of the file.
3525 :path: String. Repository-absolute path of the file.
3527 :texts: List of text chunks.
3526 :texts: List of text chunks.
3528
3527
3529 And each entry of ``{texts}`` provides the following sub-keywords.
3528 And each entry of ``{texts}`` provides the following sub-keywords.
3530
3529
3531 :matched: Boolean. True if the chunk matches the specified pattern.
3530 :matched: Boolean. True if the chunk matches the specified pattern.
3532 :text: String. Chunk content.
3531 :text: String. Chunk content.
3533
3532
3534 See :hg:`help templates.operators` for the list expansion syntax.
3533 See :hg:`help templates.operators` for the list expansion syntax.
3535
3534
3536 Returns 0 if a match is found, 1 otherwise.
3535 Returns 0 if a match is found, 1 otherwise.
3537
3536
3538 """
3537 """
3539 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3538 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3540
3539
3541 diff = opts.get('all') or opts.get('diff')
3540 diff = opts.get('all') or opts.get('diff')
3542 follow = opts.get('follow')
3541 follow = opts.get('follow')
3543 if opts.get('all_files') is None and not diff:
3542 if opts.get('all_files') is None and not diff:
3544 opts['all_files'] = True
3543 opts['all_files'] = True
3545 plaingrep = (
3544 plaingrep = (
3546 opts.get('all_files') and not opts.get('rev') and not opts.get('follow')
3545 opts.get('all_files') and not opts.get('rev') and not opts.get('follow')
3547 )
3546 )
3548 all_files = opts.get('all_files')
3547 all_files = opts.get('all_files')
3549 if plaingrep:
3548 if plaingrep:
3550 opts['rev'] = [b'wdir()']
3549 opts['rev'] = [b'wdir()']
3551
3550
3552 reflags = re.M
3551 reflags = re.M
3553 if opts.get('ignore_case'):
3552 if opts.get('ignore_case'):
3554 reflags |= re.I
3553 reflags |= re.I
3555 try:
3554 try:
3556 regexp = util.re.compile(pattern, reflags)
3555 regexp = util.re.compile(pattern, reflags)
3557 except re.error as inst:
3556 except re.error as inst:
3558 ui.warn(
3557 ui.warn(
3559 _(b"grep: invalid match pattern: %s\n")
3558 _(b"grep: invalid match pattern: %s\n")
3560 % stringutil.forcebytestr(inst)
3559 % stringutil.forcebytestr(inst)
3561 )
3560 )
3562 return 1
3561 return 1
3563 sep, eol = b':', b'\n'
3562 sep, eol = b':', b'\n'
3564 if opts.get('print0'):
3563 if opts.get('print0'):
3565 sep = eol = b'\0'
3564 sep = eol = b'\0'
3566
3565
3567 searcher = grepmod.grepsearcher(
3566 searcher = grepmod.grepsearcher(
3568 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3567 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3569 )
3568 )
3570
3569
3571 getfile = searcher._getfile
3570 getfile = searcher._getfile
3572
3571
3573 uipathfn = scmutil.getuipathfn(repo)
3572 uipathfn = scmutil.getuipathfn(repo)
3574
3573
3575 def display(fm, fn, ctx, pstates, states):
3574 def display(fm, fn, ctx, pstates, states):
3576 rev = scmutil.intrev(ctx)
3575 rev = scmutil.intrev(ctx)
3577 if fm.isplain():
3576 if fm.isplain():
3578 formatuser = ui.shortuser
3577 formatuser = ui.shortuser
3579 else:
3578 else:
3580 formatuser = pycompat.bytestr
3579 formatuser = pycompat.bytestr
3581 if ui.quiet:
3580 if ui.quiet:
3582 datefmt = b'%Y-%m-%d'
3581 datefmt = b'%Y-%m-%d'
3583 else:
3582 else:
3584 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3583 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3585 found = False
3584 found = False
3586
3585
3587 @util.cachefunc
3586 @util.cachefunc
3588 def binary():
3587 def binary():
3589 flog = getfile(fn)
3588 flog = getfile(fn)
3590 try:
3589 try:
3591 return stringutil.binary(flog.read(ctx.filenode(fn)))
3590 return stringutil.binary(flog.read(ctx.filenode(fn)))
3592 except error.WdirUnsupported:
3591 except error.WdirUnsupported:
3593 return ctx[fn].isbinary()
3592 return ctx[fn].isbinary()
3594
3593
3595 fieldnamemap = {b'linenumber': b'lineno'}
3594 fieldnamemap = {b'linenumber': b'lineno'}
3596 if diff:
3595 if diff:
3597 iter = grepmod.difflinestates(pstates, states)
3596 iter = grepmod.difflinestates(pstates, states)
3598 else:
3597 else:
3599 iter = [(b'', l) for l in states]
3598 iter = [(b'', l) for l in states]
3600 for change, l in iter:
3599 for change, l in iter:
3601 fm.startitem()
3600 fm.startitem()
3602 fm.context(ctx=ctx)
3601 fm.context(ctx=ctx)
3603 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3602 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3604 fm.plain(uipathfn(fn), label=b'grep.filename')
3603 fm.plain(uipathfn(fn), label=b'grep.filename')
3605
3604
3606 cols = [
3605 cols = [
3607 (b'rev', b'%d', rev, not plaingrep, b''),
3606 (b'rev', b'%d', rev, not plaingrep, b''),
3608 (
3607 (
3609 b'linenumber',
3608 b'linenumber',
3610 b'%d',
3609 b'%d',
3611 l.linenum,
3610 l.linenum,
3612 opts.get('line_number'),
3611 opts.get('line_number'),
3613 b'',
3612 b'',
3614 ),
3613 ),
3615 ]
3614 ]
3616 if diff:
3615 if diff:
3617 cols.append(
3616 cols.append(
3618 (
3617 (
3619 b'change',
3618 b'change',
3620 b'%s',
3619 b'%s',
3621 change,
3620 change,
3622 True,
3621 True,
3623 b'grep.inserted '
3622 b'grep.inserted '
3624 if change == b'+'
3623 if change == b'+'
3625 else b'grep.deleted ',
3624 else b'grep.deleted ',
3626 )
3625 )
3627 )
3626 )
3628 cols.extend(
3627 cols.extend(
3629 [
3628 [
3630 (
3629 (
3631 b'user',
3630 b'user',
3632 b'%s',
3631 b'%s',
3633 formatuser(ctx.user()),
3632 formatuser(ctx.user()),
3634 opts.get('user'),
3633 opts.get('user'),
3635 b'',
3634 b'',
3636 ),
3635 ),
3637 (
3636 (
3638 b'date',
3637 b'date',
3639 b'%s',
3638 b'%s',
3640 fm.formatdate(ctx.date(), datefmt),
3639 fm.formatdate(ctx.date(), datefmt),
3641 opts.get('date'),
3640 opts.get('date'),
3642 b'',
3641 b'',
3643 ),
3642 ),
3644 ]
3643 ]
3645 )
3644 )
3646 for name, fmt, data, cond, extra_label in cols:
3645 for name, fmt, data, cond, extra_label in cols:
3647 if cond:
3646 if cond:
3648 fm.plain(sep, label=b'grep.sep')
3647 fm.plain(sep, label=b'grep.sep')
3649 field = fieldnamemap.get(name, name)
3648 field = fieldnamemap.get(name, name)
3650 label = extra_label + (b'grep.%s' % name)
3649 label = extra_label + (b'grep.%s' % name)
3651 fm.condwrite(cond, field, fmt, data, label=label)
3650 fm.condwrite(cond, field, fmt, data, label=label)
3652 if not opts.get('files_with_matches'):
3651 if not opts.get('files_with_matches'):
3653 fm.plain(sep, label=b'grep.sep')
3652 fm.plain(sep, label=b'grep.sep')
3654 if not opts.get('text') and binary():
3653 if not opts.get('text') and binary():
3655 fm.plain(_(b" Binary file matches"))
3654 fm.plain(_(b" Binary file matches"))
3656 else:
3655 else:
3657 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3656 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3658 fm.plain(eol)
3657 fm.plain(eol)
3659 found = True
3658 found = True
3660 if opts.get('files_with_matches'):
3659 if opts.get('files_with_matches'):
3661 break
3660 break
3662 return found
3661 return found
3663
3662
3664 def displaymatches(fm, l):
3663 def displaymatches(fm, l):
3665 p = 0
3664 p = 0
3666 for s, e in l.findpos(regexp):
3665 for s, e in l.findpos(regexp):
3667 if p < s:
3666 if p < s:
3668 fm.startitem()
3667 fm.startitem()
3669 fm.write(b'text', b'%s', l.line[p:s])
3668 fm.write(b'text', b'%s', l.line[p:s])
3670 fm.data(matched=False)
3669 fm.data(matched=False)
3671 fm.startitem()
3670 fm.startitem()
3672 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3671 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3673 fm.data(matched=True)
3672 fm.data(matched=True)
3674 p = e
3673 p = e
3675 if p < len(l.line):
3674 if p < len(l.line):
3676 fm.startitem()
3675 fm.startitem()
3677 fm.write(b'text', b'%s', l.line[p:])
3676 fm.write(b'text', b'%s', l.line[p:])
3678 fm.data(matched=False)
3677 fm.data(matched=False)
3679 fm.end()
3678 fm.end()
3680
3679
3681 found = False
3680 found = False
3682
3681
3683 wopts = logcmdutil.walkopts(
3682 wopts = logcmdutil.walkopts(
3684 pats=pats,
3683 pats=pats,
3685 opts=opts,
3684 opts=opts,
3686 revspec=opts['rev'],
3685 revspec=opts['rev'],
3687 include_pats=opts['include'],
3686 include_pats=opts['include'],
3688 exclude_pats=opts['exclude'],
3687 exclude_pats=opts['exclude'],
3689 follow=follow,
3688 follow=follow,
3690 force_changelog_traversal=all_files,
3689 force_changelog_traversal=all_files,
3691 filter_revisions_by_pats=not all_files,
3690 filter_revisions_by_pats=not all_files,
3692 )
3691 )
3693 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3692 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3694
3693
3695 ui.pager(b'grep')
3694 ui.pager(b'grep')
3696 fm = ui.formatter(b'grep', pycompat.byteskwargs(opts))
3695 fm = ui.formatter(b'grep', pycompat.byteskwargs(opts))
3697 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3696 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3698 r = display(fm, fn, ctx, pstates, states)
3697 r = display(fm, fn, ctx, pstates, states)
3699 found = found or r
3698 found = found or r
3700 if r and not diff and not all_files:
3699 if r and not diff and not all_files:
3701 searcher.skipfile(fn, ctx.rev())
3700 searcher.skipfile(fn, ctx.rev())
3702 fm.end()
3701 fm.end()
3703
3702
3704 return not found
3703 return not found
3705
3704
3706
3705
3707 @command(
3706 @command(
3708 b'heads',
3707 b'heads',
3709 [
3708 [
3710 (
3709 (
3711 b'r',
3710 b'r',
3712 b'rev',
3711 b'rev',
3713 b'',
3712 b'',
3714 _(b'show only heads which are descendants of STARTREV'),
3713 _(b'show only heads which are descendants of STARTREV'),
3715 _(b'STARTREV'),
3714 _(b'STARTREV'),
3716 ),
3715 ),
3717 (b't', b'topo', False, _(b'show topological heads only')),
3716 (b't', b'topo', False, _(b'show topological heads only')),
3718 (
3717 (
3719 b'a',
3718 b'a',
3720 b'active',
3719 b'active',
3721 False,
3720 False,
3722 _(b'show active branchheads only (DEPRECATED)'),
3721 _(b'show active branchheads only (DEPRECATED)'),
3723 ),
3722 ),
3724 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3723 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3725 ]
3724 ]
3726 + templateopts,
3725 + templateopts,
3727 _(b'[-ct] [-r STARTREV] [REV]...'),
3726 _(b'[-ct] [-r STARTREV] [REV]...'),
3728 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3727 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3729 intents={INTENT_READONLY},
3728 intents={INTENT_READONLY},
3730 )
3729 )
3731 def heads(ui, repo, *branchrevs, **opts):
3730 def heads(ui, repo, *branchrevs, **opts):
3732 """show branch heads
3731 """show branch heads
3733
3732
3734 With no arguments, show all open branch heads in the repository.
3733 With no arguments, show all open branch heads in the repository.
3735 Branch heads are changesets that have no descendants on the
3734 Branch heads are changesets that have no descendants on the
3736 same branch. They are where development generally takes place and
3735 same branch. They are where development generally takes place and
3737 are the usual targets for update and merge operations.
3736 are the usual targets for update and merge operations.
3738
3737
3739 If one or more REVs are given, only open branch heads on the
3738 If one or more REVs are given, only open branch heads on the
3740 branches associated with the specified changesets are shown. This
3739 branches associated with the specified changesets are shown. This
3741 means that you can use :hg:`heads .` to see the heads on the
3740 means that you can use :hg:`heads .` to see the heads on the
3742 currently checked-out branch.
3741 currently checked-out branch.
3743
3742
3744 If -c/--closed is specified, also show branch heads marked closed
3743 If -c/--closed is specified, also show branch heads marked closed
3745 (see :hg:`commit --close-branch`).
3744 (see :hg:`commit --close-branch`).
3746
3745
3747 If STARTREV is specified, only those heads that are descendants of
3746 If STARTREV is specified, only those heads that are descendants of
3748 STARTREV will be displayed.
3747 STARTREV will be displayed.
3749
3748
3750 If -t/--topo is specified, named branch mechanics will be ignored and only
3749 If -t/--topo is specified, named branch mechanics will be ignored and only
3751 topological heads (changesets with no children) will be shown.
3750 topological heads (changesets with no children) will be shown.
3752
3751
3753 Returns 0 if matching heads are found, 1 if not.
3752 Returns 0 if matching heads are found, 1 if not.
3754 """
3753 """
3755
3754
3756 opts = pycompat.byteskwargs(opts)
3755 opts = pycompat.byteskwargs(opts)
3757 start = None
3756 start = None
3758 rev = opts.get(b'rev')
3757 rev = opts.get(b'rev')
3759 if rev:
3758 if rev:
3760 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3759 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3761 start = logcmdutil.revsingle(repo, rev, None).node()
3760 start = logcmdutil.revsingle(repo, rev, None).node()
3762
3761
3763 if opts.get(b'topo'):
3762 if opts.get(b'topo'):
3764 heads = [repo[h] for h in repo.heads(start)]
3763 heads = [repo[h] for h in repo.heads(start)]
3765 else:
3764 else:
3766 heads = []
3765 heads = []
3767 for branch in repo.branchmap():
3766 for branch in repo.branchmap():
3768 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3767 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3769 heads = [repo[h] for h in heads]
3768 heads = [repo[h] for h in heads]
3770
3769
3771 if branchrevs:
3770 if branchrevs:
3772 branches = {
3771 branches = {
3773 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3772 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3774 }
3773 }
3775 heads = [h for h in heads if h.branch() in branches]
3774 heads = [h for h in heads if h.branch() in branches]
3776
3775
3777 if opts.get(b'active') and branchrevs:
3776 if opts.get(b'active') and branchrevs:
3778 dagheads = repo.heads(start)
3777 dagheads = repo.heads(start)
3779 heads = [h for h in heads if h.node() in dagheads]
3778 heads = [h for h in heads if h.node() in dagheads]
3780
3779
3781 if branchrevs:
3780 if branchrevs:
3782 haveheads = {h.branch() for h in heads}
3781 haveheads = {h.branch() for h in heads}
3783 if branches - haveheads:
3782 if branches - haveheads:
3784 headless = b', '.join(b for b in branches - haveheads)
3783 headless = b', '.join(b for b in branches - haveheads)
3785 msg = _(b'no open branch heads found on branches %s')
3784 msg = _(b'no open branch heads found on branches %s')
3786 if opts.get(b'rev'):
3785 if opts.get(b'rev'):
3787 msg += _(b' (started at %s)') % opts[b'rev']
3786 msg += _(b' (started at %s)') % opts[b'rev']
3788 ui.warn((msg + b'\n') % headless)
3787 ui.warn((msg + b'\n') % headless)
3789
3788
3790 if not heads:
3789 if not heads:
3791 return 1
3790 return 1
3792
3791
3793 ui.pager(b'heads')
3792 ui.pager(b'heads')
3794 heads = sorted(heads, key=lambda x: -(x.rev()))
3793 heads = sorted(heads, key=lambda x: -(x.rev()))
3795 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3794 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3796 for ctx in heads:
3795 for ctx in heads:
3797 displayer.show(ctx)
3796 displayer.show(ctx)
3798 displayer.close()
3797 displayer.close()
3799
3798
3800
3799
3801 @command(
3800 @command(
3802 b'help',
3801 b'help',
3803 [
3802 [
3804 (b'e', b'extension', None, _(b'show only help for extensions')),
3803 (b'e', b'extension', None, _(b'show only help for extensions')),
3805 (b'c', b'command', None, _(b'show only help for commands')),
3804 (b'c', b'command', None, _(b'show only help for commands')),
3806 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3805 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3807 (
3806 (
3808 b's',
3807 b's',
3809 b'system',
3808 b'system',
3810 [],
3809 [],
3811 _(b'show help for specific platform(s)'),
3810 _(b'show help for specific platform(s)'),
3812 _(b'PLATFORM'),
3811 _(b'PLATFORM'),
3813 ),
3812 ),
3814 ],
3813 ],
3815 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3814 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3816 helpcategory=command.CATEGORY_HELP,
3815 helpcategory=command.CATEGORY_HELP,
3817 norepo=True,
3816 norepo=True,
3818 intents={INTENT_READONLY},
3817 intents={INTENT_READONLY},
3819 )
3818 )
3820 def help_(ui, name=None, **opts):
3819 def help_(ui, name=None, **opts):
3821 """show help for a given topic or a help overview
3820 """show help for a given topic or a help overview
3822
3821
3823 With no arguments, print a list of commands with short help messages.
3822 With no arguments, print a list of commands with short help messages.
3824
3823
3825 Given a topic, extension, or command name, print help for that
3824 Given a topic, extension, or command name, print help for that
3826 topic.
3825 topic.
3827
3826
3828 Returns 0 if successful.
3827 Returns 0 if successful.
3829 """
3828 """
3830
3829
3831 keep = opts.get('system') or []
3830 keep = opts.get('system') or []
3832 if len(keep) == 0:
3831 if len(keep) == 0:
3833 if pycompat.sysplatform.startswith(b'win'):
3832 if pycompat.sysplatform.startswith(b'win'):
3834 keep.append(b'windows')
3833 keep.append(b'windows')
3835 elif pycompat.sysplatform == b'OpenVMS':
3834 elif pycompat.sysplatform == b'OpenVMS':
3836 keep.append(b'vms')
3835 keep.append(b'vms')
3837 elif pycompat.sysplatform == b'plan9':
3836 elif pycompat.sysplatform == b'plan9':
3838 keep.append(b'plan9')
3837 keep.append(b'plan9')
3839 else:
3838 else:
3840 keep.append(b'unix')
3839 keep.append(b'unix')
3841 keep.append(pycompat.sysplatform.lower())
3840 keep.append(pycompat.sysplatform.lower())
3842 if ui.verbose:
3841 if ui.verbose:
3843 keep.append(b'verbose')
3842 keep.append(b'verbose')
3844
3843
3845 commands = sys.modules[__name__]
3844 commands = sys.modules[__name__]
3846 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3845 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3847 ui.pager(b'help')
3846 ui.pager(b'help')
3848 ui.write(formatted)
3847 ui.write(formatted)
3849
3848
3850
3849
3851 @command(
3850 @command(
3852 b'identify|id',
3851 b'identify|id',
3853 [
3852 [
3854 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3853 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3855 (b'n', b'num', None, _(b'show local revision number')),
3854 (b'n', b'num', None, _(b'show local revision number')),
3856 (b'i', b'id', None, _(b'show global revision id')),
3855 (b'i', b'id', None, _(b'show global revision id')),
3857 (b'b', b'branch', None, _(b'show branch')),
3856 (b'b', b'branch', None, _(b'show branch')),
3858 (b't', b'tags', None, _(b'show tags')),
3857 (b't', b'tags', None, _(b'show tags')),
3859 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3858 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3860 ]
3859 ]
3861 + remoteopts
3860 + remoteopts
3862 + formatteropts,
3861 + formatteropts,
3863 _(b'[-nibtB] [-r REV] [SOURCE]'),
3862 _(b'[-nibtB] [-r REV] [SOURCE]'),
3864 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3863 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3865 optionalrepo=True,
3864 optionalrepo=True,
3866 intents={INTENT_READONLY},
3865 intents={INTENT_READONLY},
3867 )
3866 )
3868 def identify(
3867 def identify(
3869 ui,
3868 ui,
3870 repo,
3869 repo,
3871 source=None,
3870 source=None,
3872 rev=None,
3871 rev=None,
3873 num=None,
3872 num=None,
3874 id=None,
3873 id=None,
3875 branch=None,
3874 branch=None,
3876 tags=None,
3875 tags=None,
3877 bookmarks=None,
3876 bookmarks=None,
3878 **opts
3877 **opts
3879 ):
3878 ):
3880 """identify the working directory or specified revision
3879 """identify the working directory or specified revision
3881
3880
3882 Print a summary identifying the repository state at REV using one or
3881 Print a summary identifying the repository state at REV using one or
3883 two parent hash identifiers, followed by a "+" if the working
3882 two parent hash identifiers, followed by a "+" if the working
3884 directory has uncommitted changes, the branch name (if not default),
3883 directory has uncommitted changes, the branch name (if not default),
3885 a list of tags, and a list of bookmarks.
3884 a list of tags, and a list of bookmarks.
3886
3885
3887 When REV is not given, print a summary of the current state of the
3886 When REV is not given, print a summary of the current state of the
3888 repository including the working directory. Specify -r. to get information
3887 repository including the working directory. Specify -r. to get information
3889 of the working directory parent without scanning uncommitted changes.
3888 of the working directory parent without scanning uncommitted changes.
3890
3889
3891 Specifying a path to a repository root or Mercurial bundle will
3890 Specifying a path to a repository root or Mercurial bundle will
3892 cause lookup to operate on that repository/bundle.
3891 cause lookup to operate on that repository/bundle.
3893
3892
3894 .. container:: verbose
3893 .. container:: verbose
3895
3894
3896 Template:
3895 Template:
3897
3896
3898 The following keywords are supported in addition to the common template
3897 The following keywords are supported in addition to the common template
3899 keywords and functions. See also :hg:`help templates`.
3898 keywords and functions. See also :hg:`help templates`.
3900
3899
3901 :dirty: String. Character ``+`` denoting if the working directory has
3900 :dirty: String. Character ``+`` denoting if the working directory has
3902 uncommitted changes.
3901 uncommitted changes.
3903 :id: String. One or two nodes, optionally followed by ``+``.
3902 :id: String. One or two nodes, optionally followed by ``+``.
3904 :parents: List of strings. Parent nodes of the changeset.
3903 :parents: List of strings. Parent nodes of the changeset.
3905
3904
3906 Examples:
3905 Examples:
3907
3906
3908 - generate a build identifier for the working directory::
3907 - generate a build identifier for the working directory::
3909
3908
3910 hg id --id > build-id.dat
3909 hg id --id > build-id.dat
3911
3910
3912 - find the revision corresponding to a tag::
3911 - find the revision corresponding to a tag::
3913
3912
3914 hg id -n -r 1.3
3913 hg id -n -r 1.3
3915
3914
3916 - check the most recent revision of a remote repository::
3915 - check the most recent revision of a remote repository::
3917
3916
3918 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3917 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3919
3918
3920 See :hg:`log` for generating more information about specific revisions,
3919 See :hg:`log` for generating more information about specific revisions,
3921 including full hash identifiers.
3920 including full hash identifiers.
3922
3921
3923 Returns 0 if successful.
3922 Returns 0 if successful.
3924 """
3923 """
3925
3924
3926 opts = pycompat.byteskwargs(opts)
3925 opts = pycompat.byteskwargs(opts)
3927 if not repo and not source:
3926 if not repo and not source:
3928 raise error.InputError(
3927 raise error.InputError(
3929 _(b"there is no Mercurial repository here (.hg not found)")
3928 _(b"there is no Mercurial repository here (.hg not found)")
3930 )
3929 )
3931
3930
3932 default = not (num or id or branch or tags or bookmarks)
3931 default = not (num or id or branch or tags or bookmarks)
3933 output = []
3932 output = []
3934 revs = []
3933 revs = []
3935
3934
3936 peer = None
3935 peer = None
3937 try:
3936 try:
3938 if source:
3937 if source:
3939 path = urlutil.get_unique_pull_path_obj(b'identify', ui, source)
3938 path = urlutil.get_unique_pull_path_obj(b'identify', ui, source)
3940 # only pass ui when no repo
3939 # only pass ui when no repo
3941 peer = hg.peer(repo or ui, opts, path)
3940 peer = hg.peer(repo or ui, opts, path)
3942 repo = peer.local()
3941 repo = peer.local()
3943 branches = (path.branch, [])
3942 branches = (path.branch, [])
3944 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3943 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3945
3944
3946 fm = ui.formatter(b'identify', opts)
3945 fm = ui.formatter(b'identify', opts)
3947 fm.startitem()
3946 fm.startitem()
3948
3947
3949 if not repo:
3948 if not repo:
3950 if num or branch or tags:
3949 if num or branch or tags:
3951 raise error.InputError(
3950 raise error.InputError(
3952 _(b"can't query remote revision number, branch, or tags")
3951 _(b"can't query remote revision number, branch, or tags")
3953 )
3952 )
3954 if not rev and revs:
3953 if not rev and revs:
3955 rev = revs[0]
3954 rev = revs[0]
3956 if not rev:
3955 if not rev:
3957 rev = b"tip"
3956 rev = b"tip"
3958
3957
3959 remoterev = peer.lookup(rev)
3958 remoterev = peer.lookup(rev)
3960 hexrev = fm.hexfunc(remoterev)
3959 hexrev = fm.hexfunc(remoterev)
3961 if default or id:
3960 if default or id:
3962 output = [hexrev]
3961 output = [hexrev]
3963 fm.data(id=hexrev)
3962 fm.data(id=hexrev)
3964
3963
3965 @util.cachefunc
3964 @util.cachefunc
3966 def getbms():
3965 def getbms():
3967 bms = []
3966 bms = []
3968
3967
3969 if b'bookmarks' in peer.listkeys(b'namespaces'):
3968 if b'bookmarks' in peer.listkeys(b'namespaces'):
3970 hexremoterev = hex(remoterev)
3969 hexremoterev = hex(remoterev)
3971 bms = [
3970 bms = [
3972 bm
3971 bm
3973 for bm, bmr in peer.listkeys(b'bookmarks').items()
3972 for bm, bmr in peer.listkeys(b'bookmarks').items()
3974 if bmr == hexremoterev
3973 if bmr == hexremoterev
3975 ]
3974 ]
3976
3975
3977 return sorted(bms)
3976 return sorted(bms)
3978
3977
3979 if fm.isplain():
3978 if fm.isplain():
3980 if bookmarks:
3979 if bookmarks:
3981 output.extend(getbms())
3980 output.extend(getbms())
3982 elif default and not ui.quiet:
3981 elif default and not ui.quiet:
3983 # multiple bookmarks for a single parent separated by '/'
3982 # multiple bookmarks for a single parent separated by '/'
3984 bm = b'/'.join(getbms())
3983 bm = b'/'.join(getbms())
3985 if bm:
3984 if bm:
3986 output.append(bm)
3985 output.append(bm)
3987 else:
3986 else:
3988 fm.data(node=hex(remoterev))
3987 fm.data(node=hex(remoterev))
3989 if bookmarks or b'bookmarks' in fm.datahint():
3988 if bookmarks or b'bookmarks' in fm.datahint():
3990 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3989 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3991 else:
3990 else:
3992 if rev:
3991 if rev:
3993 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3992 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3994 ctx = logcmdutil.revsingle(repo, rev, None)
3993 ctx = logcmdutil.revsingle(repo, rev, None)
3995
3994
3996 if ctx.rev() is None:
3995 if ctx.rev() is None:
3997 ctx = repo[None]
3996 ctx = repo[None]
3998 parents = ctx.parents()
3997 parents = ctx.parents()
3999 taglist = []
3998 taglist = []
4000 for p in parents:
3999 for p in parents:
4001 taglist.extend(p.tags())
4000 taglist.extend(p.tags())
4002
4001
4003 dirty = b""
4002 dirty = b""
4004 if ctx.dirty(missing=True, merge=False, branch=False):
4003 if ctx.dirty(missing=True, merge=False, branch=False):
4005 dirty = b'+'
4004 dirty = b'+'
4006 fm.data(dirty=dirty)
4005 fm.data(dirty=dirty)
4007
4006
4008 hexoutput = [fm.hexfunc(p.node()) for p in parents]
4007 hexoutput = [fm.hexfunc(p.node()) for p in parents]
4009 if default or id:
4008 if default or id:
4010 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
4009 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
4011 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
4010 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
4012
4011
4013 if num:
4012 if num:
4014 numoutput = [b"%d" % p.rev() for p in parents]
4013 numoutput = [b"%d" % p.rev() for p in parents]
4015 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
4014 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
4016
4015
4017 fm.data(
4016 fm.data(
4018 parents=fm.formatlist(
4017 parents=fm.formatlist(
4019 [fm.hexfunc(p.node()) for p in parents], name=b'node'
4018 [fm.hexfunc(p.node()) for p in parents], name=b'node'
4020 )
4019 )
4021 )
4020 )
4022 else:
4021 else:
4023 hexoutput = fm.hexfunc(ctx.node())
4022 hexoutput = fm.hexfunc(ctx.node())
4024 if default or id:
4023 if default or id:
4025 output = [hexoutput]
4024 output = [hexoutput]
4026 fm.data(id=hexoutput)
4025 fm.data(id=hexoutput)
4027
4026
4028 if num:
4027 if num:
4029 output.append(pycompat.bytestr(ctx.rev()))
4028 output.append(pycompat.bytestr(ctx.rev()))
4030 taglist = ctx.tags()
4029 taglist = ctx.tags()
4031
4030
4032 if default and not ui.quiet:
4031 if default and not ui.quiet:
4033 b = ctx.branch()
4032 b = ctx.branch()
4034 if b != b'default':
4033 if b != b'default':
4035 output.append(b"(%s)" % b)
4034 output.append(b"(%s)" % b)
4036
4035
4037 # multiple tags for a single parent separated by '/'
4036 # multiple tags for a single parent separated by '/'
4038 t = b'/'.join(taglist)
4037 t = b'/'.join(taglist)
4039 if t:
4038 if t:
4040 output.append(t)
4039 output.append(t)
4041
4040
4042 # multiple bookmarks for a single parent separated by '/'
4041 # multiple bookmarks for a single parent separated by '/'
4043 bm = b'/'.join(ctx.bookmarks())
4042 bm = b'/'.join(ctx.bookmarks())
4044 if bm:
4043 if bm:
4045 output.append(bm)
4044 output.append(bm)
4046 else:
4045 else:
4047 if branch:
4046 if branch:
4048 output.append(ctx.branch())
4047 output.append(ctx.branch())
4049
4048
4050 if tags:
4049 if tags:
4051 output.extend(taglist)
4050 output.extend(taglist)
4052
4051
4053 if bookmarks:
4052 if bookmarks:
4054 output.extend(ctx.bookmarks())
4053 output.extend(ctx.bookmarks())
4055
4054
4056 fm.data(node=ctx.hex())
4055 fm.data(node=ctx.hex())
4057 fm.data(branch=ctx.branch())
4056 fm.data(branch=ctx.branch())
4058 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4057 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4059 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4058 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4060 fm.context(ctx=ctx)
4059 fm.context(ctx=ctx)
4061
4060
4062 fm.plain(b"%s\n" % b' '.join(output))
4061 fm.plain(b"%s\n" % b' '.join(output))
4063 fm.end()
4062 fm.end()
4064 finally:
4063 finally:
4065 if peer:
4064 if peer:
4066 peer.close()
4065 peer.close()
4067
4066
4068
4067
4069 @command(
4068 @command(
4070 b'import|patch',
4069 b'import|patch',
4071 [
4070 [
4072 (
4071 (
4073 b'p',
4072 b'p',
4074 b'strip',
4073 b'strip',
4075 1,
4074 1,
4076 _(
4075 _(
4077 b'directory strip option for patch. This has the same '
4076 b'directory strip option for patch. This has the same '
4078 b'meaning as the corresponding patch option'
4077 b'meaning as the corresponding patch option'
4079 ),
4078 ),
4080 _(b'NUM'),
4079 _(b'NUM'),
4081 ),
4080 ),
4082 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4081 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4083 (b'', b'secret', None, _(b'use the secret phase for committing')),
4082 (b'', b'secret', None, _(b'use the secret phase for committing')),
4084 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4083 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4085 (
4084 (
4086 b'f',
4085 b'f',
4087 b'force',
4086 b'force',
4088 None,
4087 None,
4089 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4088 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4090 ),
4089 ),
4091 (
4090 (
4092 b'',
4091 b'',
4093 b'no-commit',
4092 b'no-commit',
4094 None,
4093 None,
4095 _(b"don't commit, just update the working directory"),
4094 _(b"don't commit, just update the working directory"),
4096 ),
4095 ),
4097 (
4096 (
4098 b'',
4097 b'',
4099 b'bypass',
4098 b'bypass',
4100 None,
4099 None,
4101 _(b"apply patch without touching the working directory"),
4100 _(b"apply patch without touching the working directory"),
4102 ),
4101 ),
4103 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4102 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4104 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4103 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4105 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4104 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4106 (
4105 (
4107 b'',
4106 b'',
4108 b'import-branch',
4107 b'import-branch',
4109 None,
4108 None,
4110 _(b'use any branch information in patch (implied by --exact)'),
4109 _(b'use any branch information in patch (implied by --exact)'),
4111 ),
4110 ),
4112 ]
4111 ]
4113 + commitopts
4112 + commitopts
4114 + commitopts2
4113 + commitopts2
4115 + similarityopts,
4114 + similarityopts,
4116 _(b'[OPTION]... PATCH...'),
4115 _(b'[OPTION]... PATCH...'),
4117 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4116 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4118 )
4117 )
4119 def import_(ui, repo, patch1=None, *patches, **opts):
4118 def import_(ui, repo, patch1=None, *patches, **opts):
4120 """import an ordered set of patches
4119 """import an ordered set of patches
4121
4120
4122 Import a list of patches and commit them individually (unless
4121 Import a list of patches and commit them individually (unless
4123 --no-commit is specified).
4122 --no-commit is specified).
4124
4123
4125 To read a patch from standard input (stdin), use "-" as the patch
4124 To read a patch from standard input (stdin), use "-" as the patch
4126 name. If a URL is specified, the patch will be downloaded from
4125 name. If a URL is specified, the patch will be downloaded from
4127 there.
4126 there.
4128
4127
4129 Import first applies changes to the working directory (unless
4128 Import first applies changes to the working directory (unless
4130 --bypass is specified), import will abort if there are outstanding
4129 --bypass is specified), import will abort if there are outstanding
4131 changes.
4130 changes.
4132
4131
4133 Use --bypass to apply and commit patches directly to the
4132 Use --bypass to apply and commit patches directly to the
4134 repository, without affecting the working directory. Without
4133 repository, without affecting the working directory. Without
4135 --exact, patches will be applied on top of the working directory
4134 --exact, patches will be applied on top of the working directory
4136 parent revision.
4135 parent revision.
4137
4136
4138 You can import a patch straight from a mail message. Even patches
4137 You can import a patch straight from a mail message. Even patches
4139 as attachments work (to use the body part, it must have type
4138 as attachments work (to use the body part, it must have type
4140 text/plain or text/x-patch). From and Subject headers of email
4139 text/plain or text/x-patch). From and Subject headers of email
4141 message are used as default committer and commit message. All
4140 message are used as default committer and commit message. All
4142 text/plain body parts before first diff are added to the commit
4141 text/plain body parts before first diff are added to the commit
4143 message.
4142 message.
4144
4143
4145 If the imported patch was generated by :hg:`export`, user and
4144 If the imported patch was generated by :hg:`export`, user and
4146 description from patch override values from message headers and
4145 description from patch override values from message headers and
4147 body. Values given on command line with -m/--message and -u/--user
4146 body. Values given on command line with -m/--message and -u/--user
4148 override these.
4147 override these.
4149
4148
4150 If --exact is specified, import will set the working directory to
4149 If --exact is specified, import will set the working directory to
4151 the parent of each patch before applying it, and will abort if the
4150 the parent of each patch before applying it, and will abort if the
4152 resulting changeset has a different ID than the one recorded in
4151 resulting changeset has a different ID than the one recorded in
4153 the patch. This will guard against various ways that portable
4152 the patch. This will guard against various ways that portable
4154 patch formats and mail systems might fail to transfer Mercurial
4153 patch formats and mail systems might fail to transfer Mercurial
4155 data or metadata. See :hg:`bundle` for lossless transmission.
4154 data or metadata. See :hg:`bundle` for lossless transmission.
4156
4155
4157 Use --partial to ensure a changeset will be created from the patch
4156 Use --partial to ensure a changeset will be created from the patch
4158 even if some hunks fail to apply. Hunks that fail to apply will be
4157 even if some hunks fail to apply. Hunks that fail to apply will be
4159 written to a <target-file>.rej file. Conflicts can then be resolved
4158 written to a <target-file>.rej file. Conflicts can then be resolved
4160 by hand before :hg:`commit --amend` is run to update the created
4159 by hand before :hg:`commit --amend` is run to update the created
4161 changeset. This flag exists to let people import patches that
4160 changeset. This flag exists to let people import patches that
4162 partially apply without losing the associated metadata (author,
4161 partially apply without losing the associated metadata (author,
4163 date, description, ...).
4162 date, description, ...).
4164
4163
4165 .. note::
4164 .. note::
4166
4165
4167 When no hunks apply cleanly, :hg:`import --partial` will create
4166 When no hunks apply cleanly, :hg:`import --partial` will create
4168 an empty changeset, importing only the patch metadata.
4167 an empty changeset, importing only the patch metadata.
4169
4168
4170 With -s/--similarity, hg will attempt to discover renames and
4169 With -s/--similarity, hg will attempt to discover renames and
4171 copies in the patch in the same way as :hg:`addremove`.
4170 copies in the patch in the same way as :hg:`addremove`.
4172
4171
4173 It is possible to use external patch programs to perform the patch
4172 It is possible to use external patch programs to perform the patch
4174 by setting the ``ui.patch`` configuration option. For the default
4173 by setting the ``ui.patch`` configuration option. For the default
4175 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4174 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4176 See :hg:`help config` for more information about configuration
4175 See :hg:`help config` for more information about configuration
4177 files and how to use these options.
4176 files and how to use these options.
4178
4177
4179 See :hg:`help dates` for a list of formats valid for -d/--date.
4178 See :hg:`help dates` for a list of formats valid for -d/--date.
4180
4179
4181 .. container:: verbose
4180 .. container:: verbose
4182
4181
4183 Examples:
4182 Examples:
4184
4183
4185 - import a traditional patch from a website and detect renames::
4184 - import a traditional patch from a website and detect renames::
4186
4185
4187 hg import -s 80 http://example.com/bugfix.patch
4186 hg import -s 80 http://example.com/bugfix.patch
4188
4187
4189 - import a changeset from an hgweb server::
4188 - import a changeset from an hgweb server::
4190
4189
4191 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4190 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4192
4191
4193 - import all the patches in an Unix-style mbox::
4192 - import all the patches in an Unix-style mbox::
4194
4193
4195 hg import incoming-patches.mbox
4194 hg import incoming-patches.mbox
4196
4195
4197 - import patches from stdin::
4196 - import patches from stdin::
4198
4197
4199 hg import -
4198 hg import -
4200
4199
4201 - attempt to exactly restore an exported changeset (not always
4200 - attempt to exactly restore an exported changeset (not always
4202 possible)::
4201 possible)::
4203
4202
4204 hg import --exact proposed-fix.patch
4203 hg import --exact proposed-fix.patch
4205
4204
4206 - use an external tool to apply a patch which is too fuzzy for
4205 - use an external tool to apply a patch which is too fuzzy for
4207 the default internal tool.
4206 the default internal tool.
4208
4207
4209 hg import --config ui.patch="patch --merge" fuzzy.patch
4208 hg import --config ui.patch="patch --merge" fuzzy.patch
4210
4209
4211 - change the default fuzzing from 2 to a less strict 7
4210 - change the default fuzzing from 2 to a less strict 7
4212
4211
4213 hg import --config ui.fuzz=7 fuzz.patch
4212 hg import --config ui.fuzz=7 fuzz.patch
4214
4213
4215 Returns 0 on success, 1 on partial success (see --partial).
4214 Returns 0 on success, 1 on partial success (see --partial).
4216 """
4215 """
4217
4216
4218 cmdutil.check_incompatible_arguments(
4217 cmdutil.check_incompatible_arguments(
4219 opts, 'no_commit', ['bypass', 'secret']
4218 opts, 'no_commit', ['bypass', 'secret']
4220 )
4219 )
4221 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4220 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4222 opts = pycompat.byteskwargs(opts)
4221 opts = pycompat.byteskwargs(opts)
4223 if not patch1:
4222 if not patch1:
4224 raise error.InputError(_(b'need at least one patch to import'))
4223 raise error.InputError(_(b'need at least one patch to import'))
4225
4224
4226 patches = (patch1,) + patches
4225 patches = (patch1,) + patches
4227
4226
4228 date = opts.get(b'date')
4227 date = opts.get(b'date')
4229 if date:
4228 if date:
4230 opts[b'date'] = dateutil.parsedate(date)
4229 opts[b'date'] = dateutil.parsedate(date)
4231
4230
4232 exact = opts.get(b'exact')
4231 exact = opts.get(b'exact')
4233 update = not opts.get(b'bypass')
4232 update = not opts.get(b'bypass')
4234 try:
4233 try:
4235 sim = float(opts.get(b'similarity') or 0)
4234 sim = float(opts.get(b'similarity') or 0)
4236 except ValueError:
4235 except ValueError:
4237 raise error.InputError(_(b'similarity must be a number'))
4236 raise error.InputError(_(b'similarity must be a number'))
4238 if sim < 0 or sim > 100:
4237 if sim < 0 or sim > 100:
4239 raise error.InputError(_(b'similarity must be between 0 and 100'))
4238 raise error.InputError(_(b'similarity must be between 0 and 100'))
4240 if sim and not update:
4239 if sim and not update:
4241 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4240 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4242
4241
4243 base = opts[b"base"]
4242 base = opts[b"base"]
4244 msgs = []
4243 msgs = []
4245 ret = 0
4244 ret = 0
4246
4245
4247 with repo.wlock():
4246 with repo.wlock():
4248 if update:
4247 if update:
4249 cmdutil.checkunfinished(repo)
4248 cmdutil.checkunfinished(repo)
4250 if exact or not opts.get(b'force'):
4249 if exact or not opts.get(b'force'):
4251 cmdutil.bailifchanged(repo)
4250 cmdutil.bailifchanged(repo)
4252
4251
4253 if not opts.get(b'no_commit'):
4252 if not opts.get(b'no_commit'):
4254 lock = repo.lock
4253 lock = repo.lock
4255 tr = lambda: repo.transaction(b'import')
4254 tr = lambda: repo.transaction(b'import')
4256 else:
4255 else:
4257 lock = util.nullcontextmanager
4256 lock = util.nullcontextmanager
4258 tr = util.nullcontextmanager
4257 tr = util.nullcontextmanager
4259 with lock(), tr():
4258 with lock(), tr():
4260 parents = repo[None].parents()
4259 parents = repo[None].parents()
4261 for patchurl in patches:
4260 for patchurl in patches:
4262 if patchurl == b'-':
4261 if patchurl == b'-':
4263 ui.status(_(b'applying patch from stdin\n'))
4262 ui.status(_(b'applying patch from stdin\n'))
4264 patchfile = ui.fin
4263 patchfile = ui.fin
4265 patchurl = b'stdin' # for error message
4264 patchurl = b'stdin' # for error message
4266 else:
4265 else:
4267 patchurl = os.path.join(base, patchurl)
4266 patchurl = os.path.join(base, patchurl)
4268 ui.status(_(b'applying %s\n') % patchurl)
4267 ui.status(_(b'applying %s\n') % patchurl)
4269 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4268 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4270
4269
4271 haspatch = False
4270 haspatch = False
4272 for hunk in patch.split(patchfile):
4271 for hunk in patch.split(patchfile):
4273 with patch.extract(ui, hunk) as patchdata:
4272 with patch.extract(ui, hunk) as patchdata:
4274 msg, node, rej = cmdutil.tryimportone(
4273 msg, node, rej = cmdutil.tryimportone(
4275 ui, repo, patchdata, parents, opts, msgs, hg.clean
4274 ui, repo, patchdata, parents, opts, msgs, hg.clean
4276 )
4275 )
4277 if msg:
4276 if msg:
4278 haspatch = True
4277 haspatch = True
4279 ui.note(msg + b'\n')
4278 ui.note(msg + b'\n')
4280 if update or exact:
4279 if update or exact:
4281 parents = repo[None].parents()
4280 parents = repo[None].parents()
4282 else:
4281 else:
4283 parents = [repo[node]]
4282 parents = [repo[node]]
4284 if rej:
4283 if rej:
4285 ui.write_err(_(b"patch applied partially\n"))
4284 ui.write_err(_(b"patch applied partially\n"))
4286 ui.write_err(
4285 ui.write_err(
4287 _(
4286 _(
4288 b"(fix the .rej files and run "
4287 b"(fix the .rej files and run "
4289 b"`hg commit --amend`)\n"
4288 b"`hg commit --amend`)\n"
4290 )
4289 )
4291 )
4290 )
4292 ret = 1
4291 ret = 1
4293 break
4292 break
4294
4293
4295 if not haspatch:
4294 if not haspatch:
4296 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4295 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4297
4296
4298 if msgs:
4297 if msgs:
4299 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4298 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4300 return ret
4299 return ret
4301
4300
4302
4301
4303 @command(
4302 @command(
4304 b'incoming|in',
4303 b'incoming|in',
4305 [
4304 [
4306 (
4305 (
4307 b'f',
4306 b'f',
4308 b'force',
4307 b'force',
4309 None,
4308 None,
4310 _(b'run even if remote repository is unrelated'),
4309 _(b'run even if remote repository is unrelated'),
4311 ),
4310 ),
4312 (b'n', b'newest-first', None, _(b'show newest record first')),
4311 (b'n', b'newest-first', None, _(b'show newest record first')),
4313 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4312 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4314 (
4313 (
4315 b'r',
4314 b'r',
4316 b'rev',
4315 b'rev',
4317 [],
4316 [],
4318 _(b'a remote changeset intended to be added'),
4317 _(b'a remote changeset intended to be added'),
4319 _(b'REV'),
4318 _(b'REV'),
4320 ),
4319 ),
4321 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4320 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4322 (
4321 (
4323 b'b',
4322 b'b',
4324 b'branch',
4323 b'branch',
4325 [],
4324 [],
4326 _(b'a specific branch you would like to pull'),
4325 _(b'a specific branch you would like to pull'),
4327 _(b'BRANCH'),
4326 _(b'BRANCH'),
4328 ),
4327 ),
4329 ]
4328 ]
4330 + logopts
4329 + logopts
4331 + remoteopts
4330 + remoteopts
4332 + subrepoopts,
4331 + subrepoopts,
4333 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4332 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4334 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4333 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4335 )
4334 )
4336 def incoming(ui, repo, source=b"default", **opts):
4335 def incoming(ui, repo, source=b"default", **opts):
4337 """show new changesets found in source
4336 """show new changesets found in source
4338
4337
4339 Show new changesets found in the specified path/URL or the default
4338 Show new changesets found in the specified path/URL or the default
4340 pull location. These are the changesets that would have been pulled
4339 pull location. These are the changesets that would have been pulled
4341 by :hg:`pull` at the time you issued this command.
4340 by :hg:`pull` at the time you issued this command.
4342
4341
4343 See pull for valid source format details.
4342 See pull for valid source format details.
4344
4343
4345 .. container:: verbose
4344 .. container:: verbose
4346
4345
4347 With -B/--bookmarks, the result of bookmark comparison between
4346 With -B/--bookmarks, the result of bookmark comparison between
4348 local and remote repositories is displayed. With -v/--verbose,
4347 local and remote repositories is displayed. With -v/--verbose,
4349 status is also displayed for each bookmark like below::
4348 status is also displayed for each bookmark like below::
4350
4349
4351 BM1 01234567890a added
4350 BM1 01234567890a added
4352 BM2 1234567890ab advanced
4351 BM2 1234567890ab advanced
4353 BM3 234567890abc diverged
4352 BM3 234567890abc diverged
4354 BM4 34567890abcd changed
4353 BM4 34567890abcd changed
4355
4354
4356 The action taken locally when pulling depends on the
4355 The action taken locally when pulling depends on the
4357 status of each bookmark:
4356 status of each bookmark:
4358
4357
4359 :``added``: pull will create it
4358 :``added``: pull will create it
4360 :``advanced``: pull will update it
4359 :``advanced``: pull will update it
4361 :``diverged``: pull will create a divergent bookmark
4360 :``diverged``: pull will create a divergent bookmark
4362 :``changed``: result depends on remote changesets
4361 :``changed``: result depends on remote changesets
4363
4362
4364 From the point of view of pulling behavior, bookmark
4363 From the point of view of pulling behavior, bookmark
4365 existing only in the remote repository are treated as ``added``,
4364 existing only in the remote repository are treated as ``added``,
4366 even if it is in fact locally deleted.
4365 even if it is in fact locally deleted.
4367
4366
4368 .. container:: verbose
4367 .. container:: verbose
4369
4368
4370 For remote repository, using --bundle avoids downloading the
4369 For remote repository, using --bundle avoids downloading the
4371 changesets twice if the incoming is followed by a pull.
4370 changesets twice if the incoming is followed by a pull.
4372
4371
4373 Examples:
4372 Examples:
4374
4373
4375 - show incoming changes with patches and full description::
4374 - show incoming changes with patches and full description::
4376
4375
4377 hg incoming -vp
4376 hg incoming -vp
4378
4377
4379 - show incoming changes excluding merges, store a bundle::
4378 - show incoming changes excluding merges, store a bundle::
4380
4379
4381 hg in -vpM --bundle incoming.hg
4380 hg in -vpM --bundle incoming.hg
4382 hg pull incoming.hg
4381 hg pull incoming.hg
4383
4382
4384 - briefly list changes inside a bundle::
4383 - briefly list changes inside a bundle::
4385
4384
4386 hg in changes.hg -T "{desc|firstline}\\n"
4385 hg in changes.hg -T "{desc|firstline}\\n"
4387
4386
4388 Returns 0 if there are incoming changes, 1 otherwise.
4387 Returns 0 if there are incoming changes, 1 otherwise.
4389 """
4388 """
4390 opts = pycompat.byteskwargs(opts)
4389 opts = pycompat.byteskwargs(opts)
4391 if opts.get(b'graph'):
4390 if opts.get(b'graph'):
4392 logcmdutil.checkunsupportedgraphflags([], opts)
4391 logcmdutil.checkunsupportedgraphflags([], opts)
4393
4392
4394 def display(other, chlist, displayer):
4393 def display(other, chlist, displayer):
4395 revdag = logcmdutil.graphrevs(other, chlist, opts)
4394 revdag = logcmdutil.graphrevs(other, chlist, opts)
4396 logcmdutil.displaygraph(
4395 logcmdutil.displaygraph(
4397 ui, repo, revdag, displayer, graphmod.asciiedges
4396 ui, repo, revdag, displayer, graphmod.asciiedges
4398 )
4397 )
4399
4398
4400 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4399 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4401 return 0
4400 return 0
4402
4401
4403 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4402 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4404
4403
4405 if opts.get(b'bookmarks'):
4404 if opts.get(b'bookmarks'):
4406 srcs = urlutil.get_pull_paths(repo, ui, [source])
4405 srcs = urlutil.get_pull_paths(repo, ui, [source])
4407 for path in srcs:
4406 for path in srcs:
4408 # XXX the "branches" options are not used. Should it be used?
4407 # XXX the "branches" options are not used. Should it be used?
4409 other = hg.peer(repo, opts, path)
4408 other = hg.peer(repo, opts, path)
4410 try:
4409 try:
4411 if b'bookmarks' not in other.listkeys(b'namespaces'):
4410 if b'bookmarks' not in other.listkeys(b'namespaces'):
4412 ui.warn(_(b"remote doesn't support bookmarks\n"))
4411 ui.warn(_(b"remote doesn't support bookmarks\n"))
4413 return 0
4412 return 0
4414 ui.pager(b'incoming')
4413 ui.pager(b'incoming')
4415 ui.status(
4414 ui.status(
4416 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
4415 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
4417 )
4416 )
4418 return bookmarks.incoming(
4417 return bookmarks.incoming(
4419 ui, repo, other, mode=path.bookmarks_mode
4418 ui, repo, other, mode=path.bookmarks_mode
4420 )
4419 )
4421 finally:
4420 finally:
4422 other.close()
4421 other.close()
4423
4422
4424 return hg.incoming(ui, repo, source, opts)
4423 return hg.incoming(ui, repo, source, opts)
4425
4424
4426
4425
4427 @command(
4426 @command(
4428 b'init',
4427 b'init',
4429 remoteopts,
4428 remoteopts,
4430 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4429 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4431 helpcategory=command.CATEGORY_REPO_CREATION,
4430 helpcategory=command.CATEGORY_REPO_CREATION,
4432 helpbasic=True,
4431 helpbasic=True,
4433 norepo=True,
4432 norepo=True,
4434 )
4433 )
4435 def init(ui, dest=b".", **opts):
4434 def init(ui, dest=b".", **opts):
4436 """create a new repository in the given directory
4435 """create a new repository in the given directory
4437
4436
4438 Initialize a new repository in the given directory. If the given
4437 Initialize a new repository in the given directory. If the given
4439 directory does not exist, it will be created.
4438 directory does not exist, it will be created.
4440
4439
4441 If no directory is given, the current directory is used.
4440 If no directory is given, the current directory is used.
4442
4441
4443 It is possible to specify an ``ssh://`` URL as the destination.
4442 It is possible to specify an ``ssh://`` URL as the destination.
4444 See :hg:`help urls` for more information.
4443 See :hg:`help urls` for more information.
4445
4444
4446 Returns 0 on success.
4445 Returns 0 on success.
4447 """
4446 """
4448 opts = pycompat.byteskwargs(opts)
4447 opts = pycompat.byteskwargs(opts)
4449 path = urlutil.get_clone_path_obj(ui, dest)
4448 path = urlutil.get_clone_path_obj(ui, dest)
4450 peer = hg.peer(ui, opts, path, create=True)
4449 peer = hg.peer(ui, opts, path, create=True)
4451 peer.close()
4450 peer.close()
4452
4451
4453
4452
4454 @command(
4453 @command(
4455 b'locate',
4454 b'locate',
4456 [
4455 [
4457 (
4456 (
4458 b'r',
4457 b'r',
4459 b'rev',
4458 b'rev',
4460 b'',
4459 b'',
4461 _(b'search the repository as it is in REV'),
4460 _(b'search the repository as it is in REV'),
4462 _(b'REV'),
4461 _(b'REV'),
4463 ),
4462 ),
4464 (
4463 (
4465 b'0',
4464 b'0',
4466 b'print0',
4465 b'print0',
4467 None,
4466 None,
4468 _(b'end filenames with NUL, for use with xargs'),
4467 _(b'end filenames with NUL, for use with xargs'),
4469 ),
4468 ),
4470 (
4469 (
4471 b'f',
4470 b'f',
4472 b'fullpath',
4471 b'fullpath',
4473 None,
4472 None,
4474 _(b'print complete paths from the filesystem root'),
4473 _(b'print complete paths from the filesystem root'),
4475 ),
4474 ),
4476 ]
4475 ]
4477 + walkopts,
4476 + walkopts,
4478 _(b'[OPTION]... [PATTERN]...'),
4477 _(b'[OPTION]... [PATTERN]...'),
4479 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4478 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4480 )
4479 )
4481 def locate(ui, repo, *pats, **opts):
4480 def locate(ui, repo, *pats, **opts):
4482 """locate files matching specific patterns (DEPRECATED)
4481 """locate files matching specific patterns (DEPRECATED)
4483
4482
4484 Print files under Mercurial control in the working directory whose
4483 Print files under Mercurial control in the working directory whose
4485 names match the given patterns.
4484 names match the given patterns.
4486
4485
4487 By default, this command searches all directories in the working
4486 By default, this command searches all directories in the working
4488 directory. To search just the current directory and its
4487 directory. To search just the current directory and its
4489 subdirectories, use "--include .".
4488 subdirectories, use "--include .".
4490
4489
4491 If no patterns are given to match, this command prints the names
4490 If no patterns are given to match, this command prints the names
4492 of all files under Mercurial control in the working directory.
4491 of all files under Mercurial control in the working directory.
4493
4492
4494 If you want to feed the output of this command into the "xargs"
4493 If you want to feed the output of this command into the "xargs"
4495 command, use the -0 option to both this command and "xargs". This
4494 command, use the -0 option to both this command and "xargs". This
4496 will avoid the problem of "xargs" treating single filenames that
4495 will avoid the problem of "xargs" treating single filenames that
4497 contain whitespace as multiple filenames.
4496 contain whitespace as multiple filenames.
4498
4497
4499 See :hg:`help files` for a more versatile command.
4498 See :hg:`help files` for a more versatile command.
4500
4499
4501 Returns 0 if a match is found, 1 otherwise.
4500 Returns 0 if a match is found, 1 otherwise.
4502 """
4501 """
4503 opts = pycompat.byteskwargs(opts)
4502 opts = pycompat.byteskwargs(opts)
4504 if opts.get(b'print0'):
4503 if opts.get(b'print0'):
4505 end = b'\0'
4504 end = b'\0'
4506 else:
4505 else:
4507 end = b'\n'
4506 end = b'\n'
4508 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4507 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4509
4508
4510 ret = 1
4509 ret = 1
4511 m = scmutil.match(
4510 m = scmutil.match(
4512 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4511 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4513 )
4512 )
4514
4513
4515 ui.pager(b'locate')
4514 ui.pager(b'locate')
4516 if ctx.rev() is None:
4515 if ctx.rev() is None:
4517 # When run on the working copy, "locate" includes removed files, so
4516 # When run on the working copy, "locate" includes removed files, so
4518 # we get the list of files from the dirstate.
4517 # we get the list of files from the dirstate.
4519 filesgen = sorted(repo.dirstate.matches(m))
4518 filesgen = sorted(repo.dirstate.matches(m))
4520 else:
4519 else:
4521 filesgen = ctx.matches(m)
4520 filesgen = ctx.matches(m)
4522 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4521 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4523 for abs in filesgen:
4522 for abs in filesgen:
4524 if opts.get(b'fullpath'):
4523 if opts.get(b'fullpath'):
4525 ui.write(repo.wjoin(abs), end)
4524 ui.write(repo.wjoin(abs), end)
4526 else:
4525 else:
4527 ui.write(uipathfn(abs), end)
4526 ui.write(uipathfn(abs), end)
4528 ret = 0
4527 ret = 0
4529
4528
4530 return ret
4529 return ret
4531
4530
4532
4531
4533 @command(
4532 @command(
4534 b'log|history',
4533 b'log|history',
4535 [
4534 [
4536 (
4535 (
4537 b'f',
4536 b'f',
4538 b'follow',
4537 b'follow',
4539 None,
4538 None,
4540 _(
4539 _(
4541 b'follow changeset history, or file history across copies and renames'
4540 b'follow changeset history, or file history across copies and renames'
4542 ),
4541 ),
4543 ),
4542 ),
4544 (
4543 (
4545 b'',
4544 b'',
4546 b'follow-first',
4545 b'follow-first',
4547 None,
4546 None,
4548 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4547 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4549 ),
4548 ),
4550 (
4549 (
4551 b'd',
4550 b'd',
4552 b'date',
4551 b'date',
4553 b'',
4552 b'',
4554 _(b'show revisions matching date spec'),
4553 _(b'show revisions matching date spec'),
4555 _(b'DATE'),
4554 _(b'DATE'),
4556 ),
4555 ),
4557 (b'C', b'copies', None, _(b'show copied files')),
4556 (b'C', b'copies', None, _(b'show copied files')),
4558 (
4557 (
4559 b'k',
4558 b'k',
4560 b'keyword',
4559 b'keyword',
4561 [],
4560 [],
4562 _(b'do case-insensitive search for a given text'),
4561 _(b'do case-insensitive search for a given text'),
4563 _(b'TEXT'),
4562 _(b'TEXT'),
4564 ),
4563 ),
4565 (
4564 (
4566 b'r',
4565 b'r',
4567 b'rev',
4566 b'rev',
4568 [],
4567 [],
4569 _(b'revisions to select or follow from'),
4568 _(b'revisions to select or follow from'),
4570 _(b'REV'),
4569 _(b'REV'),
4571 ),
4570 ),
4572 (
4571 (
4573 b'L',
4572 b'L',
4574 b'line-range',
4573 b'line-range',
4575 [],
4574 [],
4576 _(b'follow line range of specified file (EXPERIMENTAL)'),
4575 _(b'follow line range of specified file (EXPERIMENTAL)'),
4577 _(b'FILE,RANGE'),
4576 _(b'FILE,RANGE'),
4578 ),
4577 ),
4579 (
4578 (
4580 b'',
4579 b'',
4581 b'removed',
4580 b'removed',
4582 None,
4581 None,
4583 _(b'include revisions where files were removed'),
4582 _(b'include revisions where files were removed'),
4584 ),
4583 ),
4585 (
4584 (
4586 b'm',
4585 b'm',
4587 b'only-merges',
4586 b'only-merges',
4588 None,
4587 None,
4589 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4588 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4590 ),
4589 ),
4591 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4590 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4592 (
4591 (
4593 b'',
4592 b'',
4594 b'only-branch',
4593 b'only-branch',
4595 [],
4594 [],
4596 _(
4595 _(
4597 b'show only changesets within the given named branch (DEPRECATED)'
4596 b'show only changesets within the given named branch (DEPRECATED)'
4598 ),
4597 ),
4599 _(b'BRANCH'),
4598 _(b'BRANCH'),
4600 ),
4599 ),
4601 (
4600 (
4602 b'b',
4601 b'b',
4603 b'branch',
4602 b'branch',
4604 [],
4603 [],
4605 _(b'show changesets within the given named branch'),
4604 _(b'show changesets within the given named branch'),
4606 _(b'BRANCH'),
4605 _(b'BRANCH'),
4607 ),
4606 ),
4608 (
4607 (
4609 b'B',
4608 b'B',
4610 b'bookmark',
4609 b'bookmark',
4611 [],
4610 [],
4612 _(b"show changesets within the given bookmark"),
4611 _(b"show changesets within the given bookmark"),
4613 _(b'BOOKMARK'),
4612 _(b'BOOKMARK'),
4614 ),
4613 ),
4615 (
4614 (
4616 b'P',
4615 b'P',
4617 b'prune',
4616 b'prune',
4618 [],
4617 [],
4619 _(b'do not display revision or any of its ancestors'),
4618 _(b'do not display revision or any of its ancestors'),
4620 _(b'REV'),
4619 _(b'REV'),
4621 ),
4620 ),
4622 ]
4621 ]
4623 + logopts
4622 + logopts
4624 + walkopts,
4623 + walkopts,
4625 _(b'[OPTION]... [FILE]'),
4624 _(b'[OPTION]... [FILE]'),
4626 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4625 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4627 helpbasic=True,
4626 helpbasic=True,
4628 inferrepo=True,
4627 inferrepo=True,
4629 intents={INTENT_READONLY},
4628 intents={INTENT_READONLY},
4630 )
4629 )
4631 def log(ui, repo, *pats, **opts):
4630 def log(ui, repo, *pats, **opts):
4632 """show revision history of entire repository or files
4631 """show revision history of entire repository or files
4633
4632
4634 Print the revision history of the specified files or the entire
4633 Print the revision history of the specified files or the entire
4635 project.
4634 project.
4636
4635
4637 If no revision range is specified, the default is ``tip:0`` unless
4636 If no revision range is specified, the default is ``tip:0`` unless
4638 --follow is set.
4637 --follow is set.
4639
4638
4640 File history is shown without following rename or copy history of
4639 File history is shown without following rename or copy history of
4641 files. Use -f/--follow with a filename to follow history across
4640 files. Use -f/--follow with a filename to follow history across
4642 renames and copies. --follow without a filename will only show
4641 renames and copies. --follow without a filename will only show
4643 ancestors of the starting revisions. The starting revisions can be
4642 ancestors of the starting revisions. The starting revisions can be
4644 specified by -r/--rev, which default to the working directory parent.
4643 specified by -r/--rev, which default to the working directory parent.
4645
4644
4646 By default this command prints revision number and changeset id,
4645 By default this command prints revision number and changeset id,
4647 tags, non-trivial parents, user, date and time, and a summary for
4646 tags, non-trivial parents, user, date and time, and a summary for
4648 each commit. When the -v/--verbose switch is used, the list of
4647 each commit. When the -v/--verbose switch is used, the list of
4649 changed files and full commit message are shown.
4648 changed files and full commit message are shown.
4650
4649
4651 With --graph the revisions are shown as an ASCII art DAG with the most
4650 With --graph the revisions are shown as an ASCII art DAG with the most
4652 recent changeset at the top.
4651 recent changeset at the top.
4653 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4652 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4654 involved in an unresolved merge conflict, '_' closes a branch,
4653 involved in an unresolved merge conflict, '_' closes a branch,
4655 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4654 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4656 changeset from the lines below is a parent of the 'o' merge on the same
4655 changeset from the lines below is a parent of the 'o' merge on the same
4657 line.
4656 line.
4658 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4657 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4659 of a '|' indicates one or more revisions in a path are omitted.
4658 of a '|' indicates one or more revisions in a path are omitted.
4660
4659
4661 .. container:: verbose
4660 .. container:: verbose
4662
4661
4663 Use -L/--line-range FILE,M:N options to follow the history of lines
4662 Use -L/--line-range FILE,M:N options to follow the history of lines
4664 from M to N in FILE. With -p/--patch only diff hunks affecting
4663 from M to N in FILE. With -p/--patch only diff hunks affecting
4665 specified line range will be shown. This option requires --follow;
4664 specified line range will be shown. This option requires --follow;
4666 it can be specified multiple times. Currently, this option is not
4665 it can be specified multiple times. Currently, this option is not
4667 compatible with --graph. This option is experimental.
4666 compatible with --graph. This option is experimental.
4668
4667
4669 .. note::
4668 .. note::
4670
4669
4671 :hg:`log --patch` may generate unexpected diff output for merge
4670 :hg:`log --patch` may generate unexpected diff output for merge
4672 changesets, as it will only compare the merge changeset against
4671 changesets, as it will only compare the merge changeset against
4673 its first parent. Also, only files different from BOTH parents
4672 its first parent. Also, only files different from BOTH parents
4674 will appear in files:.
4673 will appear in files:.
4675
4674
4676 .. note::
4675 .. note::
4677
4676
4678 For performance reasons, :hg:`log FILE` may omit duplicate changes
4677 For performance reasons, :hg:`log FILE` may omit duplicate changes
4679 made on branches and will not show removals or mode changes. To
4678 made on branches and will not show removals or mode changes. To
4680 see all such changes, use the --removed switch.
4679 see all such changes, use the --removed switch.
4681
4680
4682 .. container:: verbose
4681 .. container:: verbose
4683
4682
4684 .. note::
4683 .. note::
4685
4684
4686 The history resulting from -L/--line-range options depends on diff
4685 The history resulting from -L/--line-range options depends on diff
4687 options; for instance if white-spaces are ignored, respective changes
4686 options; for instance if white-spaces are ignored, respective changes
4688 with only white-spaces in specified line range will not be listed.
4687 with only white-spaces in specified line range will not be listed.
4689
4688
4690 .. container:: verbose
4689 .. container:: verbose
4691
4690
4692 Some examples:
4691 Some examples:
4693
4692
4694 - changesets with full descriptions and file lists::
4693 - changesets with full descriptions and file lists::
4695
4694
4696 hg log -v
4695 hg log -v
4697
4696
4698 - changesets ancestral to the working directory::
4697 - changesets ancestral to the working directory::
4699
4698
4700 hg log -f
4699 hg log -f
4701
4700
4702 - last 10 commits on the current branch::
4701 - last 10 commits on the current branch::
4703
4702
4704 hg log -l 10 -b .
4703 hg log -l 10 -b .
4705
4704
4706 - changesets showing all modifications of a file, including removals::
4705 - changesets showing all modifications of a file, including removals::
4707
4706
4708 hg log --removed file.c
4707 hg log --removed file.c
4709
4708
4710 - all changesets that touch a directory, with diffs, excluding merges::
4709 - all changesets that touch a directory, with diffs, excluding merges::
4711
4710
4712 hg log -Mp lib/
4711 hg log -Mp lib/
4713
4712
4714 - all revision numbers that match a keyword::
4713 - all revision numbers that match a keyword::
4715
4714
4716 hg log -k bug --template "{rev}\\n"
4715 hg log -k bug --template "{rev}\\n"
4717
4716
4718 - the full hash identifier of the working directory parent::
4717 - the full hash identifier of the working directory parent::
4719
4718
4720 hg log -r . --template "{node}\\n"
4719 hg log -r . --template "{node}\\n"
4721
4720
4722 - list available log templates::
4721 - list available log templates::
4723
4722
4724 hg log -T list
4723 hg log -T list
4725
4724
4726 - check if a given changeset is included in a tagged release::
4725 - check if a given changeset is included in a tagged release::
4727
4726
4728 hg log -r "a21ccf and ancestor(1.9)"
4727 hg log -r "a21ccf and ancestor(1.9)"
4729
4728
4730 - find all changesets by some user in a date range::
4729 - find all changesets by some user in a date range::
4731
4730
4732 hg log -k alice -d "may 2008 to jul 2008"
4731 hg log -k alice -d "may 2008 to jul 2008"
4733
4732
4734 - summary of all changesets after the last tag::
4733 - summary of all changesets after the last tag::
4735
4734
4736 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4735 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4737
4736
4738 - changesets touching lines 13 to 23 for file.c::
4737 - changesets touching lines 13 to 23 for file.c::
4739
4738
4740 hg log -L file.c,13:23
4739 hg log -L file.c,13:23
4741
4740
4742 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4741 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4743 main.c with patch::
4742 main.c with patch::
4744
4743
4745 hg log -L file.c,13:23 -L main.c,2:6 -p
4744 hg log -L file.c,13:23 -L main.c,2:6 -p
4746
4745
4747 See :hg:`help dates` for a list of formats valid for -d/--date.
4746 See :hg:`help dates` for a list of formats valid for -d/--date.
4748
4747
4749 See :hg:`help revisions` for more about specifying and ordering
4748 See :hg:`help revisions` for more about specifying and ordering
4750 revisions.
4749 revisions.
4751
4750
4752 See :hg:`help templates` for more about pre-packaged styles and
4751 See :hg:`help templates` for more about pre-packaged styles and
4753 specifying custom templates. The default template used by the log
4752 specifying custom templates. The default template used by the log
4754 command can be customized via the ``command-templates.log`` configuration
4753 command can be customized via the ``command-templates.log`` configuration
4755 setting.
4754 setting.
4756
4755
4757 Returns 0 on success.
4756 Returns 0 on success.
4758
4757
4759 """
4758 """
4760 opts = pycompat.byteskwargs(opts)
4759 opts = pycompat.byteskwargs(opts)
4761 linerange = opts.get(b'line_range')
4760 linerange = opts.get(b'line_range')
4762
4761
4763 if linerange and not opts.get(b'follow'):
4762 if linerange and not opts.get(b'follow'):
4764 raise error.InputError(_(b'--line-range requires --follow'))
4763 raise error.InputError(_(b'--line-range requires --follow'))
4765
4764
4766 if linerange and pats:
4765 if linerange and pats:
4767 # TODO: take pats as patterns with no line-range filter
4766 # TODO: take pats as patterns with no line-range filter
4768 raise error.InputError(
4767 raise error.InputError(
4769 _(b'FILE arguments are not compatible with --line-range option')
4768 _(b'FILE arguments are not compatible with --line-range option')
4770 )
4769 )
4771
4770
4772 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4771 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4773 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4772 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4774 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4773 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4775 if linerange:
4774 if linerange:
4776 # TODO: should follow file history from logcmdutil._initialrevs(),
4775 # TODO: should follow file history from logcmdutil._initialrevs(),
4777 # then filter the result by logcmdutil._makerevset() and --limit
4776 # then filter the result by logcmdutil._makerevset() and --limit
4778 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4777 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4779
4778
4780 getcopies = None
4779 getcopies = None
4781 if opts.get(b'copies'):
4780 if opts.get(b'copies'):
4782 endrev = None
4781 endrev = None
4783 if revs:
4782 if revs:
4784 endrev = revs.max() + 1
4783 endrev = revs.max() + 1
4785 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4784 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4786
4785
4787 ui.pager(b'log')
4786 ui.pager(b'log')
4788 displayer = logcmdutil.changesetdisplayer(
4787 displayer = logcmdutil.changesetdisplayer(
4789 ui, repo, opts, differ, buffered=True
4788 ui, repo, opts, differ, buffered=True
4790 )
4789 )
4791 if opts.get(b'graph'):
4790 if opts.get(b'graph'):
4792 displayfn = logcmdutil.displaygraphrevs
4791 displayfn = logcmdutil.displaygraphrevs
4793 else:
4792 else:
4794 displayfn = logcmdutil.displayrevs
4793 displayfn = logcmdutil.displayrevs
4795 displayfn(ui, repo, revs, displayer, getcopies)
4794 displayfn(ui, repo, revs, displayer, getcopies)
4796
4795
4797
4796
4798 @command(
4797 @command(
4799 b'manifest',
4798 b'manifest',
4800 [
4799 [
4801 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4800 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4802 (b'', b'all', False, _(b"list files from all revisions")),
4801 (b'', b'all', False, _(b"list files from all revisions")),
4803 ]
4802 ]
4804 + formatteropts,
4803 + formatteropts,
4805 _(b'[-r REV]'),
4804 _(b'[-r REV]'),
4806 helpcategory=command.CATEGORY_MAINTENANCE,
4805 helpcategory=command.CATEGORY_MAINTENANCE,
4807 intents={INTENT_READONLY},
4806 intents={INTENT_READONLY},
4808 )
4807 )
4809 def manifest(ui, repo, node=None, rev=None, **opts):
4808 def manifest(ui, repo, node=None, rev=None, **opts):
4810 """output the current or given revision of the project manifest
4809 """output the current or given revision of the project manifest
4811
4810
4812 Print a list of version controlled files for the given revision.
4811 Print a list of version controlled files for the given revision.
4813 If no revision is given, the first parent of the working directory
4812 If no revision is given, the first parent of the working directory
4814 is used, or the null revision if no revision is checked out.
4813 is used, or the null revision if no revision is checked out.
4815
4814
4816 With -v, print file permissions, symlink and executable bits.
4815 With -v, print file permissions, symlink and executable bits.
4817 With --debug, print file revision hashes.
4816 With --debug, print file revision hashes.
4818
4817
4819 If option --all is specified, the list of all files from all revisions
4818 If option --all is specified, the list of all files from all revisions
4820 is printed. This includes deleted and renamed files.
4819 is printed. This includes deleted and renamed files.
4821
4820
4822 Returns 0 on success.
4821 Returns 0 on success.
4823 """
4822 """
4824 opts = pycompat.byteskwargs(opts)
4823 opts = pycompat.byteskwargs(opts)
4825 fm = ui.formatter(b'manifest', opts)
4824 fm = ui.formatter(b'manifest', opts)
4826
4825
4827 if opts.get(b'all'):
4826 if opts.get(b'all'):
4828 if rev or node:
4827 if rev or node:
4829 raise error.InputError(_(b"can't specify a revision with --all"))
4828 raise error.InputError(_(b"can't specify a revision with --all"))
4830
4829
4831 res = set()
4830 res = set()
4832 for rev in repo:
4831 for rev in repo:
4833 ctx = repo[rev]
4832 ctx = repo[rev]
4834 res |= set(ctx.files())
4833 res |= set(ctx.files())
4835
4834
4836 ui.pager(b'manifest')
4835 ui.pager(b'manifest')
4837 for f in sorted(res):
4836 for f in sorted(res):
4838 fm.startitem()
4837 fm.startitem()
4839 fm.write(b"path", b'%s\n', f)
4838 fm.write(b"path", b'%s\n', f)
4840 fm.end()
4839 fm.end()
4841 return
4840 return
4842
4841
4843 if rev and node:
4842 if rev and node:
4844 raise error.InputError(_(b"please specify just one revision"))
4843 raise error.InputError(_(b"please specify just one revision"))
4845
4844
4846 if not node:
4845 if not node:
4847 node = rev
4846 node = rev
4848
4847
4849 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4848 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4850 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4849 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4851 if node:
4850 if node:
4852 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4851 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4853 ctx = logcmdutil.revsingle(repo, node)
4852 ctx = logcmdutil.revsingle(repo, node)
4854 mf = ctx.manifest()
4853 mf = ctx.manifest()
4855 ui.pager(b'manifest')
4854 ui.pager(b'manifest')
4856 for f in ctx:
4855 for f in ctx:
4857 fm.startitem()
4856 fm.startitem()
4858 fm.context(ctx=ctx)
4857 fm.context(ctx=ctx)
4859 fl = ctx[f].flags()
4858 fl = ctx[f].flags()
4860 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4859 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4861 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4860 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4862 fm.write(b'path', b'%s\n', f)
4861 fm.write(b'path', b'%s\n', f)
4863 fm.end()
4862 fm.end()
4864
4863
4865
4864
4866 @command(
4865 @command(
4867 b'merge',
4866 b'merge',
4868 [
4867 [
4869 (
4868 (
4870 b'f',
4869 b'f',
4871 b'force',
4870 b'force',
4872 None,
4871 None,
4873 _(b'force a merge including outstanding changes (DEPRECATED)'),
4872 _(b'force a merge including outstanding changes (DEPRECATED)'),
4874 ),
4873 ),
4875 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4874 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4876 (
4875 (
4877 b'P',
4876 b'P',
4878 b'preview',
4877 b'preview',
4879 None,
4878 None,
4880 _(b'review revisions to merge (no merge is performed)'),
4879 _(b'review revisions to merge (no merge is performed)'),
4881 ),
4880 ),
4882 (b'', b'abort', None, _(b'abort the ongoing merge')),
4881 (b'', b'abort', None, _(b'abort the ongoing merge')),
4883 ]
4882 ]
4884 + mergetoolopts,
4883 + mergetoolopts,
4885 _(b'[-P] [[-r] REV]'),
4884 _(b'[-P] [[-r] REV]'),
4886 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4885 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4887 helpbasic=True,
4886 helpbasic=True,
4888 )
4887 )
4889 def merge(ui, repo, node=None, **opts):
4888 def merge(ui, repo, node=None, **opts):
4890 """merge another revision into working directory
4889 """merge another revision into working directory
4891
4890
4892 The current working directory is updated with all changes made in
4891 The current working directory is updated with all changes made in
4893 the requested revision since the last common predecessor revision.
4892 the requested revision since the last common predecessor revision.
4894
4893
4895 Files that changed between either parent are marked as changed for
4894 Files that changed between either parent are marked as changed for
4896 the next commit and a commit must be performed before any further
4895 the next commit and a commit must be performed before any further
4897 updates to the repository are allowed. The next commit will have
4896 updates to the repository are allowed. The next commit will have
4898 two parents.
4897 two parents.
4899
4898
4900 ``--tool`` can be used to specify the merge tool used for file
4899 ``--tool`` can be used to specify the merge tool used for file
4901 merges. It overrides the HGMERGE environment variable and your
4900 merges. It overrides the HGMERGE environment variable and your
4902 configuration files. See :hg:`help merge-tools` for options.
4901 configuration files. See :hg:`help merge-tools` for options.
4903
4902
4904 If no revision is specified, the working directory's parent is a
4903 If no revision is specified, the working directory's parent is a
4905 head revision, and the current branch contains exactly one other
4904 head revision, and the current branch contains exactly one other
4906 head, the other head is merged with by default. Otherwise, an
4905 head, the other head is merged with by default. Otherwise, an
4907 explicit revision with which to merge must be provided.
4906 explicit revision with which to merge must be provided.
4908
4907
4909 See :hg:`help resolve` for information on handling file conflicts.
4908 See :hg:`help resolve` for information on handling file conflicts.
4910
4909
4911 To undo an uncommitted merge, use :hg:`merge --abort` which
4910 To undo an uncommitted merge, use :hg:`merge --abort` which
4912 will check out a clean copy of the original merge parent, losing
4911 will check out a clean copy of the original merge parent, losing
4913 all changes.
4912 all changes.
4914
4913
4915 Returns 0 on success, 1 if there are unresolved files.
4914 Returns 0 on success, 1 if there are unresolved files.
4916 """
4915 """
4917
4916
4918 opts = pycompat.byteskwargs(opts)
4917 opts = pycompat.byteskwargs(opts)
4919 abort = opts.get(b'abort')
4918 abort = opts.get(b'abort')
4920 if abort and repo.dirstate.p2() == repo.nullid:
4919 if abort and repo.dirstate.p2() == repo.nullid:
4921 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4920 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4922 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4921 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4923 if abort:
4922 if abort:
4924 state = cmdutil.getunfinishedstate(repo)
4923 state = cmdutil.getunfinishedstate(repo)
4925 if state and state._opname != b'merge':
4924 if state and state._opname != b'merge':
4926 raise error.StateError(
4925 raise error.StateError(
4927 _(b'cannot abort merge with %s in progress') % (state._opname),
4926 _(b'cannot abort merge with %s in progress') % (state._opname),
4928 hint=state.hint(),
4927 hint=state.hint(),
4929 )
4928 )
4930 if node:
4929 if node:
4931 raise error.InputError(_(b"cannot specify a node with --abort"))
4930 raise error.InputError(_(b"cannot specify a node with --abort"))
4932 return hg.abortmerge(repo.ui, repo)
4931 return hg.abortmerge(repo.ui, repo)
4933
4932
4934 if opts.get(b'rev') and node:
4933 if opts.get(b'rev') and node:
4935 raise error.InputError(_(b"please specify just one revision"))
4934 raise error.InputError(_(b"please specify just one revision"))
4936 if not node:
4935 if not node:
4937 node = opts.get(b'rev')
4936 node = opts.get(b'rev')
4938
4937
4939 if node:
4938 if node:
4940 ctx = logcmdutil.revsingle(repo, node)
4939 ctx = logcmdutil.revsingle(repo, node)
4941 else:
4940 else:
4942 if ui.configbool(b'commands', b'merge.require-rev'):
4941 if ui.configbool(b'commands', b'merge.require-rev'):
4943 raise error.InputError(
4942 raise error.InputError(
4944 _(
4943 _(
4945 b'configuration requires specifying revision to merge '
4944 b'configuration requires specifying revision to merge '
4946 b'with'
4945 b'with'
4947 )
4946 )
4948 )
4947 )
4949 ctx = repo[destutil.destmerge(repo)]
4948 ctx = repo[destutil.destmerge(repo)]
4950
4949
4951 if ctx.node() is None:
4950 if ctx.node() is None:
4952 raise error.InputError(
4951 raise error.InputError(
4953 _(b'merging with the working copy has no effect')
4952 _(b'merging with the working copy has no effect')
4954 )
4953 )
4955
4954
4956 if opts.get(b'preview'):
4955 if opts.get(b'preview'):
4957 # find nodes that are ancestors of p2 but not of p1
4956 # find nodes that are ancestors of p2 but not of p1
4958 p1 = repo[b'.'].node()
4957 p1 = repo[b'.'].node()
4959 p2 = ctx.node()
4958 p2 = ctx.node()
4960 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4959 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4961
4960
4962 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4961 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4963 for node in nodes:
4962 for node in nodes:
4964 displayer.show(repo[node])
4963 displayer.show(repo[node])
4965 displayer.close()
4964 displayer.close()
4966 return 0
4965 return 0
4967
4966
4968 # ui.forcemerge is an internal variable, do not document
4967 # ui.forcemerge is an internal variable, do not document
4969 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4968 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4970 with ui.configoverride(overrides, b'merge'):
4969 with ui.configoverride(overrides, b'merge'):
4971 force = opts.get(b'force')
4970 force = opts.get(b'force')
4972 labels = [b'working copy', b'merge rev', b'common ancestor']
4971 labels = [b'working copy', b'merge rev', b'common ancestor']
4973 return hg.merge(ctx, force=force, labels=labels)
4972 return hg.merge(ctx, force=force, labels=labels)
4974
4973
4975
4974
4976 statemod.addunfinished(
4975 statemod.addunfinished(
4977 b'merge',
4976 b'merge',
4978 fname=None,
4977 fname=None,
4979 clearable=True,
4978 clearable=True,
4980 allowcommit=True,
4979 allowcommit=True,
4981 cmdmsg=_(b'outstanding uncommitted merge'),
4980 cmdmsg=_(b'outstanding uncommitted merge'),
4982 abortfunc=hg.abortmerge,
4981 abortfunc=hg.abortmerge,
4983 statushint=_(
4982 statushint=_(
4984 b'To continue: hg commit\nTo abort: hg merge --abort'
4983 b'To continue: hg commit\nTo abort: hg merge --abort'
4985 ),
4984 ),
4986 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4985 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4987 )
4986 )
4988
4987
4989
4988
4990 @command(
4989 @command(
4991 b'outgoing|out',
4990 b'outgoing|out',
4992 [
4991 [
4993 (
4992 (
4994 b'f',
4993 b'f',
4995 b'force',
4994 b'force',
4996 None,
4995 None,
4997 _(b'run even when the destination is unrelated'),
4996 _(b'run even when the destination is unrelated'),
4998 ),
4997 ),
4999 (
4998 (
5000 b'r',
4999 b'r',
5001 b'rev',
5000 b'rev',
5002 [],
5001 [],
5003 _(b'a changeset intended to be included in the destination'),
5002 _(b'a changeset intended to be included in the destination'),
5004 _(b'REV'),
5003 _(b'REV'),
5005 ),
5004 ),
5006 (b'n', b'newest-first', None, _(b'show newest record first')),
5005 (b'n', b'newest-first', None, _(b'show newest record first')),
5007 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
5006 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
5008 (
5007 (
5009 b'b',
5008 b'b',
5010 b'branch',
5009 b'branch',
5011 [],
5010 [],
5012 _(b'a specific branch you would like to push'),
5011 _(b'a specific branch you would like to push'),
5013 _(b'BRANCH'),
5012 _(b'BRANCH'),
5014 ),
5013 ),
5015 ]
5014 ]
5016 + logopts
5015 + logopts
5017 + remoteopts
5016 + remoteopts
5018 + subrepoopts,
5017 + subrepoopts,
5019 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
5018 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
5020 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5019 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5021 )
5020 )
5022 def outgoing(ui, repo, *dests, **opts):
5021 def outgoing(ui, repo, *dests, **opts):
5023 """show changesets not found in the destination
5022 """show changesets not found in the destination
5024
5023
5025 Show changesets not found in the specified destination repository
5024 Show changesets not found in the specified destination repository
5026 or the default push location. These are the changesets that would
5025 or the default push location. These are the changesets that would
5027 be pushed if a push was requested.
5026 be pushed if a push was requested.
5028
5027
5029 See pull for details of valid destination formats.
5028 See pull for details of valid destination formats.
5030
5029
5031 .. container:: verbose
5030 .. container:: verbose
5032
5031
5033 With -B/--bookmarks, the result of bookmark comparison between
5032 With -B/--bookmarks, the result of bookmark comparison between
5034 local and remote repositories is displayed. With -v/--verbose,
5033 local and remote repositories is displayed. With -v/--verbose,
5035 status is also displayed for each bookmark like below::
5034 status is also displayed for each bookmark like below::
5036
5035
5037 BM1 01234567890a added
5036 BM1 01234567890a added
5038 BM2 deleted
5037 BM2 deleted
5039 BM3 234567890abc advanced
5038 BM3 234567890abc advanced
5040 BM4 34567890abcd diverged
5039 BM4 34567890abcd diverged
5041 BM5 4567890abcde changed
5040 BM5 4567890abcde changed
5042
5041
5043 The action taken when pushing depends on the
5042 The action taken when pushing depends on the
5044 status of each bookmark:
5043 status of each bookmark:
5045
5044
5046 :``added``: push with ``-B`` will create it
5045 :``added``: push with ``-B`` will create it
5047 :``deleted``: push with ``-B`` will delete it
5046 :``deleted``: push with ``-B`` will delete it
5048 :``advanced``: push will update it
5047 :``advanced``: push will update it
5049 :``diverged``: push with ``-B`` will update it
5048 :``diverged``: push with ``-B`` will update it
5050 :``changed``: push with ``-B`` will update it
5049 :``changed``: push with ``-B`` will update it
5051
5050
5052 From the point of view of pushing behavior, bookmarks
5051 From the point of view of pushing behavior, bookmarks
5053 existing only in the remote repository are treated as
5052 existing only in the remote repository are treated as
5054 ``deleted``, even if it is in fact added remotely.
5053 ``deleted``, even if it is in fact added remotely.
5055
5054
5056 Returns 0 if there are outgoing changes, 1 otherwise.
5055 Returns 0 if there are outgoing changes, 1 otherwise.
5057 """
5056 """
5058 opts = pycompat.byteskwargs(opts)
5057 opts = pycompat.byteskwargs(opts)
5059 if opts.get(b'bookmarks'):
5058 if opts.get(b'bookmarks'):
5060 for path in urlutil.get_push_paths(repo, ui, dests):
5059 for path in urlutil.get_push_paths(repo, ui, dests):
5061 other = hg.peer(repo, opts, path)
5060 other = hg.peer(repo, opts, path)
5062 try:
5061 try:
5063 if b'bookmarks' not in other.listkeys(b'namespaces'):
5062 if b'bookmarks' not in other.listkeys(b'namespaces'):
5064 ui.warn(_(b"remote doesn't support bookmarks\n"))
5063 ui.warn(_(b"remote doesn't support bookmarks\n"))
5065 return 0
5064 return 0
5066 ui.status(
5065 ui.status(
5067 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
5066 _(b'comparing with %s\n') % urlutil.hidepassword(path.loc)
5068 )
5067 )
5069 ui.pager(b'outgoing')
5068 ui.pager(b'outgoing')
5070 return bookmarks.outgoing(ui, repo, other)
5069 return bookmarks.outgoing(ui, repo, other)
5071 finally:
5070 finally:
5072 other.close()
5071 other.close()
5073
5072
5074 return hg.outgoing(ui, repo, dests, opts)
5073 return hg.outgoing(ui, repo, dests, opts)
5075
5074
5076
5075
5077 @command(
5076 @command(
5078 b'parents',
5077 b'parents',
5079 [
5078 [
5080 (
5079 (
5081 b'r',
5080 b'r',
5082 b'rev',
5081 b'rev',
5083 b'',
5082 b'',
5084 _(b'show parents of the specified revision'),
5083 _(b'show parents of the specified revision'),
5085 _(b'REV'),
5084 _(b'REV'),
5086 ),
5085 ),
5087 ]
5086 ]
5088 + templateopts,
5087 + templateopts,
5089 _(b'[-r REV] [FILE]'),
5088 _(b'[-r REV] [FILE]'),
5090 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5089 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5091 inferrepo=True,
5090 inferrepo=True,
5092 )
5091 )
5093 def parents(ui, repo, file_=None, **opts):
5092 def parents(ui, repo, file_=None, **opts):
5094 """show the parents of the working directory or revision (DEPRECATED)
5093 """show the parents of the working directory or revision (DEPRECATED)
5095
5094
5096 Print the working directory's parent revisions. If a revision is
5095 Print the working directory's parent revisions. If a revision is
5097 given via -r/--rev, the parent of that revision will be printed.
5096 given via -r/--rev, the parent of that revision will be printed.
5098 If a file argument is given, the revision in which the file was
5097 If a file argument is given, the revision in which the file was
5099 last changed (before the working directory revision or the
5098 last changed (before the working directory revision or the
5100 argument to --rev if given) is printed.
5099 argument to --rev if given) is printed.
5101
5100
5102 This command is equivalent to::
5101 This command is equivalent to::
5103
5102
5104 hg log -r "p1()+p2()" or
5103 hg log -r "p1()+p2()" or
5105 hg log -r "p1(REV)+p2(REV)" or
5104 hg log -r "p1(REV)+p2(REV)" or
5106 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5105 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5107 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5106 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5108
5107
5109 See :hg:`summary` and :hg:`help revsets` for related information.
5108 See :hg:`summary` and :hg:`help revsets` for related information.
5110
5109
5111 Returns 0 on success.
5110 Returns 0 on success.
5112 """
5111 """
5113
5112
5114 opts = pycompat.byteskwargs(opts)
5113 opts = pycompat.byteskwargs(opts)
5115 rev = opts.get(b'rev')
5114 rev = opts.get(b'rev')
5116 if rev:
5115 if rev:
5117 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5116 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5118 ctx = logcmdutil.revsingle(repo, rev, None)
5117 ctx = logcmdutil.revsingle(repo, rev, None)
5119
5118
5120 if file_:
5119 if file_:
5121 m = scmutil.match(ctx, (file_,), opts)
5120 m = scmutil.match(ctx, (file_,), opts)
5122 if m.anypats() or len(m.files()) != 1:
5121 if m.anypats() or len(m.files()) != 1:
5123 raise error.InputError(_(b'can only specify an explicit filename'))
5122 raise error.InputError(_(b'can only specify an explicit filename'))
5124 file_ = m.files()[0]
5123 file_ = m.files()[0]
5125 filenodes = []
5124 filenodes = []
5126 for cp in ctx.parents():
5125 for cp in ctx.parents():
5127 if not cp:
5126 if not cp:
5128 continue
5127 continue
5129 try:
5128 try:
5130 filenodes.append(cp.filenode(file_))
5129 filenodes.append(cp.filenode(file_))
5131 except error.LookupError:
5130 except error.LookupError:
5132 pass
5131 pass
5133 if not filenodes:
5132 if not filenodes:
5134 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5133 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5135 p = []
5134 p = []
5136 for fn in filenodes:
5135 for fn in filenodes:
5137 fctx = repo.filectx(file_, fileid=fn)
5136 fctx = repo.filectx(file_, fileid=fn)
5138 p.append(fctx.node())
5137 p.append(fctx.node())
5139 else:
5138 else:
5140 p = [cp.node() for cp in ctx.parents()]
5139 p = [cp.node() for cp in ctx.parents()]
5141
5140
5142 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5141 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5143 for n in p:
5142 for n in p:
5144 if n != repo.nullid:
5143 if n != repo.nullid:
5145 displayer.show(repo[n])
5144 displayer.show(repo[n])
5146 displayer.close()
5145 displayer.close()
5147
5146
5148
5147
5149 @command(
5148 @command(
5150 b'paths',
5149 b'paths',
5151 formatteropts,
5150 formatteropts,
5152 _(b'[NAME]'),
5151 _(b'[NAME]'),
5153 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5152 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5154 optionalrepo=True,
5153 optionalrepo=True,
5155 intents={INTENT_READONLY},
5154 intents={INTENT_READONLY},
5156 )
5155 )
5157 def paths(ui, repo, search=None, **opts):
5156 def paths(ui, repo, search=None, **opts):
5158 """show aliases for remote repositories
5157 """show aliases for remote repositories
5159
5158
5160 Show definition of symbolic path name NAME. If no name is given,
5159 Show definition of symbolic path name NAME. If no name is given,
5161 show definition of all available names.
5160 show definition of all available names.
5162
5161
5163 Option -q/--quiet suppresses all output when searching for NAME
5162 Option -q/--quiet suppresses all output when searching for NAME
5164 and shows only the path names when listing all definitions.
5163 and shows only the path names when listing all definitions.
5165
5164
5166 Path names are defined in the [paths] section of your
5165 Path names are defined in the [paths] section of your
5167 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5166 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5168 repository, ``.hg/hgrc`` is used, too.
5167 repository, ``.hg/hgrc`` is used, too.
5169
5168
5170 The path names ``default`` and ``default-push`` have a special
5169 The path names ``default`` and ``default-push`` have a special
5171 meaning. When performing a push or pull operation, they are used
5170 meaning. When performing a push or pull operation, they are used
5172 as fallbacks if no location is specified on the command-line.
5171 as fallbacks if no location is specified on the command-line.
5173 When ``default-push`` is set, it will be used for push and
5172 When ``default-push`` is set, it will be used for push and
5174 ``default`` will be used for pull; otherwise ``default`` is used
5173 ``default`` will be used for pull; otherwise ``default`` is used
5175 as the fallback for both. When cloning a repository, the clone
5174 as the fallback for both. When cloning a repository, the clone
5176 source is written as ``default`` in ``.hg/hgrc``.
5175 source is written as ``default`` in ``.hg/hgrc``.
5177
5176
5178 .. note::
5177 .. note::
5179
5178
5180 ``default`` and ``default-push`` apply to all inbound (e.g.
5179 ``default`` and ``default-push`` apply to all inbound (e.g.
5181 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5180 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5182 and :hg:`bundle`) operations.
5181 and :hg:`bundle`) operations.
5183
5182
5184 See :hg:`help urls` for more information.
5183 See :hg:`help urls` for more information.
5185
5184
5186 .. container:: verbose
5185 .. container:: verbose
5187
5186
5188 Template:
5187 Template:
5189
5188
5190 The following keywords are supported. See also :hg:`help templates`.
5189 The following keywords are supported. See also :hg:`help templates`.
5191
5190
5192 :name: String. Symbolic name of the path alias.
5191 :name: String. Symbolic name of the path alias.
5193 :pushurl: String. URL for push operations.
5192 :pushurl: String. URL for push operations.
5194 :url: String. URL or directory path for the other operations.
5193 :url: String. URL or directory path for the other operations.
5195
5194
5196 Returns 0 on success.
5195 Returns 0 on success.
5197 """
5196 """
5198
5197
5199 opts = pycompat.byteskwargs(opts)
5198 opts = pycompat.byteskwargs(opts)
5200
5199
5201 pathitems = urlutil.list_paths(ui, search)
5200 pathitems = urlutil.list_paths(ui, search)
5202 ui.pager(b'paths')
5201 ui.pager(b'paths')
5203
5202
5204 fm = ui.formatter(b'paths', opts)
5203 fm = ui.formatter(b'paths', opts)
5205 if fm.isplain():
5204 if fm.isplain():
5206 hidepassword = urlutil.hidepassword
5205 hidepassword = urlutil.hidepassword
5207 else:
5206 else:
5208 hidepassword = bytes
5207 hidepassword = bytes
5209 if ui.quiet:
5208 if ui.quiet:
5210 namefmt = b'%s\n'
5209 namefmt = b'%s\n'
5211 else:
5210 else:
5212 namefmt = b'%s = '
5211 namefmt = b'%s = '
5213 showsubopts = not search and not ui.quiet
5212 showsubopts = not search and not ui.quiet
5214
5213
5215 for name, path in pathitems:
5214 for name, path in pathitems:
5216 fm.startitem()
5215 fm.startitem()
5217 fm.condwrite(not search, b'name', namefmt, name)
5216 fm.condwrite(not search, b'name', namefmt, name)
5218 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5217 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5219 for subopt, value in sorted(path.suboptions.items()):
5218 for subopt, value in sorted(path.suboptions.items()):
5220 assert subopt not in (b'name', b'url')
5219 assert subopt not in (b'name', b'url')
5221 if showsubopts:
5220 if showsubopts:
5222 fm.plain(b'%s:%s = ' % (name, subopt))
5221 fm.plain(b'%s:%s = ' % (name, subopt))
5223 display = urlutil.path_suboptions_display[subopt]
5222 display = urlutil.path_suboptions_display[subopt]
5224 value = display(value)
5223 value = display(value)
5225 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5224 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5226
5225
5227 fm.end()
5226 fm.end()
5228
5227
5229 if search and not pathitems:
5228 if search and not pathitems:
5230 if not ui.quiet:
5229 if not ui.quiet:
5231 ui.warn(_(b"not found!\n"))
5230 ui.warn(_(b"not found!\n"))
5232 return 1
5231 return 1
5233 else:
5232 else:
5234 return 0
5233 return 0
5235
5234
5236
5235
5237 @command(
5236 @command(
5238 b'phase',
5237 b'phase',
5239 [
5238 [
5240 (b'p', b'public', False, _(b'set changeset phase to public')),
5239 (b'p', b'public', False, _(b'set changeset phase to public')),
5241 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5240 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5242 (b's', b'secret', False, _(b'set changeset phase to secret')),
5241 (b's', b'secret', False, _(b'set changeset phase to secret')),
5243 (b'f', b'force', False, _(b'allow to move boundary backward')),
5242 (b'f', b'force', False, _(b'allow to move boundary backward')),
5244 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5243 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5245 ],
5244 ],
5246 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5245 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5247 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5246 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5248 )
5247 )
5249 def phase(ui, repo, *revs, **opts):
5248 def phase(ui, repo, *revs, **opts):
5250 """set or show the current phase name
5249 """set or show the current phase name
5251
5250
5252 With no argument, show the phase name of the current revision(s).
5251 With no argument, show the phase name of the current revision(s).
5253
5252
5254 With one of -p/--public, -d/--draft or -s/--secret, change the
5253 With one of -p/--public, -d/--draft or -s/--secret, change the
5255 phase value of the specified revisions.
5254 phase value of the specified revisions.
5256
5255
5257 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5256 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5258 lower phase to a higher phase. Phases are ordered as follows::
5257 lower phase to a higher phase. Phases are ordered as follows::
5259
5258
5260 public < draft < secret
5259 public < draft < secret
5261
5260
5262 Returns 0 on success, 1 if some phases could not be changed.
5261 Returns 0 on success, 1 if some phases could not be changed.
5263
5262
5264 (For more information about the phases concept, see :hg:`help phases`.)
5263 (For more information about the phases concept, see :hg:`help phases`.)
5265 """
5264 """
5266 opts = pycompat.byteskwargs(opts)
5265 opts = pycompat.byteskwargs(opts)
5267 # search for a unique phase argument
5266 # search for a unique phase argument
5268 targetphase = None
5267 targetphase = None
5269 for idx, name in enumerate(phases.cmdphasenames):
5268 for idx, name in enumerate(phases.cmdphasenames):
5270 if opts[name]:
5269 if opts[name]:
5271 if targetphase is not None:
5270 if targetphase is not None:
5272 raise error.InputError(_(b'only one phase can be specified'))
5271 raise error.InputError(_(b'only one phase can be specified'))
5273 targetphase = idx
5272 targetphase = idx
5274
5273
5275 # look for specified revision
5274 # look for specified revision
5276 revs = list(revs)
5275 revs = list(revs)
5277 revs.extend(opts[b'rev'])
5276 revs.extend(opts[b'rev'])
5278 if revs:
5277 if revs:
5279 revs = logcmdutil.revrange(repo, revs)
5278 revs = logcmdutil.revrange(repo, revs)
5280 else:
5279 else:
5281 # display both parents as the second parent phase can influence
5280 # display both parents as the second parent phase can influence
5282 # the phase of a merge commit
5281 # the phase of a merge commit
5283 revs = [c.rev() for c in repo[None].parents()]
5282 revs = [c.rev() for c in repo[None].parents()]
5284
5283
5285 ret = 0
5284 ret = 0
5286 if targetphase is None:
5285 if targetphase is None:
5287 # display
5286 # display
5288 for r in revs:
5287 for r in revs:
5289 ctx = repo[r]
5288 ctx = repo[r]
5290 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5289 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5291 else:
5290 else:
5292 with repo.lock(), repo.transaction(b"phase") as tr:
5291 with repo.lock(), repo.transaction(b"phase") as tr:
5293 # set phase
5292 # set phase
5294 if not revs:
5293 if not revs:
5295 raise error.InputError(_(b'empty revision set'))
5294 raise error.InputError(_(b'empty revision set'))
5296 nodes = [repo[r].node() for r in revs]
5295 nodes = [repo[r].node() for r in revs]
5297 # moving revision from public to draft may hide them
5296 # moving revision from public to draft may hide them
5298 # We have to check result on an unfiltered repository
5297 # We have to check result on an unfiltered repository
5299 unfi = repo.unfiltered()
5298 unfi = repo.unfiltered()
5300 getphase = unfi._phasecache.phase
5299 getphase = unfi._phasecache.phase
5301 olddata = [getphase(unfi, r) for r in unfi]
5300 olddata = [getphase(unfi, r) for r in unfi]
5302 phases.advanceboundary(repo, tr, targetphase, nodes)
5301 phases.advanceboundary(repo, tr, targetphase, nodes)
5303 if opts[b'force']:
5302 if opts[b'force']:
5304 phases.retractboundary(repo, tr, targetphase, nodes)
5303 phases.retractboundary(repo, tr, targetphase, nodes)
5305 getphase = unfi._phasecache.phase
5304 getphase = unfi._phasecache.phase
5306 newdata = [getphase(unfi, r) for r in unfi]
5305 newdata = [getphase(unfi, r) for r in unfi]
5307 changes = sum(newdata[r] != olddata[r] for r in unfi)
5306 changes = sum(newdata[r] != olddata[r] for r in unfi)
5308 cl = unfi.changelog
5307 cl = unfi.changelog
5309 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5308 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5310 if rejected:
5309 if rejected:
5311 ui.warn(
5310 ui.warn(
5312 _(
5311 _(
5313 b'cannot move %i changesets to a higher '
5312 b'cannot move %i changesets to a higher '
5314 b'phase, use --force\n'
5313 b'phase, use --force\n'
5315 )
5314 )
5316 % len(rejected)
5315 % len(rejected)
5317 )
5316 )
5318 ret = 1
5317 ret = 1
5319 if changes:
5318 if changes:
5320 msg = _(b'phase changed for %i changesets\n') % changes
5319 msg = _(b'phase changed for %i changesets\n') % changes
5321 if ret:
5320 if ret:
5322 ui.status(msg)
5321 ui.status(msg)
5323 else:
5322 else:
5324 ui.note(msg)
5323 ui.note(msg)
5325 else:
5324 else:
5326 ui.warn(_(b'no phases changed\n'))
5325 ui.warn(_(b'no phases changed\n'))
5327 return ret
5326 return ret
5328
5327
5329
5328
5330 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5329 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5331 """Run after a changegroup has been added via pull/unbundle
5330 """Run after a changegroup has been added via pull/unbundle
5332
5331
5333 This takes arguments below:
5332 This takes arguments below:
5334
5333
5335 :modheads: change of heads by pull/unbundle
5334 :modheads: change of heads by pull/unbundle
5336 :optupdate: updating working directory is needed or not
5335 :optupdate: updating working directory is needed or not
5337 :checkout: update destination revision (or None to default destination)
5336 :checkout: update destination revision (or None to default destination)
5338 :brev: a name, which might be a bookmark to be activated after updating
5337 :brev: a name, which might be a bookmark to be activated after updating
5339
5338
5340 return True if update raise any conflict, False otherwise.
5339 return True if update raise any conflict, False otherwise.
5341 """
5340 """
5342 if modheads == 0:
5341 if modheads == 0:
5343 return False
5342 return False
5344 if optupdate:
5343 if optupdate:
5345 try:
5344 try:
5346 return hg.updatetotally(ui, repo, checkout, brev)
5345 return hg.updatetotally(ui, repo, checkout, brev)
5347 except error.UpdateAbort as inst:
5346 except error.UpdateAbort as inst:
5348 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5347 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5349 hint = inst.hint
5348 hint = inst.hint
5350 raise error.UpdateAbort(msg, hint=hint)
5349 raise error.UpdateAbort(msg, hint=hint)
5351 if modheads is not None and modheads > 1:
5350 if modheads is not None and modheads > 1:
5352 currentbranchheads = len(repo.branchheads())
5351 currentbranchheads = len(repo.branchheads())
5353 if currentbranchheads == modheads:
5352 if currentbranchheads == modheads:
5354 ui.status(
5353 ui.status(
5355 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5354 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5356 )
5355 )
5357 elif currentbranchheads > 1:
5356 elif currentbranchheads > 1:
5358 ui.status(
5357 ui.status(
5359 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5358 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5360 )
5359 )
5361 else:
5360 else:
5362 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5361 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5363 elif not ui.configbool(b'commands', b'update.requiredest'):
5362 elif not ui.configbool(b'commands', b'update.requiredest'):
5364 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5363 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5365 return False
5364 return False
5366
5365
5367
5366
5368 @command(
5367 @command(
5369 b'pull',
5368 b'pull',
5370 [
5369 [
5371 (
5370 (
5372 b'u',
5371 b'u',
5373 b'update',
5372 b'update',
5374 None,
5373 None,
5375 _(b'update to new branch head if new descendants were pulled'),
5374 _(b'update to new branch head if new descendants were pulled'),
5376 ),
5375 ),
5377 (
5376 (
5378 b'f',
5377 b'f',
5379 b'force',
5378 b'force',
5380 None,
5379 None,
5381 _(b'run even when remote repository is unrelated'),
5380 _(b'run even when remote repository is unrelated'),
5382 ),
5381 ),
5383 (
5382 (
5384 b'',
5383 b'',
5385 b'confirm',
5384 b'confirm',
5386 None,
5385 None,
5387 _(b'confirm pull before applying changes'),
5386 _(b'confirm pull before applying changes'),
5388 ),
5387 ),
5389 (
5388 (
5390 b'r',
5389 b'r',
5391 b'rev',
5390 b'rev',
5392 [],
5391 [],
5393 _(b'a remote changeset intended to be added'),
5392 _(b'a remote changeset intended to be added'),
5394 _(b'REV'),
5393 _(b'REV'),
5395 ),
5394 ),
5396 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5395 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5397 (
5396 (
5398 b'b',
5397 b'b',
5399 b'branch',
5398 b'branch',
5400 [],
5399 [],
5401 _(b'a specific branch you would like to pull'),
5400 _(b'a specific branch you would like to pull'),
5402 _(b'BRANCH'),
5401 _(b'BRANCH'),
5403 ),
5402 ),
5404 (
5403 (
5405 b'',
5404 b'',
5406 b'remote-hidden',
5405 b'remote-hidden',
5407 False,
5406 False,
5408 _(b"include changesets hidden on the remote (EXPERIMENTAL)"),
5407 _(b"include changesets hidden on the remote (EXPERIMENTAL)"),
5409 ),
5408 ),
5410 ]
5409 ]
5411 + remoteopts,
5410 + remoteopts,
5412 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5411 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5413 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5412 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5414 helpbasic=True,
5413 helpbasic=True,
5415 )
5414 )
5416 def pull(ui, repo, *sources, **opts):
5415 def pull(ui, repo, *sources, **opts):
5417 """pull changes from the specified source
5416 """pull changes from the specified source
5418
5417
5419 Pull changes from a remote repository to a local one.
5418 Pull changes from a remote repository to a local one.
5420
5419
5421 This finds all changes from the repository at the specified path
5420 This finds all changes from the repository at the specified path
5422 or URL and adds them to a local repository (the current one unless
5421 or URL and adds them to a local repository (the current one unless
5423 -R is specified). By default, this does not update the copy of the
5422 -R is specified). By default, this does not update the copy of the
5424 project in the working directory.
5423 project in the working directory.
5425
5424
5426 When cloning from servers that support it, Mercurial may fetch
5425 When cloning from servers that support it, Mercurial may fetch
5427 pre-generated data. When this is done, hooks operating on incoming
5426 pre-generated data. When this is done, hooks operating on incoming
5428 changesets and changegroups may fire more than once, once for each
5427 changesets and changegroups may fire more than once, once for each
5429 pre-generated bundle and as well as for any additional remaining
5428 pre-generated bundle and as well as for any additional remaining
5430 data. See :hg:`help -e clonebundles` for more.
5429 data. See :hg:`help -e clonebundles` for more.
5431
5430
5432 Use :hg:`incoming` if you want to see what would have been added
5431 Use :hg:`incoming` if you want to see what would have been added
5433 by a pull at the time you issued this command. If you then decide
5432 by a pull at the time you issued this command. If you then decide
5434 to add those changes to the repository, you should use :hg:`pull
5433 to add those changes to the repository, you should use :hg:`pull
5435 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5434 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5436
5435
5437 If SOURCE is omitted, the 'default' path will be used.
5436 If SOURCE is omitted, the 'default' path will be used.
5438 See :hg:`help urls` for more information.
5437 See :hg:`help urls` for more information.
5439
5438
5440 If multiple sources are specified, they will be pulled sequentially as if
5439 If multiple sources are specified, they will be pulled sequentially as if
5441 the command was run multiple time. If --update is specify and the command
5440 the command was run multiple time. If --update is specify and the command
5442 will stop at the first failed --update.
5441 will stop at the first failed --update.
5443
5442
5444 Specifying bookmark as ``.`` is equivalent to specifying the active
5443 Specifying bookmark as ``.`` is equivalent to specifying the active
5445 bookmark's name.
5444 bookmark's name.
5446
5445
5447 .. container:: verbose
5446 .. container:: verbose
5448
5447
5449 One can use the `--remote-hidden` flag to pull changesets
5448 One can use the `--remote-hidden` flag to pull changesets
5450 hidden on the remote. This flag is "best effort", and will only
5449 hidden on the remote. This flag is "best effort", and will only
5451 work if the server supports the feature and is configured to
5450 work if the server supports the feature and is configured to
5452 allow the user to access hidden changesets. This option is
5451 allow the user to access hidden changesets. This option is
5453 experimental and backwards compatibility is not garanteed.
5452 experimental and backwards compatibility is not garanteed.
5454
5453
5455 Returns 0 on success, 1 if an update had unresolved files.
5454 Returns 0 on success, 1 if an update had unresolved files.
5456 """
5455 """
5457
5456
5458 opts = pycompat.byteskwargs(opts)
5457 opts = pycompat.byteskwargs(opts)
5459 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5458 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5460 b'update'
5459 b'update'
5461 ):
5460 ):
5462 msg = _(b'update destination required by configuration')
5461 msg = _(b'update destination required by configuration')
5463 hint = _(b'use hg pull followed by hg update DEST')
5462 hint = _(b'use hg pull followed by hg update DEST')
5464 raise error.InputError(msg, hint=hint)
5463 raise error.InputError(msg, hint=hint)
5465
5464
5466 for path in urlutil.get_pull_paths(repo, ui, sources):
5465 for path in urlutil.get_pull_paths(repo, ui, sources):
5467 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(path.loc))
5466 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(path.loc))
5468 ui.flush()
5467 ui.flush()
5469 other = hg.peer(repo, opts, path, remotehidden=opts[b'remote_hidden'])
5468 other = hg.peer(repo, opts, path, remotehidden=opts[b'remote_hidden'])
5470 update_conflict = None
5469 update_conflict = None
5471 try:
5470 try:
5472 branches = (path.branch, opts.get(b'branch', []))
5471 branches = (path.branch, opts.get(b'branch', []))
5473 revs, checkout = hg.addbranchrevs(
5472 revs, checkout = hg.addbranchrevs(
5474 repo,
5473 repo,
5475 other,
5474 other,
5476 branches,
5475 branches,
5477 opts.get(b'rev'),
5476 opts.get(b'rev'),
5478 remotehidden=opts[b'remote_hidden'],
5477 remotehidden=opts[b'remote_hidden'],
5479 )
5478 )
5480
5479
5481 pullopargs = {}
5480 pullopargs = {}
5482
5481
5483 nodes = None
5482 nodes = None
5484 if opts.get(b'bookmark') or revs:
5483 if opts.get(b'bookmark') or revs:
5485 # The list of bookmark used here is the same used to actually update
5484 # The list of bookmark used here is the same used to actually update
5486 # the bookmark names, to avoid the race from issue 4689 and we do
5485 # the bookmark names, to avoid the race from issue 4689 and we do
5487 # all lookup and bookmark queries in one go so they see the same
5486 # all lookup and bookmark queries in one go so they see the same
5488 # version of the server state (issue 4700).
5487 # version of the server state (issue 4700).
5489 nodes = []
5488 nodes = []
5490 fnodes = []
5489 fnodes = []
5491 revs = revs or []
5490 revs = revs or []
5492 if revs and not other.capable(b'lookup'):
5491 if revs and not other.capable(b'lookup'):
5493 err = _(
5492 err = _(
5494 b"other repository doesn't support revision lookup, "
5493 b"other repository doesn't support revision lookup, "
5495 b"so a rev cannot be specified."
5494 b"so a rev cannot be specified."
5496 )
5495 )
5497 raise error.Abort(err)
5496 raise error.Abort(err)
5498 with other.commandexecutor() as e:
5497 with other.commandexecutor() as e:
5499 fremotebookmarks = e.callcommand(
5498 fremotebookmarks = e.callcommand(
5500 b'listkeys', {b'namespace': b'bookmarks'}
5499 b'listkeys', {b'namespace': b'bookmarks'}
5501 )
5500 )
5502 for r in revs:
5501 for r in revs:
5503 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5502 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5504 remotebookmarks = fremotebookmarks.result()
5503 remotebookmarks = fremotebookmarks.result()
5505 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5504 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5506 pullopargs[b'remotebookmarks'] = remotebookmarks
5505 pullopargs[b'remotebookmarks'] = remotebookmarks
5507 for b in opts.get(b'bookmark', []):
5506 for b in opts.get(b'bookmark', []):
5508 b = repo._bookmarks.expandname(b)
5507 b = repo._bookmarks.expandname(b)
5509 if b not in remotebookmarks:
5508 if b not in remotebookmarks:
5510 raise error.InputError(
5509 raise error.InputError(
5511 _(b'remote bookmark %s not found!') % b
5510 _(b'remote bookmark %s not found!') % b
5512 )
5511 )
5513 nodes.append(remotebookmarks[b])
5512 nodes.append(remotebookmarks[b])
5514 for i, rev in enumerate(revs):
5513 for i, rev in enumerate(revs):
5515 node = fnodes[i].result()
5514 node = fnodes[i].result()
5516 nodes.append(node)
5515 nodes.append(node)
5517 if rev == checkout:
5516 if rev == checkout:
5518 checkout = node
5517 checkout = node
5519
5518
5520 wlock = util.nullcontextmanager()
5519 wlock = util.nullcontextmanager()
5521 if opts.get(b'update'):
5520 if opts.get(b'update'):
5522 wlock = repo.wlock()
5521 wlock = repo.wlock()
5523 with wlock:
5522 with wlock:
5524 pullopargs.update(opts.get(b'opargs', {}))
5523 pullopargs.update(opts.get(b'opargs', {}))
5525 modheads = exchange.pull(
5524 modheads = exchange.pull(
5526 repo,
5525 repo,
5527 other,
5526 other,
5528 path=path,
5527 path=path,
5529 heads=nodes,
5528 heads=nodes,
5530 force=opts.get(b'force'),
5529 force=opts.get(b'force'),
5531 bookmarks=opts.get(b'bookmark', ()),
5530 bookmarks=opts.get(b'bookmark', ()),
5532 opargs=pullopargs,
5531 opargs=pullopargs,
5533 confirm=opts.get(b'confirm'),
5532 confirm=opts.get(b'confirm'),
5534 ).cgresult
5533 ).cgresult
5535
5534
5536 # brev is a name, which might be a bookmark to be activated at
5535 # brev is a name, which might be a bookmark to be activated at
5537 # the end of the update. In other words, it is an explicit
5536 # the end of the update. In other words, it is an explicit
5538 # destination of the update
5537 # destination of the update
5539 brev = None
5538 brev = None
5540
5539
5541 if checkout:
5540 if checkout:
5542 checkout = repo.unfiltered().changelog.rev(checkout)
5541 checkout = repo.unfiltered().changelog.rev(checkout)
5543
5542
5544 # order below depends on implementation of
5543 # order below depends on implementation of
5545 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5544 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5546 # because 'checkout' is determined without it.
5545 # because 'checkout' is determined without it.
5547 if opts.get(b'rev'):
5546 if opts.get(b'rev'):
5548 brev = opts[b'rev'][0]
5547 brev = opts[b'rev'][0]
5549 elif opts.get(b'branch'):
5548 elif opts.get(b'branch'):
5550 brev = opts[b'branch'][0]
5549 brev = opts[b'branch'][0]
5551 else:
5550 else:
5552 brev = path.branch
5551 brev = path.branch
5553
5552
5554 # XXX path: we are losing the `path` object here. Keeping it
5553 # XXX path: we are losing the `path` object here. Keeping it
5555 # would be valuable. For example as a "variant" as we do
5554 # would be valuable. For example as a "variant" as we do
5556 # for pushes.
5555 # for pushes.
5557 repo._subtoppath = path.loc
5556 repo._subtoppath = path.loc
5558 try:
5557 try:
5559 update_conflict = postincoming(
5558 update_conflict = postincoming(
5560 ui, repo, modheads, opts.get(b'update'), checkout, brev
5559 ui, repo, modheads, opts.get(b'update'), checkout, brev
5561 )
5560 )
5562 except error.FilteredRepoLookupError as exc:
5561 except error.FilteredRepoLookupError as exc:
5563 msg = _(b'cannot update to target: %s') % exc.args[0]
5562 msg = _(b'cannot update to target: %s') % exc.args[0]
5564 exc.args = (msg,) + exc.args[1:]
5563 exc.args = (msg,) + exc.args[1:]
5565 raise
5564 raise
5566 finally:
5565 finally:
5567 del repo._subtoppath
5566 del repo._subtoppath
5568
5567
5569 finally:
5568 finally:
5570 other.close()
5569 other.close()
5571 # skip the remaining pull source if they are some conflict.
5570 # skip the remaining pull source if they are some conflict.
5572 if update_conflict:
5571 if update_conflict:
5573 break
5572 break
5574 if update_conflict:
5573 if update_conflict:
5575 return 1
5574 return 1
5576 else:
5575 else:
5577 return 0
5576 return 0
5578
5577
5579
5578
5580 @command(
5579 @command(
5581 b'purge|clean',
5580 b'purge|clean',
5582 [
5581 [
5583 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5582 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5584 (b'', b'all', None, _(b'purge ignored files too')),
5583 (b'', b'all', None, _(b'purge ignored files too')),
5585 (b'i', b'ignored', None, _(b'purge only ignored files')),
5584 (b'i', b'ignored', None, _(b'purge only ignored files')),
5586 (b'', b'dirs', None, _(b'purge empty directories')),
5585 (b'', b'dirs', None, _(b'purge empty directories')),
5587 (b'', b'files', None, _(b'purge files')),
5586 (b'', b'files', None, _(b'purge files')),
5588 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5587 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5589 (
5588 (
5590 b'0',
5589 b'0',
5591 b'print0',
5590 b'print0',
5592 None,
5591 None,
5593 _(
5592 _(
5594 b'end filenames with NUL, for use with xargs'
5593 b'end filenames with NUL, for use with xargs'
5595 b' (implies -p/--print)'
5594 b' (implies -p/--print)'
5596 ),
5595 ),
5597 ),
5596 ),
5598 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5597 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5599 ]
5598 ]
5600 + cmdutil.walkopts,
5599 + cmdutil.walkopts,
5601 _(b'hg purge [OPTION]... [DIR]...'),
5600 _(b'hg purge [OPTION]... [DIR]...'),
5602 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5601 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5603 )
5602 )
5604 def purge(ui, repo, *dirs, **opts):
5603 def purge(ui, repo, *dirs, **opts):
5605 """removes files not tracked by Mercurial
5604 """removes files not tracked by Mercurial
5606
5605
5607 Delete files not known to Mercurial. This is useful to test local
5606 Delete files not known to Mercurial. This is useful to test local
5608 and uncommitted changes in an otherwise-clean source tree.
5607 and uncommitted changes in an otherwise-clean source tree.
5609
5608
5610 This means that purge will delete the following by default:
5609 This means that purge will delete the following by default:
5611
5610
5612 - Unknown files: files marked with "?" by :hg:`status`
5611 - Unknown files: files marked with "?" by :hg:`status`
5613 - Empty directories: in fact Mercurial ignores directories unless
5612 - Empty directories: in fact Mercurial ignores directories unless
5614 they contain files under source control management
5613 they contain files under source control management
5615
5614
5616 But it will leave untouched:
5615 But it will leave untouched:
5617
5616
5618 - Modified and unmodified tracked files
5617 - Modified and unmodified tracked files
5619 - Ignored files (unless -i or --all is specified)
5618 - Ignored files (unless -i or --all is specified)
5620 - New files added to the repository (with :hg:`add`)
5619 - New files added to the repository (with :hg:`add`)
5621
5620
5622 The --files and --dirs options can be used to direct purge to delete
5621 The --files and --dirs options can be used to direct purge to delete
5623 only files, only directories, or both. If neither option is given,
5622 only files, only directories, or both. If neither option is given,
5624 both will be deleted.
5623 both will be deleted.
5625
5624
5626 If directories are given on the command line, only files in these
5625 If directories are given on the command line, only files in these
5627 directories are considered.
5626 directories are considered.
5628
5627
5629 Be careful with purge, as you could irreversibly delete some files
5628 Be careful with purge, as you could irreversibly delete some files
5630 you forgot to add to the repository. If you only want to print the
5629 you forgot to add to the repository. If you only want to print the
5631 list of files that this program would delete, use the --print
5630 list of files that this program would delete, use the --print
5632 option.
5631 option.
5633 """
5632 """
5634 opts = pycompat.byteskwargs(opts)
5633 opts = pycompat.byteskwargs(opts)
5635 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5634 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5636
5635
5637 act = not opts.get(b'print')
5636 act = not opts.get(b'print')
5638 eol = b'\n'
5637 eol = b'\n'
5639 if opts.get(b'print0'):
5638 if opts.get(b'print0'):
5640 eol = b'\0'
5639 eol = b'\0'
5641 act = False # --print0 implies --print
5640 act = False # --print0 implies --print
5642 if opts.get(b'all', False):
5641 if opts.get(b'all', False):
5643 ignored = True
5642 ignored = True
5644 unknown = True
5643 unknown = True
5645 else:
5644 else:
5646 ignored = opts.get(b'ignored', False)
5645 ignored = opts.get(b'ignored', False)
5647 unknown = not ignored
5646 unknown = not ignored
5648
5647
5649 removefiles = opts.get(b'files')
5648 removefiles = opts.get(b'files')
5650 removedirs = opts.get(b'dirs')
5649 removedirs = opts.get(b'dirs')
5651 confirm = opts.get(b'confirm')
5650 confirm = opts.get(b'confirm')
5652 if confirm is None:
5651 if confirm is None:
5653 try:
5652 try:
5654 extensions.find(b'purge')
5653 extensions.find(b'purge')
5655 confirm = False
5654 confirm = False
5656 except KeyError:
5655 except KeyError:
5657 confirm = True
5656 confirm = True
5658
5657
5659 if not removefiles and not removedirs:
5658 if not removefiles and not removedirs:
5660 removefiles = True
5659 removefiles = True
5661 removedirs = True
5660 removedirs = True
5662
5661
5663 match = scmutil.match(repo[None], dirs, opts)
5662 match = scmutil.match(repo[None], dirs, opts)
5664
5663
5665 paths = mergemod.purge(
5664 paths = mergemod.purge(
5666 repo,
5665 repo,
5667 match,
5666 match,
5668 unknown=unknown,
5667 unknown=unknown,
5669 ignored=ignored,
5668 ignored=ignored,
5670 removeemptydirs=removedirs,
5669 removeemptydirs=removedirs,
5671 removefiles=removefiles,
5670 removefiles=removefiles,
5672 abortonerror=opts.get(b'abort_on_err'),
5671 abortonerror=opts.get(b'abort_on_err'),
5673 noop=not act,
5672 noop=not act,
5674 confirm=confirm,
5673 confirm=confirm,
5675 )
5674 )
5676
5675
5677 for path in paths:
5676 for path in paths:
5678 if not act:
5677 if not act:
5679 ui.write(b'%s%s' % (path, eol))
5678 ui.write(b'%s%s' % (path, eol))
5680
5679
5681
5680
5682 @command(
5681 @command(
5683 b'push',
5682 b'push',
5684 [
5683 [
5685 (b'f', b'force', None, _(b'force push')),
5684 (b'f', b'force', None, _(b'force push')),
5686 (
5685 (
5687 b'r',
5686 b'r',
5688 b'rev',
5687 b'rev',
5689 [],
5688 [],
5690 _(b'a changeset intended to be included in the destination'),
5689 _(b'a changeset intended to be included in the destination'),
5691 _(b'REV'),
5690 _(b'REV'),
5692 ),
5691 ),
5693 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5692 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5694 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5693 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5695 (
5694 (
5696 b'b',
5695 b'b',
5697 b'branch',
5696 b'branch',
5698 [],
5697 [],
5699 _(b'a specific branch you would like to push'),
5698 _(b'a specific branch you would like to push'),
5700 _(b'BRANCH'),
5699 _(b'BRANCH'),
5701 ),
5700 ),
5702 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5701 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5703 (
5702 (
5704 b'',
5703 b'',
5705 b'pushvars',
5704 b'pushvars',
5706 [],
5705 [],
5707 _(b'variables that can be sent to server (ADVANCED)'),
5706 _(b'variables that can be sent to server (ADVANCED)'),
5708 ),
5707 ),
5709 (
5708 (
5710 b'',
5709 b'',
5711 b'publish',
5710 b'publish',
5712 False,
5711 False,
5713 _(b'push the changeset as public (EXPERIMENTAL)'),
5712 _(b'push the changeset as public (EXPERIMENTAL)'),
5714 ),
5713 ),
5715 ]
5714 ]
5716 + remoteopts,
5715 + remoteopts,
5717 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5716 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5718 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5717 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5719 helpbasic=True,
5718 helpbasic=True,
5720 )
5719 )
5721 def push(ui, repo, *dests, **opts):
5720 def push(ui, repo, *dests, **opts):
5722 """push changes to the specified destination
5721 """push changes to the specified destination
5723
5722
5724 Push changesets from the local repository to the specified
5723 Push changesets from the local repository to the specified
5725 destination.
5724 destination.
5726
5725
5727 This operation is symmetrical to pull: it is identical to a pull
5726 This operation is symmetrical to pull: it is identical to a pull
5728 in the destination repository from the current one.
5727 in the destination repository from the current one.
5729
5728
5730 By default, push will not allow creation of new heads at the
5729 By default, push will not allow creation of new heads at the
5731 destination, since multiple heads would make it unclear which head
5730 destination, since multiple heads would make it unclear which head
5732 to use. In this situation, it is recommended to pull and merge
5731 to use. In this situation, it is recommended to pull and merge
5733 before pushing.
5732 before pushing.
5734
5733
5735 Use --new-branch if you want to allow push to create a new named
5734 Use --new-branch if you want to allow push to create a new named
5736 branch that is not present at the destination. This allows you to
5735 branch that is not present at the destination. This allows you to
5737 only create a new branch without forcing other changes.
5736 only create a new branch without forcing other changes.
5738
5737
5739 .. note::
5738 .. note::
5740
5739
5741 Extra care should be taken with the -f/--force option,
5740 Extra care should be taken with the -f/--force option,
5742 which will push all new heads on all branches, an action which will
5741 which will push all new heads on all branches, an action which will
5743 almost always cause confusion for collaborators.
5742 almost always cause confusion for collaborators.
5744
5743
5745 If -r/--rev is used, the specified revision and all its ancestors
5744 If -r/--rev is used, the specified revision and all its ancestors
5746 will be pushed to the remote repository.
5745 will be pushed to the remote repository.
5747
5746
5748 If -B/--bookmark is used, the specified bookmarked revision, its
5747 If -B/--bookmark is used, the specified bookmarked revision, its
5749 ancestors, and the bookmark will be pushed to the remote
5748 ancestors, and the bookmark will be pushed to the remote
5750 repository. Specifying ``.`` is equivalent to specifying the active
5749 repository. Specifying ``.`` is equivalent to specifying the active
5751 bookmark's name. Use the --all-bookmarks option for pushing all
5750 bookmark's name. Use the --all-bookmarks option for pushing all
5752 current bookmarks.
5751 current bookmarks.
5753
5752
5754 Please see :hg:`help urls` for important details about ``ssh://``
5753 Please see :hg:`help urls` for important details about ``ssh://``
5755 URLs. If DESTINATION is omitted, a default path will be used.
5754 URLs. If DESTINATION is omitted, a default path will be used.
5756
5755
5757 When passed multiple destinations, push will process them one after the
5756 When passed multiple destinations, push will process them one after the
5758 other, but stop should an error occur.
5757 other, but stop should an error occur.
5759
5758
5760 .. container:: verbose
5759 .. container:: verbose
5761
5760
5762 The --pushvars option sends strings to the server that become
5761 The --pushvars option sends strings to the server that become
5763 environment variables prepended with ``HG_USERVAR_``. For example,
5762 environment variables prepended with ``HG_USERVAR_``. For example,
5764 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5763 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5765 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5764 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5766
5765
5767 pushvars can provide for user-overridable hooks as well as set debug
5766 pushvars can provide for user-overridable hooks as well as set debug
5768 levels. One example is having a hook that blocks commits containing
5767 levels. One example is having a hook that blocks commits containing
5769 conflict markers, but enables the user to override the hook if the file
5768 conflict markers, but enables the user to override the hook if the file
5770 is using conflict markers for testing purposes or the file format has
5769 is using conflict markers for testing purposes or the file format has
5771 strings that look like conflict markers.
5770 strings that look like conflict markers.
5772
5771
5773 By default, servers will ignore `--pushvars`. To enable it add the
5772 By default, servers will ignore `--pushvars`. To enable it add the
5774 following to your configuration file::
5773 following to your configuration file::
5775
5774
5776 [push]
5775 [push]
5777 pushvars.server = true
5776 pushvars.server = true
5778
5777
5779 Returns 0 if push was successful, 1 if nothing to push.
5778 Returns 0 if push was successful, 1 if nothing to push.
5780 """
5779 """
5781
5780
5782 opts = pycompat.byteskwargs(opts)
5781 opts = pycompat.byteskwargs(opts)
5783
5782
5784 if opts.get(b'all_bookmarks'):
5783 if opts.get(b'all_bookmarks'):
5785 cmdutil.check_incompatible_arguments(
5784 cmdutil.check_incompatible_arguments(
5786 opts,
5785 opts,
5787 b'all_bookmarks',
5786 b'all_bookmarks',
5788 [b'bookmark', b'rev'],
5787 [b'bookmark', b'rev'],
5789 )
5788 )
5790 opts[b'bookmark'] = list(repo._bookmarks)
5789 opts[b'bookmark'] = list(repo._bookmarks)
5791
5790
5792 if opts.get(b'bookmark'):
5791 if opts.get(b'bookmark'):
5793 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5792 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5794 for b in opts[b'bookmark']:
5793 for b in opts[b'bookmark']:
5795 # translate -B options to -r so changesets get pushed
5794 # translate -B options to -r so changesets get pushed
5796 b = repo._bookmarks.expandname(b)
5795 b = repo._bookmarks.expandname(b)
5797 if b in repo._bookmarks:
5796 if b in repo._bookmarks:
5798 opts.setdefault(b'rev', []).append(b)
5797 opts.setdefault(b'rev', []).append(b)
5799 else:
5798 else:
5800 # if we try to push a deleted bookmark, translate it to null
5799 # if we try to push a deleted bookmark, translate it to null
5801 # this lets simultaneous -r, -b options continue working
5800 # this lets simultaneous -r, -b options continue working
5802 opts.setdefault(b'rev', []).append(b"null")
5801 opts.setdefault(b'rev', []).append(b"null")
5803
5802
5804 some_pushed = False
5803 some_pushed = False
5805 result = 0
5804 result = 0
5806 for path in urlutil.get_push_paths(repo, ui, dests):
5805 for path in urlutil.get_push_paths(repo, ui, dests):
5807 dest = path.loc
5806 dest = path.loc
5808 branches = (path.branch, opts.get(b'branch') or [])
5807 branches = (path.branch, opts.get(b'branch') or [])
5809 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5808 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5810 revs, checkout = hg.addbranchrevs(
5809 revs, checkout = hg.addbranchrevs(
5811 repo, repo, branches, opts.get(b'rev')
5810 repo, repo, branches, opts.get(b'rev')
5812 )
5811 )
5813 other = hg.peer(repo, opts, dest)
5812 other = hg.peer(repo, opts, dest)
5814
5813
5815 try:
5814 try:
5816 if revs:
5815 if revs:
5817 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5816 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5818 if not revs:
5817 if not revs:
5819 raise error.InputError(
5818 raise error.InputError(
5820 _(b"specified revisions evaluate to an empty set"),
5819 _(b"specified revisions evaluate to an empty set"),
5821 hint=_(b"use different revision arguments"),
5820 hint=_(b"use different revision arguments"),
5822 )
5821 )
5823 elif path.pushrev:
5822 elif path.pushrev:
5824 # It doesn't make any sense to specify ancestor revisions. So limit
5823 # It doesn't make any sense to specify ancestor revisions. So limit
5825 # to DAG heads to make discovery simpler.
5824 # to DAG heads to make discovery simpler.
5826 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5825 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5827 revs = scmutil.revrange(repo, [expr])
5826 revs = scmutil.revrange(repo, [expr])
5828 revs = [repo[rev].node() for rev in revs]
5827 revs = [repo[rev].node() for rev in revs]
5829 if not revs:
5828 if not revs:
5830 raise error.InputError(
5829 raise error.InputError(
5831 _(
5830 _(
5832 b'default push revset for path evaluates to an empty set'
5831 b'default push revset for path evaluates to an empty set'
5833 )
5832 )
5834 )
5833 )
5835 elif ui.configbool(b'commands', b'push.require-revs'):
5834 elif ui.configbool(b'commands', b'push.require-revs'):
5836 raise error.InputError(
5835 raise error.InputError(
5837 _(b'no revisions specified to push'),
5836 _(b'no revisions specified to push'),
5838 hint=_(b'did you mean "hg push -r ."?'),
5837 hint=_(b'did you mean "hg push -r ."?'),
5839 )
5838 )
5840
5839
5841 repo._subtoppath = dest
5840 repo._subtoppath = dest
5842 try:
5841 try:
5843 # push subrepos depth-first for coherent ordering
5842 # push subrepos depth-first for coherent ordering
5844 c = repo[b'.']
5843 c = repo[b'.']
5845 subs = c.substate # only repos that are committed
5844 subs = c.substate # only repos that are committed
5846 for s in sorted(subs):
5845 for s in sorted(subs):
5847 sub_result = c.sub(s).push(opts)
5846 sub_result = c.sub(s).push(opts)
5848 if sub_result == 0:
5847 if sub_result == 0:
5849 return 1
5848 return 1
5850 finally:
5849 finally:
5851 del repo._subtoppath
5850 del repo._subtoppath
5852
5851
5853 opargs = dict(
5852 opargs = dict(
5854 opts.get(b'opargs', {})
5853 opts.get(b'opargs', {})
5855 ) # copy opargs since we may mutate it
5854 ) # copy opargs since we may mutate it
5856 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5855 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5857
5856
5858 pushop = exchange.push(
5857 pushop = exchange.push(
5859 repo,
5858 repo,
5860 other,
5859 other,
5861 opts.get(b'force'),
5860 opts.get(b'force'),
5862 revs=revs,
5861 revs=revs,
5863 newbranch=opts.get(b'new_branch'),
5862 newbranch=opts.get(b'new_branch'),
5864 bookmarks=opts.get(b'bookmark', ()),
5863 bookmarks=opts.get(b'bookmark', ()),
5865 publish=opts.get(b'publish'),
5864 publish=opts.get(b'publish'),
5866 opargs=opargs,
5865 opargs=opargs,
5867 )
5866 )
5868
5867
5869 if pushop.cgresult == 0:
5868 if pushop.cgresult == 0:
5870 result = 1
5869 result = 1
5871 elif pushop.cgresult is not None:
5870 elif pushop.cgresult is not None:
5872 some_pushed = True
5871 some_pushed = True
5873
5872
5874 if pushop.bkresult is not None:
5873 if pushop.bkresult is not None:
5875 if pushop.bkresult == 2:
5874 if pushop.bkresult == 2:
5876 result = 2
5875 result = 2
5877 elif not result and pushop.bkresult:
5876 elif not result and pushop.bkresult:
5878 result = 2
5877 result = 2
5879
5878
5880 if result:
5879 if result:
5881 break
5880 break
5882
5881
5883 finally:
5882 finally:
5884 other.close()
5883 other.close()
5885 if result == 0 and not some_pushed:
5884 if result == 0 and not some_pushed:
5886 result = 1
5885 result = 1
5887 return result
5886 return result
5888
5887
5889
5888
5890 @command(
5889 @command(
5891 b'recover',
5890 b'recover',
5892 [
5891 [
5893 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5892 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5894 ],
5893 ],
5895 helpcategory=command.CATEGORY_MAINTENANCE,
5894 helpcategory=command.CATEGORY_MAINTENANCE,
5896 )
5895 )
5897 def recover(ui, repo, **opts):
5896 def recover(ui, repo, **opts):
5898 """roll back an interrupted transaction
5897 """roll back an interrupted transaction
5899
5898
5900 Recover from an interrupted commit or pull.
5899 Recover from an interrupted commit or pull.
5901
5900
5902 This command tries to fix the repository status after an
5901 This command tries to fix the repository status after an
5903 interrupted operation. It should only be necessary when Mercurial
5902 interrupted operation. It should only be necessary when Mercurial
5904 suggests it.
5903 suggests it.
5905
5904
5906 Returns 0 if successful, 1 if nothing to recover or verify fails.
5905 Returns 0 if successful, 1 if nothing to recover or verify fails.
5907 """
5906 """
5908 ret = repo.recover()
5907 ret = repo.recover()
5909 if ret:
5908 if ret:
5910 if opts['verify']:
5909 if opts['verify']:
5911 return hg.verify(repo)
5910 return hg.verify(repo)
5912 else:
5911 else:
5913 msg = _(
5912 msg = _(
5914 b"(verify step skipped, run `hg verify` to check your "
5913 b"(verify step skipped, run `hg verify` to check your "
5915 b"repository content)\n"
5914 b"repository content)\n"
5916 )
5915 )
5917 ui.warn(msg)
5916 ui.warn(msg)
5918 return 0
5917 return 0
5919 return 1
5918 return 1
5920
5919
5921
5920
5922 @command(
5921 @command(
5923 b'remove|rm',
5922 b'remove|rm',
5924 [
5923 [
5925 (b'A', b'after', None, _(b'record delete for missing files')),
5924 (b'A', b'after', None, _(b'record delete for missing files')),
5926 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5925 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5927 ]
5926 ]
5928 + subrepoopts
5927 + subrepoopts
5929 + walkopts
5928 + walkopts
5930 + dryrunopts,
5929 + dryrunopts,
5931 _(b'[OPTION]... FILE...'),
5930 _(b'[OPTION]... FILE...'),
5932 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5931 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5933 helpbasic=True,
5932 helpbasic=True,
5934 inferrepo=True,
5933 inferrepo=True,
5935 )
5934 )
5936 def remove(ui, repo, *pats, **opts):
5935 def remove(ui, repo, *pats, **opts):
5937 """remove the specified files on the next commit
5936 """remove the specified files on the next commit
5938
5937
5939 Schedule the indicated files for removal from the current branch.
5938 Schedule the indicated files for removal from the current branch.
5940
5939
5941 This command schedules the files to be removed at the next commit.
5940 This command schedules the files to be removed at the next commit.
5942 To undo a remove before that, see :hg:`revert`. To undo added
5941 To undo a remove before that, see :hg:`revert`. To undo added
5943 files, see :hg:`forget`.
5942 files, see :hg:`forget`.
5944
5943
5945 .. container:: verbose
5944 .. container:: verbose
5946
5945
5947 -A/--after can be used to remove only files that have already
5946 -A/--after can be used to remove only files that have already
5948 been deleted, -f/--force can be used to force deletion, and -Af
5947 been deleted, -f/--force can be used to force deletion, and -Af
5949 can be used to remove files from the next revision without
5948 can be used to remove files from the next revision without
5950 deleting them from the working directory.
5949 deleting them from the working directory.
5951
5950
5952 The following table details the behavior of remove for different
5951 The following table details the behavior of remove for different
5953 file states (columns) and option combinations (rows). The file
5952 file states (columns) and option combinations (rows). The file
5954 states are Added [A], Clean [C], Modified [M] and Missing [!]
5953 states are Added [A], Clean [C], Modified [M] and Missing [!]
5955 (as reported by :hg:`status`). The actions are Warn, Remove
5954 (as reported by :hg:`status`). The actions are Warn, Remove
5956 (from branch) and Delete (from disk):
5955 (from branch) and Delete (from disk):
5957
5956
5958 ========= == == == ==
5957 ========= == == == ==
5959 opt/state A C M !
5958 opt/state A C M !
5960 ========= == == == ==
5959 ========= == == == ==
5961 none W RD W R
5960 none W RD W R
5962 -f R RD RD R
5961 -f R RD RD R
5963 -A W W W R
5962 -A W W W R
5964 -Af R R R R
5963 -Af R R R R
5965 ========= == == == ==
5964 ========= == == == ==
5966
5965
5967 .. note::
5966 .. note::
5968
5967
5969 :hg:`remove` never deletes files in Added [A] state from the
5968 :hg:`remove` never deletes files in Added [A] state from the
5970 working directory, not even if ``--force`` is specified.
5969 working directory, not even if ``--force`` is specified.
5971
5970
5972 Returns 0 on success, 1 if any warnings encountered.
5971 Returns 0 on success, 1 if any warnings encountered.
5973 """
5972 """
5974
5973
5975 opts = pycompat.byteskwargs(opts)
5974 opts = pycompat.byteskwargs(opts)
5976 after, force = opts.get(b'after'), opts.get(b'force')
5975 after, force = opts.get(b'after'), opts.get(b'force')
5977 dryrun = opts.get(b'dry_run')
5976 dryrun = opts.get(b'dry_run')
5978 if not pats and not after:
5977 if not pats and not after:
5979 raise error.InputError(_(b'no files specified'))
5978 raise error.InputError(_(b'no files specified'))
5980
5979
5981 with repo.wlock(), repo.dirstate.changing_files(repo):
5980 with repo.wlock(), repo.dirstate.changing_files(repo):
5982 m = scmutil.match(repo[None], pats, opts)
5981 m = scmutil.match(repo[None], pats, opts)
5983 subrepos = opts.get(b'subrepos')
5982 subrepos = opts.get(b'subrepos')
5984 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5983 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5985 return cmdutil.remove(
5984 return cmdutil.remove(
5986 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5985 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5987 )
5986 )
5988
5987
5989
5988
5990 @command(
5989 @command(
5991 b'rename|move|mv',
5990 b'rename|move|mv',
5992 [
5991 [
5993 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5992 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5994 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5993 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5995 (
5994 (
5996 b'',
5995 b'',
5997 b'at-rev',
5996 b'at-rev',
5998 b'',
5997 b'',
5999 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5998 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
6000 _(b'REV'),
5999 _(b'REV'),
6001 ),
6000 ),
6002 (
6001 (
6003 b'f',
6002 b'f',
6004 b'force',
6003 b'force',
6005 None,
6004 None,
6006 _(b'forcibly move over an existing managed file'),
6005 _(b'forcibly move over an existing managed file'),
6007 ),
6006 ),
6008 ]
6007 ]
6009 + walkopts
6008 + walkopts
6010 + dryrunopts,
6009 + dryrunopts,
6011 _(b'[OPTION]... SOURCE... DEST'),
6010 _(b'[OPTION]... SOURCE... DEST'),
6012 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6011 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6013 )
6012 )
6014 def rename(ui, repo, *pats, **opts):
6013 def rename(ui, repo, *pats, **opts):
6015 """rename files; equivalent of copy + remove
6014 """rename files; equivalent of copy + remove
6016
6015
6017 Mark dest as copies of sources; mark sources for deletion. If dest
6016 Mark dest as copies of sources; mark sources for deletion. If dest
6018 is a directory, copies are put in that directory. If dest is a
6017 is a directory, copies are put in that directory. If dest is a
6019 file, there can only be one source.
6018 file, there can only be one source.
6020
6019
6021 By default, this command copies the contents of files as they
6020 By default, this command copies the contents of files as they
6022 exist in the working directory. If invoked with -A/--after, the
6021 exist in the working directory. If invoked with -A/--after, the
6023 operation is recorded, but no copying is performed.
6022 operation is recorded, but no copying is performed.
6024
6023
6025 To undo marking a destination file as renamed, use --forget. With that
6024 To undo marking a destination file as renamed, use --forget. With that
6026 option, all given (positional) arguments are unmarked as renames. The
6025 option, all given (positional) arguments are unmarked as renames. The
6027 destination file(s) will be left in place (still tracked). The source
6026 destination file(s) will be left in place (still tracked). The source
6028 file(s) will not be restored. Note that :hg:`rename --forget` behaves
6027 file(s) will not be restored. Note that :hg:`rename --forget` behaves
6029 the same way as :hg:`copy --forget`.
6028 the same way as :hg:`copy --forget`.
6030
6029
6031 This command takes effect with the next commit by default.
6030 This command takes effect with the next commit by default.
6032
6031
6033 Returns 0 on success, 1 if errors are encountered.
6032 Returns 0 on success, 1 if errors are encountered.
6034 """
6033 """
6035 opts = pycompat.byteskwargs(opts)
6034 opts = pycompat.byteskwargs(opts)
6036 context = lambda repo: repo.dirstate.changing_files(repo)
6035 context = lambda repo: repo.dirstate.changing_files(repo)
6037 rev = opts.get(b'at_rev')
6036 rev = opts.get(b'at_rev')
6038 ctx = None
6037 ctx = None
6039 if rev:
6038 if rev:
6040 ctx = logcmdutil.revsingle(repo, rev)
6039 ctx = logcmdutil.revsingle(repo, rev)
6041 if ctx.rev() is not None:
6040 if ctx.rev() is not None:
6042
6041
6043 def context(repo):
6042 def context(repo):
6044 return util.nullcontextmanager()
6043 return util.nullcontextmanager()
6045
6044
6046 opts[b'at_rev'] = ctx.rev()
6045 opts[b'at_rev'] = ctx.rev()
6047 with repo.wlock(), context(repo):
6046 with repo.wlock(), context(repo):
6048 return cmdutil.copy(ui, repo, pats, opts, rename=True)
6047 return cmdutil.copy(ui, repo, pats, opts, rename=True)
6049
6048
6050
6049
6051 @command(
6050 @command(
6052 b'resolve',
6051 b'resolve',
6053 [
6052 [
6054 (b'a', b'all', None, _(b'select all unresolved files')),
6053 (b'a', b'all', None, _(b'select all unresolved files')),
6055 (b'l', b'list', None, _(b'list state of files needing merge')),
6054 (b'l', b'list', None, _(b'list state of files needing merge')),
6056 (b'm', b'mark', None, _(b'mark files as resolved')),
6055 (b'm', b'mark', None, _(b'mark files as resolved')),
6057 (b'u', b'unmark', None, _(b'mark files as unresolved')),
6056 (b'u', b'unmark', None, _(b'mark files as unresolved')),
6058 (b'n', b'no-status', None, _(b'hide status prefix')),
6057 (b'n', b'no-status', None, _(b'hide status prefix')),
6059 (b'', b're-merge', None, _(b're-merge files')),
6058 (b'', b're-merge', None, _(b're-merge files')),
6060 ]
6059 ]
6061 + mergetoolopts
6060 + mergetoolopts
6062 + walkopts
6061 + walkopts
6063 + formatteropts,
6062 + formatteropts,
6064 _(b'[OPTION]... [FILE]...'),
6063 _(b'[OPTION]... [FILE]...'),
6065 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6064 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6066 inferrepo=True,
6065 inferrepo=True,
6067 )
6066 )
6068 def resolve(ui, repo, *pats, **opts):
6067 def resolve(ui, repo, *pats, **opts):
6069 """redo merges or set/view the merge status of files
6068 """redo merges or set/view the merge status of files
6070
6069
6071 Merges with unresolved conflicts are often the result of
6070 Merges with unresolved conflicts are often the result of
6072 non-interactive merging using the ``internal:merge`` configuration
6071 non-interactive merging using the ``internal:merge`` configuration
6073 setting, or a command-line merge tool like ``diff3``. The resolve
6072 setting, or a command-line merge tool like ``diff3``. The resolve
6074 command is used to manage the files involved in a merge, after
6073 command is used to manage the files involved in a merge, after
6075 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
6074 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
6076 working directory must have two parents). See :hg:`help
6075 working directory must have two parents). See :hg:`help
6077 merge-tools` for information on configuring merge tools.
6076 merge-tools` for information on configuring merge tools.
6078
6077
6079 The resolve command can be used in the following ways:
6078 The resolve command can be used in the following ways:
6080
6079
6081 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
6080 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
6082 the specified files, discarding any previous merge attempts. Re-merging
6081 the specified files, discarding any previous merge attempts. Re-merging
6083 is not performed for files already marked as resolved. Use ``--all/-a``
6082 is not performed for files already marked as resolved. Use ``--all/-a``
6084 to select all unresolved files. ``--tool`` can be used to specify
6083 to select all unresolved files. ``--tool`` can be used to specify
6085 the merge tool used for the given files. It overrides the HGMERGE
6084 the merge tool used for the given files. It overrides the HGMERGE
6086 environment variable and your configuration files. Previous file
6085 environment variable and your configuration files. Previous file
6087 contents are saved with a ``.orig`` suffix.
6086 contents are saved with a ``.orig`` suffix.
6088
6087
6089 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6088 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6090 (e.g. after having manually fixed-up the files). The default is
6089 (e.g. after having manually fixed-up the files). The default is
6091 to mark all unresolved files.
6090 to mark all unresolved files.
6092
6091
6093 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6092 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6094 default is to mark all resolved files.
6093 default is to mark all resolved files.
6095
6094
6096 - :hg:`resolve -l`: list files which had or still have conflicts.
6095 - :hg:`resolve -l`: list files which had or still have conflicts.
6097 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6096 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6098 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6097 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6099 the list. See :hg:`help filesets` for details.
6098 the list. See :hg:`help filesets` for details.
6100
6099
6101 .. note::
6100 .. note::
6102
6101
6103 Mercurial will not let you commit files with unresolved merge
6102 Mercurial will not let you commit files with unresolved merge
6104 conflicts. You must use :hg:`resolve -m ...` before you can
6103 conflicts. You must use :hg:`resolve -m ...` before you can
6105 commit after a conflicting merge.
6104 commit after a conflicting merge.
6106
6105
6107 .. container:: verbose
6106 .. container:: verbose
6108
6107
6109 Template:
6108 Template:
6110
6109
6111 The following keywords are supported in addition to the common template
6110 The following keywords are supported in addition to the common template
6112 keywords and functions. See also :hg:`help templates`.
6111 keywords and functions. See also :hg:`help templates`.
6113
6112
6114 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6113 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6115 :path: String. Repository-absolute path of the file.
6114 :path: String. Repository-absolute path of the file.
6116
6115
6117 Returns 0 on success, 1 if any files fail a resolve attempt.
6116 Returns 0 on success, 1 if any files fail a resolve attempt.
6118 """
6117 """
6119
6118
6120 opts = pycompat.byteskwargs(opts)
6119 opts = pycompat.byteskwargs(opts)
6121 confirm = ui.configbool(b'commands', b'resolve.confirm')
6120 confirm = ui.configbool(b'commands', b'resolve.confirm')
6122 flaglist = b'all mark unmark list no_status re_merge'.split()
6121 flaglist = b'all mark unmark list no_status re_merge'.split()
6123 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6122 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6124
6123
6125 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6124 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6126 if actioncount > 1:
6125 if actioncount > 1:
6127 raise error.InputError(_(b"too many actions specified"))
6126 raise error.InputError(_(b"too many actions specified"))
6128 elif actioncount == 0 and ui.configbool(
6127 elif actioncount == 0 and ui.configbool(
6129 b'commands', b'resolve.explicit-re-merge'
6128 b'commands', b'resolve.explicit-re-merge'
6130 ):
6129 ):
6131 hint = _(b'use --mark, --unmark, --list or --re-merge')
6130 hint = _(b'use --mark, --unmark, --list or --re-merge')
6132 raise error.InputError(_(b'no action specified'), hint=hint)
6131 raise error.InputError(_(b'no action specified'), hint=hint)
6133 if pats and all:
6132 if pats and all:
6134 raise error.InputError(_(b"can't specify --all and patterns"))
6133 raise error.InputError(_(b"can't specify --all and patterns"))
6135 if not (all or pats or show or mark or unmark):
6134 if not (all or pats or show or mark or unmark):
6136 raise error.InputError(
6135 raise error.InputError(
6137 _(b'no files or directories specified'),
6136 _(b'no files or directories specified'),
6138 hint=b'use --all to re-merge all unresolved files',
6137 hint=b'use --all to re-merge all unresolved files',
6139 )
6138 )
6140
6139
6141 if confirm:
6140 if confirm:
6142 if all:
6141 if all:
6143 if ui.promptchoice(
6142 if ui.promptchoice(
6144 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6143 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6145 ):
6144 ):
6146 raise error.CanceledError(_(b'user quit'))
6145 raise error.CanceledError(_(b'user quit'))
6147 if mark and not pats:
6146 if mark and not pats:
6148 if ui.promptchoice(
6147 if ui.promptchoice(
6149 _(
6148 _(
6150 b'mark all unresolved files as resolved (yn)?'
6149 b'mark all unresolved files as resolved (yn)?'
6151 b'$$ &Yes $$ &No'
6150 b'$$ &Yes $$ &No'
6152 )
6151 )
6153 ):
6152 ):
6154 raise error.CanceledError(_(b'user quit'))
6153 raise error.CanceledError(_(b'user quit'))
6155 if unmark and not pats:
6154 if unmark and not pats:
6156 if ui.promptchoice(
6155 if ui.promptchoice(
6157 _(
6156 _(
6158 b'mark all resolved files as unresolved (yn)?'
6157 b'mark all resolved files as unresolved (yn)?'
6159 b'$$ &Yes $$ &No'
6158 b'$$ &Yes $$ &No'
6160 )
6159 )
6161 ):
6160 ):
6162 raise error.CanceledError(_(b'user quit'))
6161 raise error.CanceledError(_(b'user quit'))
6163
6162
6164 uipathfn = scmutil.getuipathfn(repo)
6163 uipathfn = scmutil.getuipathfn(repo)
6165
6164
6166 if show:
6165 if show:
6167 ui.pager(b'resolve')
6166 ui.pager(b'resolve')
6168 fm = ui.formatter(b'resolve', opts)
6167 fm = ui.formatter(b'resolve', opts)
6169 ms = mergestatemod.mergestate.read(repo)
6168 ms = mergestatemod.mergestate.read(repo)
6170 wctx = repo[None]
6169 wctx = repo[None]
6171 m = scmutil.match(wctx, pats, opts)
6170 m = scmutil.match(wctx, pats, opts)
6172
6171
6173 # Labels and keys based on merge state. Unresolved path conflicts show
6172 # Labels and keys based on merge state. Unresolved path conflicts show
6174 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6173 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6175 # resolved conflicts.
6174 # resolved conflicts.
6176 mergestateinfo = {
6175 mergestateinfo = {
6177 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6176 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6178 b'resolve.unresolved',
6177 b'resolve.unresolved',
6179 b'U',
6178 b'U',
6180 ),
6179 ),
6181 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6180 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6182 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6181 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6183 b'resolve.unresolved',
6182 b'resolve.unresolved',
6184 b'P',
6183 b'P',
6185 ),
6184 ),
6186 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6185 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6187 b'resolve.resolved',
6186 b'resolve.resolved',
6188 b'R',
6187 b'R',
6189 ),
6188 ),
6190 }
6189 }
6191
6190
6192 for f in ms:
6191 for f in ms:
6193 if not m(f):
6192 if not m(f):
6194 continue
6193 continue
6195
6194
6196 label, key = mergestateinfo[ms[f]]
6195 label, key = mergestateinfo[ms[f]]
6197 fm.startitem()
6196 fm.startitem()
6198 fm.context(ctx=wctx)
6197 fm.context(ctx=wctx)
6199 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6198 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6200 fm.data(path=f)
6199 fm.data(path=f)
6201 fm.plain(b'%s\n' % uipathfn(f), label=label)
6200 fm.plain(b'%s\n' % uipathfn(f), label=label)
6202 fm.end()
6201 fm.end()
6203 return 0
6202 return 0
6204
6203
6205 with repo.wlock():
6204 with repo.wlock():
6206 ms = mergestatemod.mergestate.read(repo)
6205 ms = mergestatemod.mergestate.read(repo)
6207
6206
6208 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6207 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6209 raise error.StateError(
6208 raise error.StateError(
6210 _(b'resolve command not applicable when not merging')
6209 _(b'resolve command not applicable when not merging')
6211 )
6210 )
6212
6211
6213 wctx = repo[None]
6212 wctx = repo[None]
6214 m = scmutil.match(wctx, pats, opts)
6213 m = scmutil.match(wctx, pats, opts)
6215 ret = 0
6214 ret = 0
6216 didwork = False
6215 didwork = False
6217
6216
6218 hasconflictmarkers = []
6217 hasconflictmarkers = []
6219 if mark:
6218 if mark:
6220 markcheck = ui.config(b'commands', b'resolve.mark-check')
6219 markcheck = ui.config(b'commands', b'resolve.mark-check')
6221 if markcheck not in [b'warn', b'abort']:
6220 if markcheck not in [b'warn', b'abort']:
6222 # Treat all invalid / unrecognized values as 'none'.
6221 # Treat all invalid / unrecognized values as 'none'.
6223 markcheck = False
6222 markcheck = False
6224 for f in ms:
6223 for f in ms:
6225 if not m(f):
6224 if not m(f):
6226 continue
6225 continue
6227
6226
6228 didwork = True
6227 didwork = True
6229
6228
6230 # path conflicts must be resolved manually
6229 # path conflicts must be resolved manually
6231 if ms[f] in (
6230 if ms[f] in (
6232 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6231 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6233 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6232 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6234 ):
6233 ):
6235 if mark:
6234 if mark:
6236 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6235 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6237 elif unmark:
6236 elif unmark:
6238 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6237 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6239 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6238 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6240 ui.warn(
6239 ui.warn(
6241 _(b'%s: path conflict must be resolved manually\n')
6240 _(b'%s: path conflict must be resolved manually\n')
6242 % uipathfn(f)
6241 % uipathfn(f)
6243 )
6242 )
6244 continue
6243 continue
6245
6244
6246 if mark:
6245 if mark:
6247 if markcheck:
6246 if markcheck:
6248 fdata = repo.wvfs.tryread(f)
6247 fdata = repo.wvfs.tryread(f)
6249 if (
6248 if (
6250 filemerge.hasconflictmarkers(fdata)
6249 filemerge.hasconflictmarkers(fdata)
6251 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6250 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6252 ):
6251 ):
6253 hasconflictmarkers.append(f)
6252 hasconflictmarkers.append(f)
6254 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6253 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6255 elif unmark:
6254 elif unmark:
6256 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6255 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6257 else:
6256 else:
6258 # backup pre-resolve (merge uses .orig for its own purposes)
6257 # backup pre-resolve (merge uses .orig for its own purposes)
6259 a = repo.wjoin(f)
6258 a = repo.wjoin(f)
6260 try:
6259 try:
6261 util.copyfile(a, a + b".resolve")
6260 util.copyfile(a, a + b".resolve")
6262 except FileNotFoundError:
6261 except FileNotFoundError:
6263 pass
6262 pass
6264
6263
6265 try:
6264 try:
6266 # preresolve file
6265 # preresolve file
6267 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6266 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6268 with ui.configoverride(overrides, b'resolve'):
6267 with ui.configoverride(overrides, b'resolve'):
6269 r = ms.resolve(f, wctx)
6268 r = ms.resolve(f, wctx)
6270 if r:
6269 if r:
6271 ret = 1
6270 ret = 1
6272 finally:
6271 finally:
6273 ms.commit()
6272 ms.commit()
6274
6273
6275 # replace filemerge's .orig file with our resolve file
6274 # replace filemerge's .orig file with our resolve file
6276 try:
6275 try:
6277 util.rename(
6276 util.rename(
6278 a + b".resolve", scmutil.backuppath(ui, repo, f)
6277 a + b".resolve", scmutil.backuppath(ui, repo, f)
6279 )
6278 )
6280 except FileNotFoundError:
6279 except FileNotFoundError:
6281 pass
6280 pass
6282
6281
6283 if hasconflictmarkers:
6282 if hasconflictmarkers:
6284 ui.warn(
6283 ui.warn(
6285 _(
6284 _(
6286 b'warning: the following files still have conflict '
6285 b'warning: the following files still have conflict '
6287 b'markers:\n'
6286 b'markers:\n'
6288 )
6287 )
6289 + b''.join(
6288 + b''.join(
6290 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6289 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6291 )
6290 )
6292 )
6291 )
6293 if markcheck == b'abort' and not all and not pats:
6292 if markcheck == b'abort' and not all and not pats:
6294 raise error.StateError(
6293 raise error.StateError(
6295 _(b'conflict markers detected'),
6294 _(b'conflict markers detected'),
6296 hint=_(b'use --all to mark anyway'),
6295 hint=_(b'use --all to mark anyway'),
6297 )
6296 )
6298
6297
6299 ms.commit()
6298 ms.commit()
6300 branchmerge = repo.dirstate.p2() != repo.nullid
6299 branchmerge = repo.dirstate.p2() != repo.nullid
6301 # resolve is not doing a parent change here, however, `record updates`
6300 # resolve is not doing a parent change here, however, `record updates`
6302 # will call some dirstate API that at intended for parent changes call.
6301 # will call some dirstate API that at intended for parent changes call.
6303 # Ideally we would not need this and could implement a lighter version
6302 # Ideally we would not need this and could implement a lighter version
6304 # of the recordupdateslogic that will not have to deal with the part
6303 # of the recordupdateslogic that will not have to deal with the part
6305 # related to parent changes. However this would requires that:
6304 # related to parent changes. However this would requires that:
6306 # - we are sure we passed around enough information at update/merge
6305 # - we are sure we passed around enough information at update/merge
6307 # time to no longer needs it at `hg resolve time`
6306 # time to no longer needs it at `hg resolve time`
6308 # - we are sure we store that information well enough to be able to reuse it
6307 # - we are sure we store that information well enough to be able to reuse it
6309 # - we are the necessary logic to reuse it right.
6308 # - we are the necessary logic to reuse it right.
6310 #
6309 #
6311 # All this should eventually happens, but in the mean time, we use this
6310 # All this should eventually happens, but in the mean time, we use this
6312 # context manager slightly out of the context it should be.
6311 # context manager slightly out of the context it should be.
6313 with repo.dirstate.changing_parents(repo):
6312 with repo.dirstate.changing_parents(repo):
6314 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6313 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6315
6314
6316 if not didwork and pats:
6315 if not didwork and pats:
6317 hint = None
6316 hint = None
6318 if not any([p for p in pats if p.find(b':') >= 0]):
6317 if not any([p for p in pats if p.find(b':') >= 0]):
6319 pats = [b'path:%s' % p for p in pats]
6318 pats = [b'path:%s' % p for p in pats]
6320 m = scmutil.match(wctx, pats, opts)
6319 m = scmutil.match(wctx, pats, opts)
6321 for f in ms:
6320 for f in ms:
6322 if not m(f):
6321 if not m(f):
6323 continue
6322 continue
6324
6323
6325 def flag(o):
6324 def flag(o):
6326 if o == b're_merge':
6325 if o == b're_merge':
6327 return b'--re-merge '
6326 return b'--re-merge '
6328 return b'-%s ' % o[0:1]
6327 return b'-%s ' % o[0:1]
6329
6328
6330 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6329 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6331 hint = _(b"(try: hg resolve %s%s)\n") % (
6330 hint = _(b"(try: hg resolve %s%s)\n") % (
6332 flags,
6331 flags,
6333 b' '.join(pats),
6332 b' '.join(pats),
6334 )
6333 )
6335 break
6334 break
6336 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6335 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6337 if hint:
6336 if hint:
6338 ui.warn(hint)
6337 ui.warn(hint)
6339
6338
6340 unresolvedf = ms.unresolvedcount()
6339 unresolvedf = ms.unresolvedcount()
6341 if not unresolvedf:
6340 if not unresolvedf:
6342 ui.status(_(b'(no more unresolved files)\n'))
6341 ui.status(_(b'(no more unresolved files)\n'))
6343 cmdutil.checkafterresolved(repo)
6342 cmdutil.checkafterresolved(repo)
6344
6343
6345 return ret
6344 return ret
6346
6345
6347
6346
6348 @command(
6347 @command(
6349 b'revert',
6348 b'revert',
6350 [
6349 [
6351 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6350 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6352 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6351 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6353 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6352 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6354 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6353 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6355 (b'i', b'interactive', None, _(b'interactively select the changes')),
6354 (b'i', b'interactive', None, _(b'interactively select the changes')),
6356 ]
6355 ]
6357 + walkopts
6356 + walkopts
6358 + dryrunopts,
6357 + dryrunopts,
6359 _(b'[OPTION]... [-r REV] [NAME]...'),
6358 _(b'[OPTION]... [-r REV] [NAME]...'),
6360 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6359 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6361 )
6360 )
6362 def revert(ui, repo, *pats, **opts):
6361 def revert(ui, repo, *pats, **opts):
6363 """restore files to their checkout state
6362 """restore files to their checkout state
6364
6363
6365 .. note::
6364 .. note::
6366
6365
6367 To check out earlier revisions, you should use :hg:`update REV`.
6366 To check out earlier revisions, you should use :hg:`update REV`.
6368 To cancel an uncommitted merge (and lose your changes),
6367 To cancel an uncommitted merge (and lose your changes),
6369 use :hg:`merge --abort`.
6368 use :hg:`merge --abort`.
6370
6369
6371 With no revision specified, revert the specified files or directories
6370 With no revision specified, revert the specified files or directories
6372 to the contents they had in the parent of the working directory.
6371 to the contents they had in the parent of the working directory.
6373 This restores the contents of files to an unmodified
6372 This restores the contents of files to an unmodified
6374 state and unschedules adds, removes, copies, and renames. If the
6373 state and unschedules adds, removes, copies, and renames. If the
6375 working directory has two parents, you must explicitly specify a
6374 working directory has two parents, you must explicitly specify a
6376 revision.
6375 revision.
6377
6376
6378 Using the -r/--rev or -d/--date options, revert the given files or
6377 Using the -r/--rev or -d/--date options, revert the given files or
6379 directories to their states as of a specific revision. Because
6378 directories to their states as of a specific revision. Because
6380 revert does not change the working directory parents, this will
6379 revert does not change the working directory parents, this will
6381 cause these files to appear modified. This can be helpful to "back
6380 cause these files to appear modified. This can be helpful to "back
6382 out" some or all of an earlier change. See :hg:`backout` for a
6381 out" some or all of an earlier change. See :hg:`backout` for a
6383 related method.
6382 related method.
6384
6383
6385 Modified files are saved with a .orig suffix before reverting.
6384 Modified files are saved with a .orig suffix before reverting.
6386 To disable these backups, use --no-backup. It is possible to store
6385 To disable these backups, use --no-backup. It is possible to store
6387 the backup files in a custom directory relative to the root of the
6386 the backup files in a custom directory relative to the root of the
6388 repository by setting the ``ui.origbackuppath`` configuration
6387 repository by setting the ``ui.origbackuppath`` configuration
6389 option.
6388 option.
6390
6389
6391 See :hg:`help dates` for a list of formats valid for -d/--date.
6390 See :hg:`help dates` for a list of formats valid for -d/--date.
6392
6391
6393 See :hg:`help backout` for a way to reverse the effect of an
6392 See :hg:`help backout` for a way to reverse the effect of an
6394 earlier changeset.
6393 earlier changeset.
6395
6394
6396 Returns 0 on success.
6395 Returns 0 on success.
6397 """
6396 """
6398
6397
6399 opts = pycompat.byteskwargs(opts)
6398 opts = pycompat.byteskwargs(opts)
6400 if opts.get(b"date"):
6399 if opts.get(b"date"):
6401 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6400 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6402 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6401 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6403
6402
6404 parent, p2 = repo.dirstate.parents()
6403 parent, p2 = repo.dirstate.parents()
6405 if not opts.get(b'rev') and p2 != repo.nullid:
6404 if not opts.get(b'rev') and p2 != repo.nullid:
6406 # revert after merge is a trap for new users (issue2915)
6405 # revert after merge is a trap for new users (issue2915)
6407 raise error.InputError(
6406 raise error.InputError(
6408 _(b'uncommitted merge with no revision specified'),
6407 _(b'uncommitted merge with no revision specified'),
6409 hint=_(b"use 'hg update' or see 'hg help revert'"),
6408 hint=_(b"use 'hg update' or see 'hg help revert'"),
6410 )
6409 )
6411
6410
6412 rev = opts.get(b'rev')
6411 rev = opts.get(b'rev')
6413 if rev:
6412 if rev:
6414 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6413 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6415 ctx = logcmdutil.revsingle(repo, rev)
6414 ctx = logcmdutil.revsingle(repo, rev)
6416
6415
6417 if not (
6416 if not (
6418 pats
6417 pats
6419 or opts.get(b'include')
6418 or opts.get(b'include')
6420 or opts.get(b'exclude')
6419 or opts.get(b'exclude')
6421 or opts.get(b'all')
6420 or opts.get(b'all')
6422 or opts.get(b'interactive')
6421 or opts.get(b'interactive')
6423 ):
6422 ):
6424 msg = _(b"no files or directories specified")
6423 msg = _(b"no files or directories specified")
6425 if p2 != repo.nullid:
6424 if p2 != repo.nullid:
6426 hint = _(
6425 hint = _(
6427 b"uncommitted merge, use --all to discard all changes,"
6426 b"uncommitted merge, use --all to discard all changes,"
6428 b" or 'hg update -C .' to abort the merge"
6427 b" or 'hg update -C .' to abort the merge"
6429 )
6428 )
6430 raise error.InputError(msg, hint=hint)
6429 raise error.InputError(msg, hint=hint)
6431 dirty = any(repo.status())
6430 dirty = any(repo.status())
6432 node = ctx.node()
6431 node = ctx.node()
6433 if node != parent:
6432 if node != parent:
6434 if dirty:
6433 if dirty:
6435 hint = (
6434 hint = (
6436 _(
6435 _(
6437 b"uncommitted changes, use --all to discard all"
6436 b"uncommitted changes, use --all to discard all"
6438 b" changes, or 'hg update %d' to update"
6437 b" changes, or 'hg update %d' to update"
6439 )
6438 )
6440 % ctx.rev()
6439 % ctx.rev()
6441 )
6440 )
6442 else:
6441 else:
6443 hint = (
6442 hint = (
6444 _(
6443 _(
6445 b"use --all to revert all files,"
6444 b"use --all to revert all files,"
6446 b" or 'hg update %d' to update"
6445 b" or 'hg update %d' to update"
6447 )
6446 )
6448 % ctx.rev()
6447 % ctx.rev()
6449 )
6448 )
6450 elif dirty:
6449 elif dirty:
6451 hint = _(b"uncommitted changes, use --all to discard all changes")
6450 hint = _(b"uncommitted changes, use --all to discard all changes")
6452 else:
6451 else:
6453 hint = _(b"use --all to revert all files")
6452 hint = _(b"use --all to revert all files")
6454 raise error.InputError(msg, hint=hint)
6453 raise error.InputError(msg, hint=hint)
6455
6454
6456 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6455 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6457
6456
6458
6457
6459 @command(
6458 @command(
6460 b'rollback',
6459 b'rollback',
6461 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6460 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6462 helpcategory=command.CATEGORY_MAINTENANCE,
6461 helpcategory=command.CATEGORY_MAINTENANCE,
6463 )
6462 )
6464 def rollback(ui, repo, **opts):
6463 def rollback(ui, repo, **opts):
6465 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6464 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6466
6465
6467 Please use :hg:`commit --amend` instead of rollback to correct
6466 Please use :hg:`commit --amend` instead of rollback to correct
6468 mistakes in the last commit.
6467 mistakes in the last commit.
6469
6468
6470 This command should be used with care. There is only one level of
6469 This command should be used with care. There is only one level of
6471 rollback, and there is no way to undo a rollback. It will also
6470 rollback, and there is no way to undo a rollback. It will also
6472 restore the dirstate at the time of the last transaction, losing
6471 restore the dirstate at the time of the last transaction, losing
6473 any dirstate changes since that time. This command does not alter
6472 any dirstate changes since that time. This command does not alter
6474 the working directory.
6473 the working directory.
6475
6474
6476 Transactions are used to encapsulate the effects of all commands
6475 Transactions are used to encapsulate the effects of all commands
6477 that create new changesets or propagate existing changesets into a
6476 that create new changesets or propagate existing changesets into a
6478 repository.
6477 repository.
6479
6478
6480 .. container:: verbose
6479 .. container:: verbose
6481
6480
6482 For example, the following commands are transactional, and their
6481 For example, the following commands are transactional, and their
6483 effects can be rolled back:
6482 effects can be rolled back:
6484
6483
6485 - commit
6484 - commit
6486 - import
6485 - import
6487 - pull
6486 - pull
6488 - push (with this repository as the destination)
6487 - push (with this repository as the destination)
6489 - unbundle
6488 - unbundle
6490
6489
6491 To avoid permanent data loss, rollback will refuse to rollback a
6490 To avoid permanent data loss, rollback will refuse to rollback a
6492 commit transaction if it isn't checked out. Use --force to
6491 commit transaction if it isn't checked out. Use --force to
6493 override this protection.
6492 override this protection.
6494
6493
6495 The rollback command can be entirely disabled by setting the
6494 The rollback command can be entirely disabled by setting the
6496 ``ui.rollback`` configuration setting to false. If you're here
6495 ``ui.rollback`` configuration setting to false. If you're here
6497 because you want to use rollback and it's disabled, you can
6496 because you want to use rollback and it's disabled, you can
6498 re-enable the command by setting ``ui.rollback`` to true.
6497 re-enable the command by setting ``ui.rollback`` to true.
6499
6498
6500 This command is not intended for use on public repositories. Once
6499 This command is not intended for use on public repositories. Once
6501 changes are visible for pull by other users, rolling a transaction
6500 changes are visible for pull by other users, rolling a transaction
6502 back locally is ineffective (someone else may already have pulled
6501 back locally is ineffective (someone else may already have pulled
6503 the changes). Furthermore, a race is possible with readers of the
6502 the changes). Furthermore, a race is possible with readers of the
6504 repository; for example an in-progress pull from the repository
6503 repository; for example an in-progress pull from the repository
6505 may fail if a rollback is performed.
6504 may fail if a rollback is performed.
6506
6505
6507 Returns 0 on success, 1 if no rollback data is available.
6506 Returns 0 on success, 1 if no rollback data is available.
6508 """
6507 """
6509 if not ui.configbool(b'ui', b'rollback'):
6508 if not ui.configbool(b'ui', b'rollback'):
6510 raise error.Abort(
6509 raise error.Abort(
6511 _(b'rollback is disabled because it is unsafe'),
6510 _(b'rollback is disabled because it is unsafe'),
6512 hint=b'see `hg help -v rollback` for information',
6511 hint=b'see `hg help -v rollback` for information',
6513 )
6512 )
6514 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6513 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6515
6514
6516
6515
6517 @command(
6516 @command(
6518 b'root',
6517 b'root',
6519 [] + formatteropts,
6518 [] + formatteropts,
6520 intents={INTENT_READONLY},
6519 intents={INTENT_READONLY},
6521 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6520 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6522 )
6521 )
6523 def root(ui, repo, **opts):
6522 def root(ui, repo, **opts):
6524 """print the root (top) of the current working directory
6523 """print the root (top) of the current working directory
6525
6524
6526 Print the root directory of the current repository.
6525 Print the root directory of the current repository.
6527
6526
6528 .. container:: verbose
6527 .. container:: verbose
6529
6528
6530 Template:
6529 Template:
6531
6530
6532 The following keywords are supported in addition to the common template
6531 The following keywords are supported in addition to the common template
6533 keywords and functions. See also :hg:`help templates`.
6532 keywords and functions. See also :hg:`help templates`.
6534
6533
6535 :hgpath: String. Path to the .hg directory.
6534 :hgpath: String. Path to the .hg directory.
6536 :storepath: String. Path to the directory holding versioned data.
6535 :storepath: String. Path to the directory holding versioned data.
6537
6536
6538 Returns 0 on success.
6537 Returns 0 on success.
6539 """
6538 """
6540 opts = pycompat.byteskwargs(opts)
6539 opts = pycompat.byteskwargs(opts)
6541 with ui.formatter(b'root', opts) as fm:
6540 with ui.formatter(b'root', opts) as fm:
6542 fm.startitem()
6541 fm.startitem()
6543 fm.write(b'reporoot', b'%s\n', repo.root)
6542 fm.write(b'reporoot', b'%s\n', repo.root)
6544 fm.data(hgpath=repo.path, storepath=repo.spath)
6543 fm.data(hgpath=repo.path, storepath=repo.spath)
6545
6544
6546
6545
6547 @command(
6546 @command(
6548 b'serve',
6547 b'serve',
6549 [
6548 [
6550 (
6549 (
6551 b'A',
6550 b'A',
6552 b'accesslog',
6551 b'accesslog',
6553 b'',
6552 b'',
6554 _(b'name of access log file to write to'),
6553 _(b'name of access log file to write to'),
6555 _(b'FILE'),
6554 _(b'FILE'),
6556 ),
6555 ),
6557 (b'd', b'daemon', None, _(b'run server in background')),
6556 (b'd', b'daemon', None, _(b'run server in background')),
6558 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6557 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6559 (
6558 (
6560 b'E',
6559 b'E',
6561 b'errorlog',
6560 b'errorlog',
6562 b'',
6561 b'',
6563 _(b'name of error log file to write to'),
6562 _(b'name of error log file to write to'),
6564 _(b'FILE'),
6563 _(b'FILE'),
6565 ),
6564 ),
6566 # use string type, then we can check if something was passed
6565 # use string type, then we can check if something was passed
6567 (
6566 (
6568 b'p',
6567 b'p',
6569 b'port',
6568 b'port',
6570 b'',
6569 b'',
6571 _(b'port to listen on (default: 8000)'),
6570 _(b'port to listen on (default: 8000)'),
6572 _(b'PORT'),
6571 _(b'PORT'),
6573 ),
6572 ),
6574 (
6573 (
6575 b'a',
6574 b'a',
6576 b'address',
6575 b'address',
6577 b'',
6576 b'',
6578 _(b'address to listen on (default: all interfaces)'),
6577 _(b'address to listen on (default: all interfaces)'),
6579 _(b'ADDR'),
6578 _(b'ADDR'),
6580 ),
6579 ),
6581 (
6580 (
6582 b'',
6581 b'',
6583 b'prefix',
6582 b'prefix',
6584 b'',
6583 b'',
6585 _(b'prefix path to serve from (default: server root)'),
6584 _(b'prefix path to serve from (default: server root)'),
6586 _(b'PREFIX'),
6585 _(b'PREFIX'),
6587 ),
6586 ),
6588 (
6587 (
6589 b'n',
6588 b'n',
6590 b'name',
6589 b'name',
6591 b'',
6590 b'',
6592 _(b'name to show in web pages (default: working directory)'),
6591 _(b'name to show in web pages (default: working directory)'),
6593 _(b'NAME'),
6592 _(b'NAME'),
6594 ),
6593 ),
6595 (
6594 (
6596 b'',
6595 b'',
6597 b'web-conf',
6596 b'web-conf',
6598 b'',
6597 b'',
6599 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6598 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6600 _(b'FILE'),
6599 _(b'FILE'),
6601 ),
6600 ),
6602 (
6601 (
6603 b'',
6602 b'',
6604 b'webdir-conf',
6603 b'webdir-conf',
6605 b'',
6604 b'',
6606 _(b'name of the hgweb config file (DEPRECATED)'),
6605 _(b'name of the hgweb config file (DEPRECATED)'),
6607 _(b'FILE'),
6606 _(b'FILE'),
6608 ),
6607 ),
6609 (
6608 (
6610 b'',
6609 b'',
6611 b'pid-file',
6610 b'pid-file',
6612 b'',
6611 b'',
6613 _(b'name of file to write process ID to'),
6612 _(b'name of file to write process ID to'),
6614 _(b'FILE'),
6613 _(b'FILE'),
6615 ),
6614 ),
6616 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6615 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6617 (
6616 (
6618 b'',
6617 b'',
6619 b'cmdserver',
6618 b'cmdserver',
6620 b'',
6619 b'',
6621 _(b'for remote clients (ADVANCED)'),
6620 _(b'for remote clients (ADVANCED)'),
6622 _(b'MODE'),
6621 _(b'MODE'),
6623 ),
6622 ),
6624 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6623 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6625 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6624 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6626 (b'6', b'ipv6', None, _(b'use IPv6 instead of IPv4')),
6625 (b'6', b'ipv6', None, _(b'use IPv6 instead of IPv4')),
6627 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6626 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6628 (b'', b'print-url', None, _(b'start and print only the URL')),
6627 (b'', b'print-url', None, _(b'start and print only the URL')),
6629 ]
6628 ]
6630 + subrepoopts,
6629 + subrepoopts,
6631 _(b'[OPTION]...'),
6630 _(b'[OPTION]...'),
6632 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6631 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6633 helpbasic=True,
6632 helpbasic=True,
6634 optionalrepo=True,
6633 optionalrepo=True,
6635 )
6634 )
6636 def serve(ui, repo, **opts):
6635 def serve(ui, repo, **opts):
6637 """start stand-alone webserver
6636 """start stand-alone webserver
6638
6637
6639 Start a local HTTP repository browser and pull server. You can use
6638 Start a local HTTP repository browser and pull server. You can use
6640 this for ad-hoc sharing and browsing of repositories. It is
6639 this for ad-hoc sharing and browsing of repositories. It is
6641 recommended to use a real web server to serve a repository for
6640 recommended to use a real web server to serve a repository for
6642 longer periods of time.
6641 longer periods of time.
6643
6642
6644 Please note that the server does not implement access control.
6643 Please note that the server does not implement access control.
6645 This means that, by default, anybody can read from the server and
6644 This means that, by default, anybody can read from the server and
6646 nobody can write to it by default. Set the ``web.allow-push``
6645 nobody can write to it by default. Set the ``web.allow-push``
6647 option to ``*`` to allow everybody to push to the server. You
6646 option to ``*`` to allow everybody to push to the server. You
6648 should use a real web server if you need to authenticate users.
6647 should use a real web server if you need to authenticate users.
6649
6648
6650 By default, the server logs accesses to stdout and errors to
6649 By default, the server logs accesses to stdout and errors to
6651 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6650 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6652 files.
6651 files.
6653
6652
6654 To have the server choose a free port number to listen on, specify
6653 To have the server choose a free port number to listen on, specify
6655 a port number of 0; in this case, the server will print the port
6654 a port number of 0; in this case, the server will print the port
6656 number it uses.
6655 number it uses.
6657
6656
6658 Returns 0 on success.
6657 Returns 0 on success.
6659 """
6658 """
6660
6659
6661 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6660 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6662 opts = pycompat.byteskwargs(opts)
6661 opts = pycompat.byteskwargs(opts)
6663 if opts[b"print_url"] and ui.verbose:
6662 if opts[b"print_url"] and ui.verbose:
6664 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6663 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6665
6664
6666 if opts[b"stdio"]:
6665 if opts[b"stdio"]:
6667 if repo is None:
6666 if repo is None:
6668 raise error.RepoError(
6667 raise error.RepoError(
6669 _(b"there is no Mercurial repository here (.hg not found)")
6668 _(b"there is no Mercurial repository here (.hg not found)")
6670 )
6669 )
6671 accesshidden = False
6670 accesshidden = False
6672 if repo.filtername is None:
6671 if repo.filtername is None:
6673 allow = ui.configlist(
6672 allow = ui.configlist(
6674 b'experimental', b'server.allow-hidden-access'
6673 b'experimental', b'server.allow-hidden-access'
6675 )
6674 )
6676 user = procutil.getuser()
6675 user = procutil.getuser()
6677 if allow and scmutil.ismember(ui, user, allow):
6676 if allow and scmutil.ismember(ui, user, allow):
6678 accesshidden = True
6677 accesshidden = True
6679 else:
6678 else:
6680 msg = (
6679 msg = (
6681 _(
6680 _(
6682 b'ignoring request to access hidden changeset by '
6681 b'ignoring request to access hidden changeset by '
6683 b'unauthorized user: %s\n'
6682 b'unauthorized user: %s\n'
6684 )
6683 )
6685 % user
6684 % user
6686 )
6685 )
6687 ui.warn(msg)
6686 ui.warn(msg)
6688
6687
6689 s = wireprotoserver.sshserver(ui, repo, accesshidden=accesshidden)
6688 s = wireprotoserver.sshserver(ui, repo, accesshidden=accesshidden)
6690 s.serve_forever()
6689 s.serve_forever()
6691 return
6690 return
6692
6691
6693 service = server.createservice(ui, repo, opts)
6692 service = server.createservice(ui, repo, opts)
6694 return server.runservice(opts, initfn=service.init, runfn=service.run)
6693 return server.runservice(opts, initfn=service.init, runfn=service.run)
6695
6694
6696
6695
6697 @command(
6696 @command(
6698 b'shelve',
6697 b'shelve',
6699 [
6698 [
6700 (
6699 (
6701 b'A',
6700 b'A',
6702 b'addremove',
6701 b'addremove',
6703 None,
6702 None,
6704 _(b'mark new/missing files as added/removed before shelving'),
6703 _(b'mark new/missing files as added/removed before shelving'),
6705 ),
6704 ),
6706 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6705 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6707 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6706 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6708 (
6707 (
6709 b'',
6708 b'',
6710 b'date',
6709 b'date',
6711 b'',
6710 b'',
6712 _(b'shelve with the specified commit date'),
6711 _(b'shelve with the specified commit date'),
6713 _(b'DATE'),
6712 _(b'DATE'),
6714 ),
6713 ),
6715 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6714 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6716 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6715 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6717 (
6716 (
6718 b'k',
6717 b'k',
6719 b'keep',
6718 b'keep',
6720 False,
6719 False,
6721 _(b'shelve, but keep changes in the working directory'),
6720 _(b'shelve, but keep changes in the working directory'),
6722 ),
6721 ),
6723 (b'l', b'list', None, _(b'list current shelves')),
6722 (b'l', b'list', None, _(b'list current shelves')),
6724 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6723 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6725 (
6724 (
6726 b'n',
6725 b'n',
6727 b'name',
6726 b'name',
6728 b'',
6727 b'',
6729 _(b'use the given name for the shelved commit'),
6728 _(b'use the given name for the shelved commit'),
6730 _(b'NAME'),
6729 _(b'NAME'),
6731 ),
6730 ),
6732 (
6731 (
6733 b'p',
6732 b'p',
6734 b'patch',
6733 b'patch',
6735 None,
6734 None,
6736 _(
6735 _(
6737 b'output patches for changes (provide the names of the shelved '
6736 b'output patches for changes (provide the names of the shelved '
6738 b'changes as positional arguments)'
6737 b'changes as positional arguments)'
6739 ),
6738 ),
6740 ),
6739 ),
6741 (b'i', b'interactive', None, _(b'interactive mode')),
6740 (b'i', b'interactive', None, _(b'interactive mode')),
6742 (
6741 (
6743 b'',
6742 b'',
6744 b'stat',
6743 b'stat',
6745 None,
6744 None,
6746 _(
6745 _(
6747 b'output diffstat-style summary of changes (provide the names of '
6746 b'output diffstat-style summary of changes (provide the names of '
6748 b'the shelved changes as positional arguments)'
6747 b'the shelved changes as positional arguments)'
6749 ),
6748 ),
6750 ),
6749 ),
6751 ]
6750 ]
6752 + cmdutil.walkopts,
6751 + cmdutil.walkopts,
6753 _(b'hg shelve [OPTION]... [FILE]...'),
6752 _(b'hg shelve [OPTION]... [FILE]...'),
6754 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6753 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6755 )
6754 )
6756 def shelve(ui, repo, *pats, **opts):
6755 def shelve(ui, repo, *pats, **opts):
6757 """save and set aside changes from the working directory
6756 """save and set aside changes from the working directory
6758
6757
6759 Shelving takes files that "hg status" reports as not clean, saves
6758 Shelving takes files that "hg status" reports as not clean, saves
6760 the modifications to a bundle (a shelved change), and reverts the
6759 the modifications to a bundle (a shelved change), and reverts the
6761 files so that their state in the working directory becomes clean.
6760 files so that their state in the working directory becomes clean.
6762
6761
6763 To restore these changes to the working directory, using "hg
6762 To restore these changes to the working directory, using "hg
6764 unshelve"; this will work even if you switch to a different
6763 unshelve"; this will work even if you switch to a different
6765 commit.
6764 commit.
6766
6765
6767 When no files are specified, "hg shelve" saves all not-clean
6766 When no files are specified, "hg shelve" saves all not-clean
6768 files. If specific files or directories are named, only changes to
6767 files. If specific files or directories are named, only changes to
6769 those files are shelved.
6768 those files are shelved.
6770
6769
6771 In bare shelve (when no files are specified, without interactive,
6770 In bare shelve (when no files are specified, without interactive,
6772 include and exclude option), shelving remembers information if the
6771 include and exclude option), shelving remembers information if the
6773 working directory was on newly created branch, in other words working
6772 working directory was on newly created branch, in other words working
6774 directory was on different branch than its first parent. In this
6773 directory was on different branch than its first parent. In this
6775 situation unshelving restores branch information to the working directory.
6774 situation unshelving restores branch information to the working directory.
6776
6775
6777 Each shelved change has a name that makes it easier to find later.
6776 Each shelved change has a name that makes it easier to find later.
6778 The name of a shelved change defaults to being based on the active
6777 The name of a shelved change defaults to being based on the active
6779 bookmark, or if there is no active bookmark, the current named
6778 bookmark, or if there is no active bookmark, the current named
6780 branch. To specify a different name, use ``--name``.
6779 branch. To specify a different name, use ``--name``.
6781
6780
6782 To see a list of existing shelved changes, use the ``--list``
6781 To see a list of existing shelved changes, use the ``--list``
6783 option. For each shelved change, this will print its name, age,
6782 option. For each shelved change, this will print its name, age,
6784 and description; use ``--patch`` or ``--stat`` for more details.
6783 and description; use ``--patch`` or ``--stat`` for more details.
6785
6784
6786 To delete specific shelved changes, use ``--delete``. To delete
6785 To delete specific shelved changes, use ``--delete``. To delete
6787 all shelved changes, use ``--cleanup``.
6786 all shelved changes, use ``--cleanup``.
6788 """
6787 """
6789 opts = pycompat.byteskwargs(opts)
6788 opts = pycompat.byteskwargs(opts)
6790 allowables = [
6789 allowables = [
6791 (b'addremove', {b'create'}), # 'create' is pseudo action
6790 (b'addremove', {b'create'}), # 'create' is pseudo action
6792 (b'unknown', {b'create'}),
6791 (b'unknown', {b'create'}),
6793 (b'cleanup', {b'cleanup'}),
6792 (b'cleanup', {b'cleanup'}),
6794 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6793 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6795 (b'delete', {b'delete'}),
6794 (b'delete', {b'delete'}),
6796 (b'edit', {b'create'}),
6795 (b'edit', {b'create'}),
6797 (b'keep', {b'create'}),
6796 (b'keep', {b'create'}),
6798 (b'list', {b'list'}),
6797 (b'list', {b'list'}),
6799 (b'message', {b'create'}),
6798 (b'message', {b'create'}),
6800 (b'name', {b'create'}),
6799 (b'name', {b'create'}),
6801 (b'patch', {b'patch', b'list'}),
6800 (b'patch', {b'patch', b'list'}),
6802 (b'stat', {b'stat', b'list'}),
6801 (b'stat', {b'stat', b'list'}),
6803 ]
6802 ]
6804
6803
6805 def checkopt(opt):
6804 def checkopt(opt):
6806 if opts.get(opt):
6805 if opts.get(opt):
6807 for i, allowable in allowables:
6806 for i, allowable in allowables:
6808 if opts[i] and opt not in allowable:
6807 if opts[i] and opt not in allowable:
6809 raise error.InputError(
6808 raise error.InputError(
6810 _(
6809 _(
6811 b"options '--%s' and '--%s' may not be "
6810 b"options '--%s' and '--%s' may not be "
6812 b"used together"
6811 b"used together"
6813 )
6812 )
6814 % (opt, i)
6813 % (opt, i)
6815 )
6814 )
6816 return True
6815 return True
6817
6816
6818 if checkopt(b'cleanup'):
6817 if checkopt(b'cleanup'):
6819 if pats:
6818 if pats:
6820 raise error.InputError(
6819 raise error.InputError(
6821 _(b"cannot specify names when using '--cleanup'")
6820 _(b"cannot specify names when using '--cleanup'")
6822 )
6821 )
6823 return shelvemod.cleanupcmd(ui, repo)
6822 return shelvemod.cleanupcmd(ui, repo)
6824 elif checkopt(b'delete'):
6823 elif checkopt(b'delete'):
6825 return shelvemod.deletecmd(ui, repo, pats)
6824 return shelvemod.deletecmd(ui, repo, pats)
6826 elif checkopt(b'list'):
6825 elif checkopt(b'list'):
6827 return shelvemod.listcmd(ui, repo, pats, opts)
6826 return shelvemod.listcmd(ui, repo, pats, opts)
6828 elif checkopt(b'patch') or checkopt(b'stat'):
6827 elif checkopt(b'patch') or checkopt(b'stat'):
6829 return shelvemod.patchcmds(ui, repo, pats, opts)
6828 return shelvemod.patchcmds(ui, repo, pats, opts)
6830 else:
6829 else:
6831 return shelvemod.createcmd(ui, repo, pats, opts)
6830 return shelvemod.createcmd(ui, repo, pats, opts)
6832
6831
6833
6832
6834 _NOTTERSE = b'nothing'
6833 _NOTTERSE = b'nothing'
6835
6834
6836
6835
6837 @command(
6836 @command(
6838 b'status|st',
6837 b'status|st',
6839 [
6838 [
6840 (b'A', b'all', None, _(b'show status of all files')),
6839 (b'A', b'all', None, _(b'show status of all files')),
6841 (b'm', b'modified', None, _(b'show only modified files')),
6840 (b'm', b'modified', None, _(b'show only modified files')),
6842 (b'a', b'added', None, _(b'show only added files')),
6841 (b'a', b'added', None, _(b'show only added files')),
6843 (b'r', b'removed', None, _(b'show only removed files')),
6842 (b'r', b'removed', None, _(b'show only removed files')),
6844 (b'd', b'deleted', None, _(b'show only missing files')),
6843 (b'd', b'deleted', None, _(b'show only missing files')),
6845 (b'c', b'clean', None, _(b'show only files without changes')),
6844 (b'c', b'clean', None, _(b'show only files without changes')),
6846 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6845 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6847 (b'i', b'ignored', None, _(b'show only ignored files')),
6846 (b'i', b'ignored', None, _(b'show only ignored files')),
6848 (b'n', b'no-status', None, _(b'hide status prefix')),
6847 (b'n', b'no-status', None, _(b'hide status prefix')),
6849 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6848 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6850 (
6849 (
6851 b'C',
6850 b'C',
6852 b'copies',
6851 b'copies',
6853 None,
6852 None,
6854 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6853 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6855 ),
6854 ),
6856 (
6855 (
6857 b'0',
6856 b'0',
6858 b'print0',
6857 b'print0',
6859 None,
6858 None,
6860 _(b'end filenames with NUL, for use with xargs'),
6859 _(b'end filenames with NUL, for use with xargs'),
6861 ),
6860 ),
6862 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6861 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6863 (
6862 (
6864 b'',
6863 b'',
6865 b'change',
6864 b'change',
6866 b'',
6865 b'',
6867 _(b'list the changed files of a revision'),
6866 _(b'list the changed files of a revision'),
6868 _(b'REV'),
6867 _(b'REV'),
6869 ),
6868 ),
6870 ]
6869 ]
6871 + walkopts
6870 + walkopts
6872 + subrepoopts
6871 + subrepoopts
6873 + formatteropts,
6872 + formatteropts,
6874 _(b'[OPTION]... [FILE]...'),
6873 _(b'[OPTION]... [FILE]...'),
6875 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6874 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6876 helpbasic=True,
6875 helpbasic=True,
6877 inferrepo=True,
6876 inferrepo=True,
6878 intents={INTENT_READONLY},
6877 intents={INTENT_READONLY},
6879 )
6878 )
6880 def status(ui, repo, *pats, **opts):
6879 def status(ui, repo, *pats, **opts):
6881 """show changed files in the working directory
6880 """show changed files in the working directory
6882
6881
6883 Show status of files in the repository. If names are given, only
6882 Show status of files in the repository. If names are given, only
6884 files that match are shown. Files that are clean or ignored or
6883 files that match are shown. Files that are clean or ignored or
6885 the source of a copy/move operation, are not listed unless
6884 the source of a copy/move operation, are not listed unless
6886 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6885 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6887 Unless options described with "show only ..." are given, the
6886 Unless options described with "show only ..." are given, the
6888 options -mardu are used.
6887 options -mardu are used.
6889
6888
6890 Option -q/--quiet hides untracked (unknown and ignored) files
6889 Option -q/--quiet hides untracked (unknown and ignored) files
6891 unless explicitly requested with -u/--unknown or -i/--ignored.
6890 unless explicitly requested with -u/--unknown or -i/--ignored.
6892
6891
6893 .. note::
6892 .. note::
6894
6893
6895 :hg:`status` may appear to disagree with diff if permissions have
6894 :hg:`status` may appear to disagree with diff if permissions have
6896 changed or a merge has occurred. The standard diff format does
6895 changed or a merge has occurred. The standard diff format does
6897 not report permission changes and diff only reports changes
6896 not report permission changes and diff only reports changes
6898 relative to one merge parent.
6897 relative to one merge parent.
6899
6898
6900 If one revision is given, it is used as the base revision.
6899 If one revision is given, it is used as the base revision.
6901 If two revisions are given, the differences between them are
6900 If two revisions are given, the differences between them are
6902 shown. The --change option can also be used as a shortcut to list
6901 shown. The --change option can also be used as a shortcut to list
6903 the changed files of a revision from its first parent.
6902 the changed files of a revision from its first parent.
6904
6903
6905 The codes used to show the status of files are::
6904 The codes used to show the status of files are::
6906
6905
6907 M = modified
6906 M = modified
6908 A = added
6907 A = added
6909 R = removed
6908 R = removed
6910 C = clean
6909 C = clean
6911 ! = missing (deleted by non-hg command, but still tracked)
6910 ! = missing (deleted by non-hg command, but still tracked)
6912 ? = not tracked
6911 ? = not tracked
6913 I = ignored
6912 I = ignored
6914 = origin of the previous file (with --copies)
6913 = origin of the previous file (with --copies)
6915
6914
6916 .. container:: verbose
6915 .. container:: verbose
6917
6916
6918 The -t/--terse option abbreviates the output by showing only the directory
6917 The -t/--terse option abbreviates the output by showing only the directory
6919 name if all the files in it share the same status. The option takes an
6918 name if all the files in it share the same status. The option takes an
6920 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6919 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6921 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6920 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6922 for 'ignored' and 'c' for clean.
6921 for 'ignored' and 'c' for clean.
6923
6922
6924 It abbreviates only those statuses which are passed. Note that clean and
6923 It abbreviates only those statuses which are passed. Note that clean and
6925 ignored files are not displayed with '--terse ic' unless the -c/--clean
6924 ignored files are not displayed with '--terse ic' unless the -c/--clean
6926 and -i/--ignored options are also used.
6925 and -i/--ignored options are also used.
6927
6926
6928 The -v/--verbose option shows information when the repository is in an
6927 The -v/--verbose option shows information when the repository is in an
6929 unfinished merge, shelve, rebase state etc. You can have this behavior
6928 unfinished merge, shelve, rebase state etc. You can have this behavior
6930 turned on by default by enabling the ``commands.status.verbose`` option.
6929 turned on by default by enabling the ``commands.status.verbose`` option.
6931
6930
6932 You can skip displaying some of these states by setting
6931 You can skip displaying some of these states by setting
6933 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6932 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6934 'histedit', 'merge', 'rebase', or 'unshelve'.
6933 'histedit', 'merge', 'rebase', or 'unshelve'.
6935
6934
6936 Template:
6935 Template:
6937
6936
6938 The following keywords are supported in addition to the common template
6937 The following keywords are supported in addition to the common template
6939 keywords and functions. See also :hg:`help templates`.
6938 keywords and functions. See also :hg:`help templates`.
6940
6939
6941 :path: String. Repository-absolute path of the file.
6940 :path: String. Repository-absolute path of the file.
6942 :source: String. Repository-absolute path of the file originated from.
6941 :source: String. Repository-absolute path of the file originated from.
6943 Available if ``--copies`` is specified.
6942 Available if ``--copies`` is specified.
6944 :status: String. Character denoting file's status.
6943 :status: String. Character denoting file's status.
6945
6944
6946 Examples:
6945 Examples:
6947
6946
6948 - show changes in the working directory relative to a
6947 - show changes in the working directory relative to a
6949 changeset::
6948 changeset::
6950
6949
6951 hg status --rev 9353
6950 hg status --rev 9353
6952
6951
6953 - show changes in the working directory relative to the
6952 - show changes in the working directory relative to the
6954 current directory (see :hg:`help patterns` for more information)::
6953 current directory (see :hg:`help patterns` for more information)::
6955
6954
6956 hg status re:
6955 hg status re:
6957
6956
6958 - show all changes including copies in an existing changeset::
6957 - show all changes including copies in an existing changeset::
6959
6958
6960 hg status --copies --change 9353
6959 hg status --copies --change 9353
6961
6960
6962 - get a NUL separated list of added files, suitable for xargs::
6961 - get a NUL separated list of added files, suitable for xargs::
6963
6962
6964 hg status -an0
6963 hg status -an0
6965
6964
6966 - show more information about the repository status, abbreviating
6965 - show more information about the repository status, abbreviating
6967 added, removed, modified, deleted, and untracked paths::
6966 added, removed, modified, deleted, and untracked paths::
6968
6967
6969 hg status -v -t mardu
6968 hg status -v -t mardu
6970
6969
6971 Returns 0 on success.
6970 Returns 0 on success.
6972
6971
6973 """
6972 """
6974
6973
6975 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6974 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6976 opts = pycompat.byteskwargs(opts)
6975 opts = pycompat.byteskwargs(opts)
6977 revs = opts.get(b'rev', [])
6976 revs = opts.get(b'rev', [])
6978 change = opts.get(b'change', b'')
6977 change = opts.get(b'change', b'')
6979 terse = opts.get(b'terse', _NOTTERSE)
6978 terse = opts.get(b'terse', _NOTTERSE)
6980 if terse is _NOTTERSE:
6979 if terse is _NOTTERSE:
6981 if revs:
6980 if revs:
6982 terse = b''
6981 terse = b''
6983 else:
6982 else:
6984 terse = ui.config(b'commands', b'status.terse')
6983 terse = ui.config(b'commands', b'status.terse')
6985
6984
6986 if revs and terse:
6985 if revs and terse:
6987 msg = _(b'cannot use --terse with --rev')
6986 msg = _(b'cannot use --terse with --rev')
6988 raise error.InputError(msg)
6987 raise error.InputError(msg)
6989 elif change:
6988 elif change:
6990 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6989 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6991 ctx2 = logcmdutil.revsingle(repo, change, None)
6990 ctx2 = logcmdutil.revsingle(repo, change, None)
6992 ctx1 = ctx2.p1()
6991 ctx1 = ctx2.p1()
6993 else:
6992 else:
6994 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6993 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6995 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6994 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6996
6995
6997 forcerelativevalue = None
6996 forcerelativevalue = None
6998 if ui.hasconfig(b'commands', b'status.relative'):
6997 if ui.hasconfig(b'commands', b'status.relative'):
6999 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6998 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
7000 uipathfn = scmutil.getuipathfn(
6999 uipathfn = scmutil.getuipathfn(
7001 repo,
7000 repo,
7002 legacyrelativevalue=bool(pats),
7001 legacyrelativevalue=bool(pats),
7003 forcerelativevalue=forcerelativevalue,
7002 forcerelativevalue=forcerelativevalue,
7004 )
7003 )
7005
7004
7006 if opts.get(b'print0'):
7005 if opts.get(b'print0'):
7007 end = b'\0'
7006 end = b'\0'
7008 else:
7007 else:
7009 end = b'\n'
7008 end = b'\n'
7010 states = b'modified added removed deleted unknown ignored clean'.split()
7009 states = b'modified added removed deleted unknown ignored clean'.split()
7011 show = [k for k in states if opts.get(k)]
7010 show = [k for k in states if opts.get(k)]
7012 if opts.get(b'all'):
7011 if opts.get(b'all'):
7013 show += ui.quiet and (states[:4] + [b'clean']) or states
7012 show += ui.quiet and (states[:4] + [b'clean']) or states
7014
7013
7015 if not show:
7014 if not show:
7016 if ui.quiet:
7015 if ui.quiet:
7017 show = states[:4]
7016 show = states[:4]
7018 else:
7017 else:
7019 show = states[:5]
7018 show = states[:5]
7020
7019
7021 m = scmutil.match(ctx2, pats, opts)
7020 m = scmutil.match(ctx2, pats, opts)
7022 if terse:
7021 if terse:
7023 # we need to compute clean and unknown to terse
7022 # we need to compute clean and unknown to terse
7024 stat = repo.status(
7023 stat = repo.status(
7025 ctx1.node(),
7024 ctx1.node(),
7026 ctx2.node(),
7025 ctx2.node(),
7027 m,
7026 m,
7028 b'ignored' in show or b'i' in terse,
7027 b'ignored' in show or b'i' in terse,
7029 clean=True,
7028 clean=True,
7030 unknown=True,
7029 unknown=True,
7031 listsubrepos=opts.get(b'subrepos'),
7030 listsubrepos=opts.get(b'subrepos'),
7032 )
7031 )
7033
7032
7034 stat = cmdutil.tersedir(stat, terse)
7033 stat = cmdutil.tersedir(stat, terse)
7035 else:
7034 else:
7036 stat = repo.status(
7035 stat = repo.status(
7037 ctx1.node(),
7036 ctx1.node(),
7038 ctx2.node(),
7037 ctx2.node(),
7039 m,
7038 m,
7040 b'ignored' in show,
7039 b'ignored' in show,
7041 b'clean' in show,
7040 b'clean' in show,
7042 b'unknown' in show,
7041 b'unknown' in show,
7043 opts.get(b'subrepos'),
7042 opts.get(b'subrepos'),
7044 )
7043 )
7045
7044
7046 changestates = zip(
7045 changestates = zip(
7047 states,
7046 states,
7048 pycompat.iterbytestr(b'MAR!?IC'),
7047 pycompat.iterbytestr(b'MAR!?IC'),
7049 [getattr(stat, s.decode('utf8')) for s in states],
7048 [getattr(stat, s.decode('utf8')) for s in states],
7050 )
7049 )
7051
7050
7052 copy = {}
7051 copy = {}
7053 show_copies = ui.configbool(b'ui', b'statuscopies')
7052 show_copies = ui.configbool(b'ui', b'statuscopies')
7054 if opts.get(b'copies') is not None:
7053 if opts.get(b'copies') is not None:
7055 show_copies = opts.get(b'copies')
7054 show_copies = opts.get(b'copies')
7056 show_copies = (show_copies or opts.get(b'all')) and not opts.get(
7055 show_copies = (show_copies or opts.get(b'all')) and not opts.get(
7057 b'no_status'
7056 b'no_status'
7058 )
7057 )
7059 if show_copies:
7058 if show_copies:
7060 copy = copies.pathcopies(ctx1, ctx2, m)
7059 copy = copies.pathcopies(ctx1, ctx2, m)
7061
7060
7062 morestatus = None
7061 morestatus = None
7063 if (
7062 if (
7064 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
7063 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
7065 and not ui.plain()
7064 and not ui.plain()
7066 and not opts.get(b'print0')
7065 and not opts.get(b'print0')
7067 ):
7066 ):
7068 morestatus = cmdutil.readmorestatus(repo)
7067 morestatus = cmdutil.readmorestatus(repo)
7069
7068
7070 ui.pager(b'status')
7069 ui.pager(b'status')
7071 fm = ui.formatter(b'status', opts)
7070 fm = ui.formatter(b'status', opts)
7072 fmt = b'%s' + end
7071 fmt = b'%s' + end
7073 showchar = not opts.get(b'no_status')
7072 showchar = not opts.get(b'no_status')
7074
7073
7075 for state, char, files in changestates:
7074 for state, char, files in changestates:
7076 if state in show:
7075 if state in show:
7077 label = b'status.' + state
7076 label = b'status.' + state
7078 for f in files:
7077 for f in files:
7079 fm.startitem()
7078 fm.startitem()
7080 fm.context(ctx=ctx2)
7079 fm.context(ctx=ctx2)
7081 fm.data(itemtype=b'file', path=f)
7080 fm.data(itemtype=b'file', path=f)
7082 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
7081 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
7083 fm.plain(fmt % uipathfn(f), label=label)
7082 fm.plain(fmt % uipathfn(f), label=label)
7084 if f in copy:
7083 if f in copy:
7085 fm.data(source=copy[f])
7084 fm.data(source=copy[f])
7086 fm.plain(
7085 fm.plain(
7087 (b' %s' + end) % uipathfn(copy[f]),
7086 (b' %s' + end) % uipathfn(copy[f]),
7088 label=b'status.copied',
7087 label=b'status.copied',
7089 )
7088 )
7090 if morestatus:
7089 if morestatus:
7091 morestatus.formatfile(f, fm)
7090 morestatus.formatfile(f, fm)
7092
7091
7093 if morestatus:
7092 if morestatus:
7094 morestatus.formatfooter(fm)
7093 morestatus.formatfooter(fm)
7095 fm.end()
7094 fm.end()
7096
7095
7097
7096
7098 @command(
7097 @command(
7099 b'summary|sum',
7098 b'summary|sum',
7100 [(b'', b'remote', None, _(b'check for push and pull'))],
7099 [(b'', b'remote', None, _(b'check for push and pull'))],
7101 b'[--remote]',
7100 b'[--remote]',
7102 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7101 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7103 helpbasic=True,
7102 helpbasic=True,
7104 intents={INTENT_READONLY},
7103 intents={INTENT_READONLY},
7105 )
7104 )
7106 def summary(ui, repo, **opts):
7105 def summary(ui, repo, **opts):
7107 """summarize working directory state
7106 """summarize working directory state
7108
7107
7109 This generates a brief summary of the working directory state,
7108 This generates a brief summary of the working directory state,
7110 including parents, branch, commit status, phase and available updates.
7109 including parents, branch, commit status, phase and available updates.
7111
7110
7112 With the --remote option, this will check the default paths for
7111 With the --remote option, this will check the default paths for
7113 incoming and outgoing changes. This can be time-consuming.
7112 incoming and outgoing changes. This can be time-consuming.
7114
7113
7115 Returns 0 on success.
7114 Returns 0 on success.
7116 """
7115 """
7117
7116
7118 opts = pycompat.byteskwargs(opts)
7117 opts = pycompat.byteskwargs(opts)
7119 ui.pager(b'summary')
7118 ui.pager(b'summary')
7120 ctx = repo[None]
7119 ctx = repo[None]
7121 parents = ctx.parents()
7120 parents = ctx.parents()
7122 pnode = parents[0].node()
7121 pnode = parents[0].node()
7123 marks = []
7122 marks = []
7124
7123
7125 try:
7124 try:
7126 ms = mergestatemod.mergestate.read(repo)
7125 ms = mergestatemod.mergestate.read(repo)
7127 except error.UnsupportedMergeRecords as e:
7126 except error.UnsupportedMergeRecords as e:
7128 s = b' '.join(e.recordtypes)
7127 s = b' '.join(e.recordtypes)
7129 ui.warn(
7128 ui.warn(
7130 _(b'warning: merge state has unsupported record types: %s\n') % s
7129 _(b'warning: merge state has unsupported record types: %s\n') % s
7131 )
7130 )
7132 unresolved = []
7131 unresolved = []
7133 else:
7132 else:
7134 unresolved = list(ms.unresolved())
7133 unresolved = list(ms.unresolved())
7135
7134
7136 for p in parents:
7135 for p in parents:
7137 # label with log.changeset (instead of log.parent) since this
7136 # label with log.changeset (instead of log.parent) since this
7138 # shows a working directory parent *changeset*:
7137 # shows a working directory parent *changeset*:
7139 # i18n: column positioning for "hg summary"
7138 # i18n: column positioning for "hg summary"
7140 ui.write(
7139 ui.write(
7141 _(b'parent: %d:%s ') % (p.rev(), p),
7140 _(b'parent: %d:%s ') % (p.rev(), p),
7142 label=logcmdutil.changesetlabels(p),
7141 label=logcmdutil.changesetlabels(p),
7143 )
7142 )
7144 ui.write(b' '.join(p.tags()), label=b'log.tag')
7143 ui.write(b' '.join(p.tags()), label=b'log.tag')
7145 if p.bookmarks():
7144 if p.bookmarks():
7146 marks.extend(p.bookmarks())
7145 marks.extend(p.bookmarks())
7147 if p.rev() == -1:
7146 if p.rev() == -1:
7148 if not len(repo):
7147 if not len(repo):
7149 ui.write(_(b' (empty repository)'))
7148 ui.write(_(b' (empty repository)'))
7150 else:
7149 else:
7151 ui.write(_(b' (no revision checked out)'))
7150 ui.write(_(b' (no revision checked out)'))
7152 if p.obsolete():
7151 if p.obsolete():
7153 ui.write(_(b' (obsolete)'))
7152 ui.write(_(b' (obsolete)'))
7154 if p.isunstable():
7153 if p.isunstable():
7155 instabilities = (
7154 instabilities = (
7156 ui.label(instability, b'trouble.%s' % instability)
7155 ui.label(instability, b'trouble.%s' % instability)
7157 for instability in p.instabilities()
7156 for instability in p.instabilities()
7158 )
7157 )
7159 ui.write(b' (' + b', '.join(instabilities) + b')')
7158 ui.write(b' (' + b', '.join(instabilities) + b')')
7160 ui.write(b'\n')
7159 ui.write(b'\n')
7161 if p.description():
7160 if p.description():
7162 ui.status(
7161 ui.status(
7163 b' ' + p.description().splitlines()[0].strip() + b'\n',
7162 b' ' + p.description().splitlines()[0].strip() + b'\n',
7164 label=b'log.summary',
7163 label=b'log.summary',
7165 )
7164 )
7166
7165
7167 branch = ctx.branch()
7166 branch = ctx.branch()
7168 bheads = repo.branchheads(branch)
7167 bheads = repo.branchheads(branch)
7169 # i18n: column positioning for "hg summary"
7168 # i18n: column positioning for "hg summary"
7170 m = _(b'branch: %s\n') % branch
7169 m = _(b'branch: %s\n') % branch
7171 if branch != b'default':
7170 if branch != b'default':
7172 ui.write(m, label=b'log.branch')
7171 ui.write(m, label=b'log.branch')
7173 else:
7172 else:
7174 ui.status(m, label=b'log.branch')
7173 ui.status(m, label=b'log.branch')
7175
7174
7176 if marks:
7175 if marks:
7177 active = repo._activebookmark
7176 active = repo._activebookmark
7178 # i18n: column positioning for "hg summary"
7177 # i18n: column positioning for "hg summary"
7179 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7178 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7180 if active is not None:
7179 if active is not None:
7181 if active in marks:
7180 if active in marks:
7182 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7181 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7183 marks.remove(active)
7182 marks.remove(active)
7184 else:
7183 else:
7185 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7184 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7186 for m in marks:
7185 for m in marks:
7187 ui.write(b' ' + m, label=b'log.bookmark')
7186 ui.write(b' ' + m, label=b'log.bookmark')
7188 ui.write(b'\n', label=b'log.bookmark')
7187 ui.write(b'\n', label=b'log.bookmark')
7189
7188
7190 status = repo.status(unknown=True)
7189 status = repo.status(unknown=True)
7191
7190
7192 c = repo.dirstate.copies()
7191 c = repo.dirstate.copies()
7193 copied, renamed = [], []
7192 copied, renamed = [], []
7194 for d, s in c.items():
7193 for d, s in c.items():
7195 if s in status.removed:
7194 if s in status.removed:
7196 status.removed.remove(s)
7195 status.removed.remove(s)
7197 renamed.append(d)
7196 renamed.append(d)
7198 else:
7197 else:
7199 copied.append(d)
7198 copied.append(d)
7200 if d in status.added:
7199 if d in status.added:
7201 status.added.remove(d)
7200 status.added.remove(d)
7202
7201
7203 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7202 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7204
7203
7205 labels = [
7204 labels = [
7206 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7205 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7207 (ui.label(_(b'%d added'), b'status.added'), status.added),
7206 (ui.label(_(b'%d added'), b'status.added'), status.added),
7208 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7207 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7209 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7208 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7210 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7209 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7211 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7210 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7212 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7211 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7213 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7212 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7214 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7213 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7215 ]
7214 ]
7216 t = []
7215 t = []
7217 for l, s in labels:
7216 for l, s in labels:
7218 if s:
7217 if s:
7219 t.append(l % len(s))
7218 t.append(l % len(s))
7220
7219
7221 t = b', '.join(t)
7220 t = b', '.join(t)
7222 cleanworkdir = False
7221 cleanworkdir = False
7223
7222
7224 if repo.vfs.exists(b'graftstate'):
7223 if repo.vfs.exists(b'graftstate'):
7225 t += _(b' (graft in progress)')
7224 t += _(b' (graft in progress)')
7226 if repo.vfs.exists(b'updatestate'):
7225 if repo.vfs.exists(b'updatestate'):
7227 t += _(b' (interrupted update)')
7226 t += _(b' (interrupted update)')
7228 elif len(parents) > 1:
7227 elif len(parents) > 1:
7229 t += _(b' (merge)')
7228 t += _(b' (merge)')
7230 elif branch != parents[0].branch():
7229 elif branch != parents[0].branch():
7231 t += _(b' (new branch)')
7230 t += _(b' (new branch)')
7232 elif parents[0].closesbranch() and pnode in repo.branchheads(
7231 elif parents[0].closesbranch() and pnode in repo.branchheads(
7233 branch, closed=True
7232 branch, closed=True
7234 ):
7233 ):
7235 t += _(b' (head closed)')
7234 t += _(b' (head closed)')
7236 elif not (
7235 elif not (
7237 status.modified
7236 status.modified
7238 or status.added
7237 or status.added
7239 or status.removed
7238 or status.removed
7240 or renamed
7239 or renamed
7241 or copied
7240 or copied
7242 or subs
7241 or subs
7243 ):
7242 ):
7244 t += _(b' (clean)')
7243 t += _(b' (clean)')
7245 cleanworkdir = True
7244 cleanworkdir = True
7246 elif pnode not in bheads:
7245 elif pnode not in bheads:
7247 t += _(b' (new branch head)')
7246 t += _(b' (new branch head)')
7248
7247
7249 if parents:
7248 if parents:
7250 pendingphase = max(p.phase() for p in parents)
7249 pendingphase = max(p.phase() for p in parents)
7251 else:
7250 else:
7252 pendingphase = phases.public
7251 pendingphase = phases.public
7253
7252
7254 if pendingphase > phases.newcommitphase(ui):
7253 if pendingphase > phases.newcommitphase(ui):
7255 t += b' (%s)' % phases.phasenames[pendingphase]
7254 t += b' (%s)' % phases.phasenames[pendingphase]
7256
7255
7257 if cleanworkdir:
7256 if cleanworkdir:
7258 # i18n: column positioning for "hg summary"
7257 # i18n: column positioning for "hg summary"
7259 ui.status(_(b'commit: %s\n') % t.strip())
7258 ui.status(_(b'commit: %s\n') % t.strip())
7260 else:
7259 else:
7261 # i18n: column positioning for "hg summary"
7260 # i18n: column positioning for "hg summary"
7262 ui.write(_(b'commit: %s\n') % t.strip())
7261 ui.write(_(b'commit: %s\n') % t.strip())
7263
7262
7264 # all ancestors of branch heads - all ancestors of parent = new csets
7263 # all ancestors of branch heads - all ancestors of parent = new csets
7265 new = len(
7264 new = len(
7266 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7265 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7267 )
7266 )
7268
7267
7269 if new == 0:
7268 if new == 0:
7270 # i18n: column positioning for "hg summary"
7269 # i18n: column positioning for "hg summary"
7271 ui.status(_(b'update: (current)\n'))
7270 ui.status(_(b'update: (current)\n'))
7272 elif pnode not in bheads:
7271 elif pnode not in bheads:
7273 # i18n: column positioning for "hg summary"
7272 # i18n: column positioning for "hg summary"
7274 ui.write(_(b'update: %d new changesets (update)\n') % new)
7273 ui.write(_(b'update: %d new changesets (update)\n') % new)
7275 else:
7274 else:
7276 # i18n: column positioning for "hg summary"
7275 # i18n: column positioning for "hg summary"
7277 ui.write(
7276 ui.write(
7278 _(b'update: %d new changesets, %d branch heads (merge)\n')
7277 _(b'update: %d new changesets, %d branch heads (merge)\n')
7279 % (new, len(bheads))
7278 % (new, len(bheads))
7280 )
7279 )
7281
7280
7282 t = []
7281 t = []
7283 draft = len(repo.revs(b'draft()'))
7282 draft = len(repo.revs(b'draft()'))
7284 if draft:
7283 if draft:
7285 t.append(_(b'%d draft') % draft)
7284 t.append(_(b'%d draft') % draft)
7286 secret = len(repo.revs(b'secret()'))
7285 secret = len(repo.revs(b'secret()'))
7287 if secret:
7286 if secret:
7288 t.append(_(b'%d secret') % secret)
7287 t.append(_(b'%d secret') % secret)
7289
7288
7290 if draft or secret:
7289 if draft or secret:
7291 ui.status(_(b'phases: %s\n') % b', '.join(t))
7290 ui.status(_(b'phases: %s\n') % b', '.join(t))
7292
7291
7293 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7292 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7294 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7293 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7295 numtrouble = len(repo.revs(trouble + b"()"))
7294 numtrouble = len(repo.revs(trouble + b"()"))
7296 # We write all the possibilities to ease translation
7295 # We write all the possibilities to ease translation
7297 troublemsg = {
7296 troublemsg = {
7298 b"orphan": _(b"orphan: %d changesets"),
7297 b"orphan": _(b"orphan: %d changesets"),
7299 b"contentdivergent": _(b"content-divergent: %d changesets"),
7298 b"contentdivergent": _(b"content-divergent: %d changesets"),
7300 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7299 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7301 }
7300 }
7302 if numtrouble > 0:
7301 if numtrouble > 0:
7303 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7302 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7304
7303
7305 cmdutil.summaryhooks(ui, repo)
7304 cmdutil.summaryhooks(ui, repo)
7306
7305
7307 if opts.get(b'remote'):
7306 if opts.get(b'remote'):
7308 needsincoming, needsoutgoing = True, True
7307 needsincoming, needsoutgoing = True, True
7309 else:
7308 else:
7310 needsincoming, needsoutgoing = False, False
7309 needsincoming, needsoutgoing = False, False
7311 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7310 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7312 if i:
7311 if i:
7313 needsincoming = True
7312 needsincoming = True
7314 if o:
7313 if o:
7315 needsoutgoing = True
7314 needsoutgoing = True
7316 if not needsincoming and not needsoutgoing:
7315 if not needsincoming and not needsoutgoing:
7317 return
7316 return
7318
7317
7319 def getincoming():
7318 def getincoming():
7320 # XXX We should actually skip this if no default is specified, instead
7319 # XXX We should actually skip this if no default is specified, instead
7321 # of passing "default" which will resolve as "./default/" if no default
7320 # of passing "default" which will resolve as "./default/" if no default
7322 # path is defined.
7321 # path is defined.
7323 path = urlutil.get_unique_pull_path_obj(b'summary', ui, b'default')
7322 path = urlutil.get_unique_pull_path_obj(b'summary', ui, b'default')
7324 sbranch = path.branch
7323 sbranch = path.branch
7325 try:
7324 try:
7326 other = hg.peer(repo, {}, path)
7325 other = hg.peer(repo, {}, path)
7327 except error.RepoError:
7326 except error.RepoError:
7328 if opts.get(b'remote'):
7327 if opts.get(b'remote'):
7329 raise
7328 raise
7330 return path.loc, sbranch, None, None, None
7329 return path.loc, sbranch, None, None, None
7331 branches = (path.branch, [])
7330 branches = (path.branch, [])
7332 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7331 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7333 if revs:
7332 if revs:
7334 revs = [other.lookup(rev) for rev in revs]
7333 revs = [other.lookup(rev) for rev in revs]
7335 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(path.loc))
7334 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(path.loc))
7336 with repo.ui.silent():
7335 with repo.ui.silent():
7337 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7336 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7338 return path.loc, sbranch, other, commoninc, commoninc[1]
7337 return path.loc, sbranch, other, commoninc, commoninc[1]
7339
7338
7340 if needsincoming:
7339 if needsincoming:
7341 source, sbranch, sother, commoninc, incoming = getincoming()
7340 source, sbranch, sother, commoninc, incoming = getincoming()
7342 else:
7341 else:
7343 source = sbranch = sother = commoninc = incoming = None
7342 source = sbranch = sother = commoninc = incoming = None
7344
7343
7345 def getoutgoing():
7344 def getoutgoing():
7346 # XXX We should actually skip this if no default is specified, instead
7345 # XXX We should actually skip this if no default is specified, instead
7347 # of passing "default" which will resolve as "./default/" if no default
7346 # of passing "default" which will resolve as "./default/" if no default
7348 # path is defined.
7347 # path is defined.
7349 d = None
7348 d = None
7350 if b'default-push' in ui.paths:
7349 if b'default-push' in ui.paths:
7351 d = b'default-push'
7350 d = b'default-push'
7352 elif b'default' in ui.paths:
7351 elif b'default' in ui.paths:
7353 d = b'default'
7352 d = b'default'
7354 path = None
7353 path = None
7355 if d is not None:
7354 if d is not None:
7356 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7355 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7357 dest = path.loc
7356 dest = path.loc
7358 dbranch = path.branch
7357 dbranch = path.branch
7359 else:
7358 else:
7360 dest = b'default'
7359 dest = b'default'
7361 dbranch = None
7360 dbranch = None
7362 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7361 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7363 if source != dest:
7362 if source != dest:
7364 try:
7363 try:
7365 dother = hg.peer(repo, {}, path if path is not None else dest)
7364 dother = hg.peer(repo, {}, path if path is not None else dest)
7366 except error.RepoError:
7365 except error.RepoError:
7367 if opts.get(b'remote'):
7366 if opts.get(b'remote'):
7368 raise
7367 raise
7369 return dest, dbranch, None, None
7368 return dest, dbranch, None, None
7370 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7369 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7371 elif sother is None:
7370 elif sother is None:
7372 # there is no explicit destination peer, but source one is invalid
7371 # there is no explicit destination peer, but source one is invalid
7373 return dest, dbranch, None, None
7372 return dest, dbranch, None, None
7374 else:
7373 else:
7375 dother = sother
7374 dother = sother
7376 if source != dest or (sbranch is not None and sbranch != dbranch):
7375 if source != dest or (sbranch is not None and sbranch != dbranch):
7377 common = None
7376 common = None
7378 else:
7377 else:
7379 common = commoninc
7378 common = commoninc
7380 if revs:
7379 if revs:
7381 revs = [repo.lookup(rev) for rev in revs]
7380 revs = [repo.lookup(rev) for rev in revs]
7382 with repo.ui.silent():
7381 with repo.ui.silent():
7383 outgoing = discovery.findcommonoutgoing(
7382 outgoing = discovery.findcommonoutgoing(
7384 repo, dother, onlyheads=revs, commoninc=common
7383 repo, dother, onlyheads=revs, commoninc=common
7385 )
7384 )
7386 return dest, dbranch, dother, outgoing
7385 return dest, dbranch, dother, outgoing
7387
7386
7388 if needsoutgoing:
7387 if needsoutgoing:
7389 dest, dbranch, dother, outgoing = getoutgoing()
7388 dest, dbranch, dother, outgoing = getoutgoing()
7390 else:
7389 else:
7391 dest = dbranch = dother = outgoing = None
7390 dest = dbranch = dother = outgoing = None
7392
7391
7393 if opts.get(b'remote'):
7392 if opts.get(b'remote'):
7394 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7393 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7395 # The former always sets `sother` (or raises an exception if it can't);
7394 # The former always sets `sother` (or raises an exception if it can't);
7396 # the latter always sets `outgoing`.
7395 # the latter always sets `outgoing`.
7397 assert sother is not None
7396 assert sother is not None
7398 assert outgoing is not None
7397 assert outgoing is not None
7399
7398
7400 t = []
7399 t = []
7401 if incoming:
7400 if incoming:
7402 t.append(_(b'1 or more incoming'))
7401 t.append(_(b'1 or more incoming'))
7403 o = outgoing.missing
7402 o = outgoing.missing
7404 if o:
7403 if o:
7405 t.append(_(b'%d outgoing') % len(o))
7404 t.append(_(b'%d outgoing') % len(o))
7406 other = dother or sother
7405 other = dother or sother
7407 if b'bookmarks' in other.listkeys(b'namespaces'):
7406 if b'bookmarks' in other.listkeys(b'namespaces'):
7408 counts = bookmarks.summary(repo, other)
7407 counts = bookmarks.summary(repo, other)
7409 if counts[0] > 0:
7408 if counts[0] > 0:
7410 t.append(_(b'%d incoming bookmarks') % counts[0])
7409 t.append(_(b'%d incoming bookmarks') % counts[0])
7411 if counts[1] > 0:
7410 if counts[1] > 0:
7412 t.append(_(b'%d outgoing bookmarks') % counts[1])
7411 t.append(_(b'%d outgoing bookmarks') % counts[1])
7413
7412
7414 if t:
7413 if t:
7415 # i18n: column positioning for "hg summary"
7414 # i18n: column positioning for "hg summary"
7416 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7415 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7417 else:
7416 else:
7418 # i18n: column positioning for "hg summary"
7417 # i18n: column positioning for "hg summary"
7419 ui.status(_(b'remote: (synced)\n'))
7418 ui.status(_(b'remote: (synced)\n'))
7420
7419
7421 cmdutil.summaryremotehooks(
7420 cmdutil.summaryremotehooks(
7422 ui,
7421 ui,
7423 repo,
7422 repo,
7424 opts,
7423 opts,
7425 (
7424 (
7426 (source, sbranch, sother, commoninc),
7425 (source, sbranch, sother, commoninc),
7427 (dest, dbranch, dother, outgoing),
7426 (dest, dbranch, dother, outgoing),
7428 ),
7427 ),
7429 )
7428 )
7430
7429
7431
7430
7432 @command(
7431 @command(
7433 b'tag',
7432 b'tag',
7434 [
7433 [
7435 (b'f', b'force', None, _(b'force tag')),
7434 (b'f', b'force', None, _(b'force tag')),
7436 (b'l', b'local', None, _(b'make the tag local')),
7435 (b'l', b'local', None, _(b'make the tag local')),
7437 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7436 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7438 (b'', b'remove', None, _(b'remove a tag')),
7437 (b'', b'remove', None, _(b'remove a tag')),
7439 # -l/--local is already there, commitopts cannot be used
7438 # -l/--local is already there, commitopts cannot be used
7440 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7439 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7441 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7440 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7442 ]
7441 ]
7443 + commitopts2,
7442 + commitopts2,
7444 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7443 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7445 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7444 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7446 )
7445 )
7447 def tag(ui, repo, name1, *names, **opts):
7446 def tag(ui, repo, name1, *names, **opts):
7448 """add one or more tags for the current or given revision
7447 """add one or more tags for the current or given revision
7449
7448
7450 Name a particular revision using <name>.
7449 Name a particular revision using <name>.
7451
7450
7452 Tags are used to name particular revisions of the repository and are
7451 Tags are used to name particular revisions of the repository and are
7453 very useful to compare different revisions, to go back to significant
7452 very useful to compare different revisions, to go back to significant
7454 earlier versions or to mark branch points as releases, etc. Changing
7453 earlier versions or to mark branch points as releases, etc. Changing
7455 an existing tag is normally disallowed; use -f/--force to override.
7454 an existing tag is normally disallowed; use -f/--force to override.
7456
7455
7457 If no revision is given, the parent of the working directory is
7456 If no revision is given, the parent of the working directory is
7458 used.
7457 used.
7459
7458
7460 To facilitate version control, distribution, and merging of tags,
7459 To facilitate version control, distribution, and merging of tags,
7461 they are stored as a file named ".hgtags" which is managed similarly
7460 they are stored as a file named ".hgtags" which is managed similarly
7462 to other project files and can be hand-edited if necessary. This
7461 to other project files and can be hand-edited if necessary. This
7463 also means that tagging creates a new commit. The file
7462 also means that tagging creates a new commit. The file
7464 ".hg/localtags" is used for local tags (not shared among
7463 ".hg/localtags" is used for local tags (not shared among
7465 repositories).
7464 repositories).
7466
7465
7467 Tag commits are usually made at the head of a branch. If the parent
7466 Tag commits are usually made at the head of a branch. If the parent
7468 of the working directory is not a branch head, :hg:`tag` aborts; use
7467 of the working directory is not a branch head, :hg:`tag` aborts; use
7469 -f/--force to force the tag commit to be based on a non-head
7468 -f/--force to force the tag commit to be based on a non-head
7470 changeset.
7469 changeset.
7471
7470
7472 See :hg:`help dates` for a list of formats valid for -d/--date.
7471 See :hg:`help dates` for a list of formats valid for -d/--date.
7473
7472
7474 Since tag names have priority over branch names during revision
7473 Since tag names have priority over branch names during revision
7475 lookup, using an existing branch name as a tag name is discouraged.
7474 lookup, using an existing branch name as a tag name is discouraged.
7476
7475
7477 Returns 0 on success.
7476 Returns 0 on success.
7478 """
7477 """
7479 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7478 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7480 opts = pycompat.byteskwargs(opts)
7479 opts = pycompat.byteskwargs(opts)
7481 with repo.wlock(), repo.lock():
7480 with repo.wlock(), repo.lock():
7482 rev_ = b"."
7481 rev_ = b"."
7483 names = [t.strip() for t in (name1,) + names]
7482 names = [t.strip() for t in (name1,) + names]
7484 if len(names) != len(set(names)):
7483 if len(names) != len(set(names)):
7485 raise error.InputError(_(b'tag names must be unique'))
7484 raise error.InputError(_(b'tag names must be unique'))
7486 for n in names:
7485 for n in names:
7487 scmutil.checknewlabel(repo, n, b'tag')
7486 scmutil.checknewlabel(repo, n, b'tag')
7488 if not n:
7487 if not n:
7489 raise error.InputError(
7488 raise error.InputError(
7490 _(b'tag names cannot consist entirely of whitespace')
7489 _(b'tag names cannot consist entirely of whitespace')
7491 )
7490 )
7492 if opts.get(b'rev'):
7491 if opts.get(b'rev'):
7493 rev_ = opts[b'rev']
7492 rev_ = opts[b'rev']
7494 message = opts.get(b'message')
7493 message = opts.get(b'message')
7495 if opts.get(b'remove'):
7494 if opts.get(b'remove'):
7496 if opts.get(b'local'):
7495 if opts.get(b'local'):
7497 expectedtype = b'local'
7496 expectedtype = b'local'
7498 else:
7497 else:
7499 expectedtype = b'global'
7498 expectedtype = b'global'
7500
7499
7501 for n in names:
7500 for n in names:
7502 if repo.tagtype(n) == b'global':
7501 if repo.tagtype(n) == b'global':
7503 alltags = tagsmod.findglobaltags(ui, repo)
7502 alltags = tagsmod.findglobaltags(ui, repo)
7504 if alltags[n][0] == repo.nullid:
7503 if alltags[n][0] == repo.nullid:
7505 raise error.InputError(
7504 raise error.InputError(
7506 _(b"tag '%s' is already removed") % n
7505 _(b"tag '%s' is already removed") % n
7507 )
7506 )
7508 if not repo.tagtype(n):
7507 if not repo.tagtype(n):
7509 raise error.InputError(_(b"tag '%s' does not exist") % n)
7508 raise error.InputError(_(b"tag '%s' does not exist") % n)
7510 if repo.tagtype(n) != expectedtype:
7509 if repo.tagtype(n) != expectedtype:
7511 if expectedtype == b'global':
7510 if expectedtype == b'global':
7512 raise error.InputError(
7511 raise error.InputError(
7513 _(b"tag '%s' is not a global tag") % n
7512 _(b"tag '%s' is not a global tag") % n
7514 )
7513 )
7515 else:
7514 else:
7516 raise error.InputError(
7515 raise error.InputError(
7517 _(b"tag '%s' is not a local tag") % n
7516 _(b"tag '%s' is not a local tag") % n
7518 )
7517 )
7519 rev_ = b'null'
7518 rev_ = b'null'
7520 if not message:
7519 if not message:
7521 # we don't translate commit messages
7520 # we don't translate commit messages
7522 message = b'Removed tag %s' % b', '.join(names)
7521 message = b'Removed tag %s' % b', '.join(names)
7523 elif not opts.get(b'force'):
7522 elif not opts.get(b'force'):
7524 for n in names:
7523 for n in names:
7525 if n in repo.tags():
7524 if n in repo.tags():
7526 raise error.InputError(
7525 raise error.InputError(
7527 _(b"tag '%s' already exists (use -f to force)") % n
7526 _(b"tag '%s' already exists (use -f to force)") % n
7528 )
7527 )
7529 if not opts.get(b'local'):
7528 if not opts.get(b'local'):
7530 p1, p2 = repo.dirstate.parents()
7529 p1, p2 = repo.dirstate.parents()
7531 if p2 != repo.nullid:
7530 if p2 != repo.nullid:
7532 raise error.StateError(_(b'uncommitted merge'))
7531 raise error.StateError(_(b'uncommitted merge'))
7533 bheads = repo.branchheads()
7532 bheads = repo.branchheads()
7534 if not opts.get(b'force') and bheads and p1 not in bheads:
7533 if not opts.get(b'force') and bheads and p1 not in bheads:
7535 raise error.InputError(
7534 raise error.InputError(
7536 _(
7535 _(
7537 b'working directory is not at a branch head '
7536 b'working directory is not at a branch head '
7538 b'(use -f to force)'
7537 b'(use -f to force)'
7539 )
7538 )
7540 )
7539 )
7541 node = logcmdutil.revsingle(repo, rev_).node()
7540 node = logcmdutil.revsingle(repo, rev_).node()
7542
7541
7543 # don't allow tagging the null rev or the working directory
7542 # don't allow tagging the null rev or the working directory
7544 if node is None:
7543 if node is None:
7545 raise error.InputError(_(b"cannot tag working directory"))
7544 raise error.InputError(_(b"cannot tag working directory"))
7546 elif not opts.get(b'remove') and node == nullid:
7545 elif not opts.get(b'remove') and node == nullid:
7547 raise error.InputError(_(b"cannot tag null revision"))
7546 raise error.InputError(_(b"cannot tag null revision"))
7548
7547
7549 if not message:
7548 if not message:
7550 # we don't translate commit messages
7549 # we don't translate commit messages
7551 message = b'Added tag %s for changeset %s' % (
7550 message = b'Added tag %s for changeset %s' % (
7552 b', '.join(names),
7551 b', '.join(names),
7553 short(node),
7552 short(node),
7554 )
7553 )
7555
7554
7556 date = opts.get(b'date')
7555 date = opts.get(b'date')
7557 if date:
7556 if date:
7558 date = dateutil.parsedate(date)
7557 date = dateutil.parsedate(date)
7559
7558
7560 if opts.get(b'remove'):
7559 if opts.get(b'remove'):
7561 editform = b'tag.remove'
7560 editform = b'tag.remove'
7562 else:
7561 else:
7563 editform = b'tag.add'
7562 editform = b'tag.add'
7564 editor = cmdutil.getcommiteditor(
7563 editor = cmdutil.getcommiteditor(
7565 editform=editform, **pycompat.strkwargs(opts)
7564 editform=editform, **pycompat.strkwargs(opts)
7566 )
7565 )
7567
7566
7568 tagsmod.tag(
7567 tagsmod.tag(
7569 repo,
7568 repo,
7570 names,
7569 names,
7571 node,
7570 node,
7572 message,
7571 message,
7573 opts.get(b'local'),
7572 opts.get(b'local'),
7574 opts.get(b'user'),
7573 opts.get(b'user'),
7575 date,
7574 date,
7576 editor=editor,
7575 editor=editor,
7577 )
7576 )
7578
7577
7579
7578
7580 @command(
7579 @command(
7581 b'tags',
7580 b'tags',
7582 formatteropts,
7581 formatteropts,
7583 b'',
7582 b'',
7584 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7583 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7585 intents={INTENT_READONLY},
7584 intents={INTENT_READONLY},
7586 )
7585 )
7587 def tags(ui, repo, **opts):
7586 def tags(ui, repo, **opts):
7588 """list repository tags
7587 """list repository tags
7589
7588
7590 This lists both regular and local tags. When the -v/--verbose
7589 This lists both regular and local tags. When the -v/--verbose
7591 switch is used, a third column "local" is printed for local tags.
7590 switch is used, a third column "local" is printed for local tags.
7592 When the -q/--quiet switch is used, only the tag name is printed.
7591 When the -q/--quiet switch is used, only the tag name is printed.
7593
7592
7594 .. container:: verbose
7593 .. container:: verbose
7595
7594
7596 Template:
7595 Template:
7597
7596
7598 The following keywords are supported in addition to the common template
7597 The following keywords are supported in addition to the common template
7599 keywords and functions such as ``{tag}``. See also
7598 keywords and functions such as ``{tag}``. See also
7600 :hg:`help templates`.
7599 :hg:`help templates`.
7601
7600
7602 :type: String. ``local`` for local tags.
7601 :type: String. ``local`` for local tags.
7603
7602
7604 Returns 0 on success.
7603 Returns 0 on success.
7605 """
7604 """
7606
7605
7607 opts = pycompat.byteskwargs(opts)
7606 opts = pycompat.byteskwargs(opts)
7608 ui.pager(b'tags')
7607 ui.pager(b'tags')
7609 fm = ui.formatter(b'tags', opts)
7608 fm = ui.formatter(b'tags', opts)
7610 hexfunc = fm.hexfunc
7609 hexfunc = fm.hexfunc
7611
7610
7612 for t, n in reversed(repo.tagslist()):
7611 for t, n in reversed(repo.tagslist()):
7613 hn = hexfunc(n)
7612 hn = hexfunc(n)
7614 label = b'tags.normal'
7613 label = b'tags.normal'
7615 tagtype = repo.tagtype(t)
7614 tagtype = repo.tagtype(t)
7616 if not tagtype or tagtype == b'global':
7615 if not tagtype or tagtype == b'global':
7617 tagtype = b''
7616 tagtype = b''
7618 else:
7617 else:
7619 label = b'tags.' + tagtype
7618 label = b'tags.' + tagtype
7620
7619
7621 fm.startitem()
7620 fm.startitem()
7622 fm.context(repo=repo)
7621 fm.context(repo=repo)
7623 fm.write(b'tag', b'%s', t, label=label)
7622 fm.write(b'tag', b'%s', t, label=label)
7624 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7623 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7625 fm.condwrite(
7624 fm.condwrite(
7626 not ui.quiet,
7625 not ui.quiet,
7627 b'rev node',
7626 b'rev node',
7628 fmt,
7627 fmt,
7629 repo.changelog.rev(n),
7628 repo.changelog.rev(n),
7630 hn,
7629 hn,
7631 label=label,
7630 label=label,
7632 )
7631 )
7633 fm.condwrite(
7632 fm.condwrite(
7634 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7633 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7635 )
7634 )
7636 fm.plain(b'\n')
7635 fm.plain(b'\n')
7637 fm.end()
7636 fm.end()
7638
7637
7639
7638
7640 @command(
7639 @command(
7641 b'tip',
7640 b'tip',
7642 [
7641 [
7643 (b'p', b'patch', None, _(b'show patch')),
7642 (b'p', b'patch', None, _(b'show patch')),
7644 (b'g', b'git', None, _(b'use git extended diff format')),
7643 (b'g', b'git', None, _(b'use git extended diff format')),
7645 ]
7644 ]
7646 + templateopts,
7645 + templateopts,
7647 _(b'[-p] [-g]'),
7646 _(b'[-p] [-g]'),
7648 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7647 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7649 )
7648 )
7650 def tip(ui, repo, **opts):
7649 def tip(ui, repo, **opts):
7651 """show the tip revision (DEPRECATED)
7650 """show the tip revision (DEPRECATED)
7652
7651
7653 The tip revision (usually just called the tip) is the changeset
7652 The tip revision (usually just called the tip) is the changeset
7654 most recently added to the repository (and therefore the most
7653 most recently added to the repository (and therefore the most
7655 recently changed head).
7654 recently changed head).
7656
7655
7657 If you have just made a commit, that commit will be the tip. If
7656 If you have just made a commit, that commit will be the tip. If
7658 you have just pulled changes from another repository, the tip of
7657 you have just pulled changes from another repository, the tip of
7659 that repository becomes the current tip. The "tip" tag is special
7658 that repository becomes the current tip. The "tip" tag is special
7660 and cannot be renamed or assigned to a different changeset.
7659 and cannot be renamed or assigned to a different changeset.
7661
7660
7662 This command is deprecated, please use :hg:`heads` instead.
7661 This command is deprecated, please use :hg:`heads` instead.
7663
7662
7664 Returns 0 on success.
7663 Returns 0 on success.
7665 """
7664 """
7666 opts = pycompat.byteskwargs(opts)
7665 opts = pycompat.byteskwargs(opts)
7667 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7666 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7668 displayer.show(repo[b'tip'])
7667 displayer.show(repo[b'tip'])
7669 displayer.close()
7668 displayer.close()
7670
7669
7671
7670
7672 @command(
7671 @command(
7673 b'unbundle',
7672 b'unbundle',
7674 [
7673 [
7675 (
7674 (
7676 b'u',
7675 b'u',
7677 b'update',
7676 b'update',
7678 None,
7677 None,
7679 _(b'update to new branch head if changesets were unbundled'),
7678 _(b'update to new branch head if changesets were unbundled'),
7680 )
7679 )
7681 ],
7680 ],
7682 _(b'[-u] FILE...'),
7681 _(b'[-u] FILE...'),
7683 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7682 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7684 )
7683 )
7685 def unbundle(ui, repo, fname1, *fnames, **opts):
7684 def unbundle(ui, repo, fname1, *fnames, **opts):
7686 """apply one or more bundle files
7685 """apply one or more bundle files
7687
7686
7688 Apply one or more bundle files generated by :hg:`bundle`.
7687 Apply one or more bundle files generated by :hg:`bundle`.
7689
7688
7690 Returns 0 on success, 1 if an update has unresolved files.
7689 Returns 0 on success, 1 if an update has unresolved files.
7691 """
7690 """
7692 fnames = (fname1,) + fnames
7691 fnames = (fname1,) + fnames
7693
7692
7694 with repo.lock():
7693 with repo.lock():
7695 for fname in fnames:
7694 for fname in fnames:
7696 f = hg.openpath(ui, fname)
7695 f = hg.openpath(ui, fname)
7697 gen = exchange.readbundle(ui, f, fname)
7696 gen = exchange.readbundle(ui, f, fname)
7698 if isinstance(gen, streamclone.streamcloneapplier):
7697 if isinstance(gen, streamclone.streamcloneapplier):
7699 raise error.InputError(
7698 raise error.InputError(
7700 _(
7699 _(
7701 b'packed bundles cannot be applied with '
7700 b'packed bundles cannot be applied with '
7702 b'"hg unbundle"'
7701 b'"hg unbundle"'
7703 ),
7702 ),
7704 hint=_(b'use "hg debugapplystreamclonebundle"'),
7703 hint=_(b'use "hg debugapplystreamclonebundle"'),
7705 )
7704 )
7706 url = b'bundle:' + fname
7705 url = b'bundle:' + fname
7707 try:
7706 try:
7708 txnname = b'unbundle'
7707 txnname = b'unbundle'
7709 if not isinstance(gen, bundle2.unbundle20):
7708 if not isinstance(gen, bundle2.unbundle20):
7710 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7709 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7711 with repo.transaction(txnname) as tr:
7710 with repo.transaction(txnname) as tr:
7712 op = bundle2.applybundle(
7711 op = bundle2.applybundle(
7713 repo, gen, tr, source=b'unbundle', url=url
7712 repo, gen, tr, source=b'unbundle', url=url
7714 )
7713 )
7715 except error.BundleUnknownFeatureError as exc:
7714 except error.BundleUnknownFeatureError as exc:
7716 raise error.Abort(
7715 raise error.Abort(
7717 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7716 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7718 hint=_(
7717 hint=_(
7719 b"see https://mercurial-scm.org/"
7718 b"see https://mercurial-scm.org/"
7720 b"wiki/BundleFeature for more "
7719 b"wiki/BundleFeature for more "
7721 b"information"
7720 b"information"
7722 ),
7721 ),
7723 )
7722 )
7724 modheads = bundle2.combinechangegroupresults(op)
7723 modheads = bundle2.combinechangegroupresults(op)
7725
7724
7726 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7725 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7727 return 1
7726 return 1
7728 else:
7727 else:
7729 return 0
7728 return 0
7730
7729
7731
7730
7732 @command(
7731 @command(
7733 b'unshelve',
7732 b'unshelve',
7734 [
7733 [
7735 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7734 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7736 (
7735 (
7737 b'c',
7736 b'c',
7738 b'continue',
7737 b'continue',
7739 None,
7738 None,
7740 _(b'continue an incomplete unshelve operation'),
7739 _(b'continue an incomplete unshelve operation'),
7741 ),
7740 ),
7742 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7741 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7743 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7742 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7744 (
7743 (
7745 b'n',
7744 b'n',
7746 b'name',
7745 b'name',
7747 b'',
7746 b'',
7748 _(b'restore shelved change with given name'),
7747 _(b'restore shelved change with given name'),
7749 _(b'NAME'),
7748 _(b'NAME'),
7750 ),
7749 ),
7751 (b't', b'tool', b'', _(b'specify merge tool')),
7750 (b't', b'tool', b'', _(b'specify merge tool')),
7752 (
7751 (
7753 b'',
7752 b'',
7754 b'date',
7753 b'date',
7755 b'',
7754 b'',
7756 _(b'set date for temporary commits (DEPRECATED)'),
7755 _(b'set date for temporary commits (DEPRECATED)'),
7757 _(b'DATE'),
7756 _(b'DATE'),
7758 ),
7757 ),
7759 ],
7758 ],
7760 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7759 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7761 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7760 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7762 )
7761 )
7763 def unshelve(ui, repo, *shelved, **opts):
7762 def unshelve(ui, repo, *shelved, **opts):
7764 """restore a shelved change to the working directory
7763 """restore a shelved change to the working directory
7765
7764
7766 This command accepts an optional name of a shelved change to
7765 This command accepts an optional name of a shelved change to
7767 restore. If none is given, the most recent shelved change is used.
7766 restore. If none is given, the most recent shelved change is used.
7768
7767
7769 If a shelved change is applied successfully, the bundle that
7768 If a shelved change is applied successfully, the bundle that
7770 contains the shelved changes is moved to a backup location
7769 contains the shelved changes is moved to a backup location
7771 (.hg/shelve-backup).
7770 (.hg/shelve-backup).
7772
7771
7773 Since you can restore a shelved change on top of an arbitrary
7772 Since you can restore a shelved change on top of an arbitrary
7774 commit, it is possible that unshelving will result in a conflict
7773 commit, it is possible that unshelving will result in a conflict
7775 between your changes and the commits you are unshelving onto. If
7774 between your changes and the commits you are unshelving onto. If
7776 this occurs, you must resolve the conflict, then use
7775 this occurs, you must resolve the conflict, then use
7777 ``--continue`` to complete the unshelve operation. (The bundle
7776 ``--continue`` to complete the unshelve operation. (The bundle
7778 will not be moved until you successfully complete the unshelve.)
7777 will not be moved until you successfully complete the unshelve.)
7779
7778
7780 (Alternatively, you can use ``--abort`` to abandon an unshelve
7779 (Alternatively, you can use ``--abort`` to abandon an unshelve
7781 that causes a conflict. This reverts the unshelved changes, and
7780 that causes a conflict. This reverts the unshelved changes, and
7782 leaves the bundle in place.)
7781 leaves the bundle in place.)
7783
7782
7784 If bare shelved change (without interactive, include and exclude
7783 If bare shelved change (without interactive, include and exclude
7785 option) was done on newly created branch it would restore branch
7784 option) was done on newly created branch it would restore branch
7786 information to the working directory.
7785 information to the working directory.
7787
7786
7788 After a successful unshelve, the shelved changes are stored in a
7787 After a successful unshelve, the shelved changes are stored in a
7789 backup directory. Only the N most recent backups are kept. N
7788 backup directory. Only the N most recent backups are kept. N
7790 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7789 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7791 configuration option.
7790 configuration option.
7792
7791
7793 .. container:: verbose
7792 .. container:: verbose
7794
7793
7795 Timestamp in seconds is used to decide order of backups. More
7794 Timestamp in seconds is used to decide order of backups. More
7796 than ``maxbackups`` backups are kept, if same timestamp
7795 than ``maxbackups`` backups are kept, if same timestamp
7797 prevents from deciding exact order of them, for safety.
7796 prevents from deciding exact order of them, for safety.
7798
7797
7799 Selected changes can be unshelved with ``--interactive`` flag.
7798 Selected changes can be unshelved with ``--interactive`` flag.
7800 The working directory is updated with the selected changes, and
7799 The working directory is updated with the selected changes, and
7801 only the unselected changes remain shelved.
7800 only the unselected changes remain shelved.
7802 Note: The whole shelve is applied to working directory first before
7801 Note: The whole shelve is applied to working directory first before
7803 running interactively. So, this will bring up all the conflicts between
7802 running interactively. So, this will bring up all the conflicts between
7804 working directory and the shelve, irrespective of which changes will be
7803 working directory and the shelve, irrespective of which changes will be
7805 unshelved.
7804 unshelved.
7806 """
7805 """
7807 with repo.wlock():
7806 with repo.wlock():
7808 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7807 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7809
7808
7810
7809
7811 statemod.addunfinished(
7810 statemod.addunfinished(
7812 b'unshelve',
7811 b'unshelve',
7813 fname=b'shelvedstate',
7812 fname=b'shelvedstate',
7814 continueflag=True,
7813 continueflag=True,
7815 abortfunc=shelvemod.hgabortunshelve,
7814 abortfunc=shelvemod.hgabortunshelve,
7816 continuefunc=shelvemod.hgcontinueunshelve,
7815 continuefunc=shelvemod.hgcontinueunshelve,
7817 cmdmsg=_(b'unshelve already in progress'),
7816 cmdmsg=_(b'unshelve already in progress'),
7818 )
7817 )
7819
7818
7820
7819
7821 @command(
7820 @command(
7822 b'update|up|checkout|co',
7821 b'update|up|checkout|co',
7823 [
7822 [
7824 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7823 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7825 (b'c', b'check', None, _(b'require clean working directory')),
7824 (b'c', b'check', None, _(b'require clean working directory')),
7826 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7825 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7827 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7826 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7828 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7827 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7829 ]
7828 ]
7830 + mergetoolopts,
7829 + mergetoolopts,
7831 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7830 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7832 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7831 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7833 helpbasic=True,
7832 helpbasic=True,
7834 )
7833 )
7835 def update(ui, repo, node=None, **opts):
7834 def update(ui, repo, node=None, **opts):
7836 """update working directory (or switch revisions)
7835 """update working directory (or switch revisions)
7837
7836
7838 Update the repository's working directory to the specified
7837 Update the repository's working directory to the specified
7839 changeset. If no changeset is specified, update to the tip of the
7838 changeset. If no changeset is specified, update to the tip of the
7840 current named branch and move the active bookmark (see :hg:`help
7839 current named branch and move the active bookmark (see :hg:`help
7841 bookmarks`).
7840 bookmarks`).
7842
7841
7843 Update sets the working directory's parent revision to the specified
7842 Update sets the working directory's parent revision to the specified
7844 changeset (see :hg:`help parents`).
7843 changeset (see :hg:`help parents`).
7845
7844
7846 If the changeset is not a descendant or ancestor of the working
7845 If the changeset is not a descendant or ancestor of the working
7847 directory's parent and there are uncommitted changes, the update is
7846 directory's parent and there are uncommitted changes, the update is
7848 aborted. With the -c/--check option, the working directory is checked
7847 aborted. With the -c/--check option, the working directory is checked
7849 for uncommitted changes; if none are found, the working directory is
7848 for uncommitted changes; if none are found, the working directory is
7850 updated to the specified changeset.
7849 updated to the specified changeset.
7851
7850
7852 .. container:: verbose
7851 .. container:: verbose
7853
7852
7854 The -C/--clean, -c/--check, and -m/--merge options control what
7853 The -C/--clean, -c/--check, and -m/--merge options control what
7855 happens if the working directory contains uncommitted changes.
7854 happens if the working directory contains uncommitted changes.
7856 At most of one of them can be specified.
7855 At most of one of them can be specified.
7857
7856
7858 1. If no option is specified, and if
7857 1. If no option is specified, and if
7859 the requested changeset is an ancestor or descendant of
7858 the requested changeset is an ancestor or descendant of
7860 the working directory's parent, the uncommitted changes
7859 the working directory's parent, the uncommitted changes
7861 are merged into the requested changeset and the merged
7860 are merged into the requested changeset and the merged
7862 result is left uncommitted. If the requested changeset is
7861 result is left uncommitted. If the requested changeset is
7863 not an ancestor or descendant (that is, it is on another
7862 not an ancestor or descendant (that is, it is on another
7864 branch), the update is aborted and the uncommitted changes
7863 branch), the update is aborted and the uncommitted changes
7865 are preserved.
7864 are preserved.
7866
7865
7867 2. With the -m/--merge option, the update is allowed even if the
7866 2. With the -m/--merge option, the update is allowed even if the
7868 requested changeset is not an ancestor or descendant of
7867 requested changeset is not an ancestor or descendant of
7869 the working directory's parent.
7868 the working directory's parent.
7870
7869
7871 3. With the -c/--check option, the update is aborted and the
7870 3. With the -c/--check option, the update is aborted and the
7872 uncommitted changes are preserved.
7871 uncommitted changes are preserved.
7873
7872
7874 4. With the -C/--clean option, uncommitted changes are discarded and
7873 4. With the -C/--clean option, uncommitted changes are discarded and
7875 the working directory is updated to the requested changeset.
7874 the working directory is updated to the requested changeset.
7876
7875
7877 To cancel an uncommitted merge (and lose your changes), use
7876 To cancel an uncommitted merge (and lose your changes), use
7878 :hg:`merge --abort`.
7877 :hg:`merge --abort`.
7879
7878
7880 Use null as the changeset to remove the working directory (like
7879 Use null as the changeset to remove the working directory (like
7881 :hg:`clone -U`).
7880 :hg:`clone -U`).
7882
7881
7883 If you want to revert just one file to an older revision, use
7882 If you want to revert just one file to an older revision, use
7884 :hg:`revert [-r REV] NAME`.
7883 :hg:`revert [-r REV] NAME`.
7885
7884
7886 See :hg:`help dates` for a list of formats valid for -d/--date.
7885 See :hg:`help dates` for a list of formats valid for -d/--date.
7887
7886
7888 Returns 0 on success, 1 if there are unresolved files.
7887 Returns 0 on success, 1 if there are unresolved files.
7889 """
7888 """
7890 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7889 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7891 rev = opts.get('rev')
7890 rev = opts.get('rev')
7892 date = opts.get('date')
7891 date = opts.get('date')
7893 clean = opts.get('clean')
7892 clean = opts.get('clean')
7894 check = opts.get('check')
7893 check = opts.get('check')
7895 merge = opts.get('merge')
7894 merge = opts.get('merge')
7896 if rev and node:
7895 if rev and node:
7897 raise error.InputError(_(b"please specify just one revision"))
7896 raise error.InputError(_(b"please specify just one revision"))
7898
7897
7899 if ui.configbool(b'commands', b'update.requiredest'):
7898 if ui.configbool(b'commands', b'update.requiredest'):
7900 if not node and not rev and not date:
7899 if not node and not rev and not date:
7901 raise error.InputError(
7900 raise error.InputError(
7902 _(b'you must specify a destination'),
7901 _(b'you must specify a destination'),
7903 hint=_(b'for example: hg update ".::"'),
7902 hint=_(b'for example: hg update ".::"'),
7904 )
7903 )
7905
7904
7906 if rev is None or rev == b'':
7905 if rev is None or rev == b'':
7907 rev = node
7906 rev = node
7908
7907
7909 if date and rev is not None:
7908 if date and rev is not None:
7910 raise error.InputError(_(b"you can't specify a revision and a date"))
7909 raise error.InputError(_(b"you can't specify a revision and a date"))
7911
7910
7912 updatecheck = None
7911 updatecheck = None
7913 if check or merge is not None and not merge:
7912 if check or merge is not None and not merge:
7914 updatecheck = b'abort'
7913 updatecheck = b'abort'
7915 elif merge or check is not None and not check:
7914 elif merge or check is not None and not check:
7916 updatecheck = b'none'
7915 updatecheck = b'none'
7917
7916
7918 with repo.wlock():
7917 with repo.wlock():
7919 cmdutil.clearunfinished(repo)
7918 cmdutil.clearunfinished(repo)
7920 if date:
7919 if date:
7921 rev = cmdutil.finddate(ui, repo, date)
7920 rev = cmdutil.finddate(ui, repo, date)
7922
7921
7923 # if we defined a bookmark, we have to remember the original name
7922 # if we defined a bookmark, we have to remember the original name
7924 brev = rev
7923 brev = rev
7925 if rev:
7924 if rev:
7926 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7925 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7927 ctx = logcmdutil.revsingle(repo, rev, default=None)
7926 ctx = logcmdutil.revsingle(repo, rev, default=None)
7928 rev = ctx.rev()
7927 rev = ctx.rev()
7929 hidden = ctx.hidden()
7928 hidden = ctx.hidden()
7930 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7929 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7931 with ui.configoverride(overrides, b'update'):
7930 with ui.configoverride(overrides, b'update'):
7932 ret = hg.updatetotally(
7931 ret = hg.updatetotally(
7933 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7932 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7934 )
7933 )
7935 if hidden:
7934 if hidden:
7936 ctxstr = ctx.hex()[:12]
7935 ctxstr = ctx.hex()[:12]
7937 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7936 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7938
7937
7939 if ctx.obsolete():
7938 if ctx.obsolete():
7940 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7939 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7941 ui.warn(b"(%s)\n" % obsfatemsg)
7940 ui.warn(b"(%s)\n" % obsfatemsg)
7942 return ret
7941 return ret
7943
7942
7944
7943
7945 @command(
7944 @command(
7946 b'verify',
7945 b'verify',
7947 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7946 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7948 helpcategory=command.CATEGORY_MAINTENANCE,
7947 helpcategory=command.CATEGORY_MAINTENANCE,
7949 )
7948 )
7950 def verify(ui, repo, **opts):
7949 def verify(ui, repo, **opts):
7951 """verify the integrity of the repository
7950 """verify the integrity of the repository
7952
7951
7953 Verify the integrity of the current repository.
7952 Verify the integrity of the current repository.
7954
7953
7955 This will perform an extensive check of the repository's
7954 This will perform an extensive check of the repository's
7956 integrity, validating the hashes and checksums of each entry in
7955 integrity, validating the hashes and checksums of each entry in
7957 the changelog, manifest, and tracked files, as well as the
7956 the changelog, manifest, and tracked files, as well as the
7958 integrity of their crosslinks and indices.
7957 integrity of their crosslinks and indices.
7959
7958
7960 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7959 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7961 for more information about recovery from corruption of the
7960 for more information about recovery from corruption of the
7962 repository.
7961 repository.
7963
7962
7964 Returns 0 on success, 1 if errors are encountered.
7963 Returns 0 on success, 1 if errors are encountered.
7965 """
7964 """
7966 opts = pycompat.byteskwargs(opts)
7965 opts = pycompat.byteskwargs(opts)
7967
7966
7968 level = None
7967 level = None
7969 if opts[b'full']:
7968 if opts[b'full']:
7970 level = verifymod.VERIFY_FULL
7969 level = verifymod.VERIFY_FULL
7971 return hg.verify(repo, level)
7970 return hg.verify(repo, level)
7972
7971
7973
7972
7974 @command(
7973 @command(
7975 b'version',
7974 b'version',
7976 [] + formatteropts,
7975 [] + formatteropts,
7977 helpcategory=command.CATEGORY_HELP,
7976 helpcategory=command.CATEGORY_HELP,
7978 norepo=True,
7977 norepo=True,
7979 intents={INTENT_READONLY},
7978 intents={INTENT_READONLY},
7980 )
7979 )
7981 def version_(ui, **opts):
7980 def version_(ui, **opts):
7982 """output version and copyright information
7981 """output version and copyright information
7983
7982
7984 .. container:: verbose
7983 .. container:: verbose
7985
7984
7986 Template:
7985 Template:
7987
7986
7988 The following keywords are supported. See also :hg:`help templates`.
7987 The following keywords are supported. See also :hg:`help templates`.
7989
7988
7990 :extensions: List of extensions.
7989 :extensions: List of extensions.
7991 :ver: String. Version number.
7990 :ver: String. Version number.
7992
7991
7993 And each entry of ``{extensions}`` provides the following sub-keywords
7992 And each entry of ``{extensions}`` provides the following sub-keywords
7994 in addition to ``{ver}``.
7993 in addition to ``{ver}``.
7995
7994
7996 :bundled: Boolean. True if included in the release.
7995 :bundled: Boolean. True if included in the release.
7997 :name: String. Extension name.
7996 :name: String. Extension name.
7998 """
7997 """
7999 opts = pycompat.byteskwargs(opts)
7998 opts = pycompat.byteskwargs(opts)
8000 if ui.verbose:
7999 if ui.verbose:
8001 ui.pager(b'version')
8000 ui.pager(b'version')
8002 fm = ui.formatter(b"version", opts)
8001 fm = ui.formatter(b"version", opts)
8003 fm.startitem()
8002 fm.startitem()
8004 fm.write(
8003 fm.write(
8005 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
8004 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
8006 )
8005 )
8007 license = _(
8006 license = _(
8008 b"(see https://mercurial-scm.org for more information)\n"
8007 b"(see https://mercurial-scm.org for more information)\n"
8009 b"\nCopyright (C) 2005-2023 Olivia Mackall and others\n"
8008 b"\nCopyright (C) 2005-2023 Olivia Mackall and others\n"
8010 b"This is free software; see the source for copying conditions. "
8009 b"This is free software; see the source for copying conditions. "
8011 b"There is NO\nwarranty; "
8010 b"There is NO\nwarranty; "
8012 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
8011 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
8013 )
8012 )
8014 if not ui.quiet:
8013 if not ui.quiet:
8015 fm.plain(license)
8014 fm.plain(license)
8016
8015
8017 if ui.verbose:
8016 if ui.verbose:
8018 fm.plain(_(b"\nEnabled extensions:\n\n"))
8017 fm.plain(_(b"\nEnabled extensions:\n\n"))
8019 # format names and versions into columns
8018 # format names and versions into columns
8020 names = []
8019 names = []
8021 vers = []
8020 vers = []
8022 isinternals = []
8021 isinternals = []
8023 for name, module in sorted(extensions.extensions()):
8022 for name, module in sorted(extensions.extensions()):
8024 names.append(name)
8023 names.append(name)
8025 vers.append(extensions.moduleversion(module) or None)
8024 vers.append(extensions.moduleversion(module) or None)
8026 isinternals.append(extensions.ismoduleinternal(module))
8025 isinternals.append(extensions.ismoduleinternal(module))
8027 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
8026 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
8028 if names:
8027 if names:
8029 namefmt = b" %%-%ds " % max(len(n) for n in names)
8028 namefmt = b" %%-%ds " % max(len(n) for n in names)
8030 places = [_(b"external"), _(b"internal")]
8029 places = [_(b"external"), _(b"internal")]
8031 for n, v, p in zip(names, vers, isinternals):
8030 for n, v, p in zip(names, vers, isinternals):
8032 fn.startitem()
8031 fn.startitem()
8033 fn.condwrite(ui.verbose, b"name", namefmt, n)
8032 fn.condwrite(ui.verbose, b"name", namefmt, n)
8034 if ui.verbose:
8033 if ui.verbose:
8035 fn.plain(b"%s " % places[p])
8034 fn.plain(b"%s " % places[p])
8036 fn.data(bundled=p)
8035 fn.data(bundled=p)
8037 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
8036 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
8038 if ui.verbose:
8037 if ui.verbose:
8039 fn.plain(b"\n")
8038 fn.plain(b"\n")
8040 fn.end()
8039 fn.end()
8041 fm.end()
8040 fm.end()
8042
8041
8043
8042
8044 def loadcmdtable(ui, name, cmdtable):
8043 def loadcmdtable(ui, name, cmdtable):
8045 """Load command functions from specified cmdtable"""
8044 """Load command functions from specified cmdtable"""
8046 overrides = [cmd for cmd in cmdtable if cmd in table]
8045 overrides = [cmd for cmd in cmdtable if cmd in table]
8047 if overrides:
8046 if overrides:
8048 ui.warn(
8047 ui.warn(
8049 _(b"extension '%s' overrides commands: %s\n")
8048 _(b"extension '%s' overrides commands: %s\n")
8050 % (name, b" ".join(overrides))
8049 % (name, b" ".join(overrides))
8051 )
8050 )
8052 table.update(cmdtable)
8051 table.update(cmdtable)
General Comments 0
You need to be logged in to leave comments. Login now