##// END OF EJS Templates
copy: add experimetal support for unmarking committed copies...
Martin von Zweigbergk -
r44845:7c4b98a4 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,4096 +1,4132 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 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy as copymod
10 import copy as copymod
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22 from .pycompat import (
22 from .pycompat import (
23 getattr,
23 getattr,
24 open,
24 open,
25 setattr,
25 setattr,
26 )
26 )
27 from .thirdparty import attr
27 from .thirdparty import attr
28
28
29 from . import (
29 from . import (
30 bookmarks,
30 bookmarks,
31 changelog,
31 changelog,
32 copies,
32 copies,
33 crecord as crecordmod,
33 crecord as crecordmod,
34 dirstateguard,
34 dirstateguard,
35 encoding,
35 encoding,
36 error,
36 error,
37 formatter,
37 formatter,
38 logcmdutil,
38 logcmdutil,
39 match as matchmod,
39 match as matchmod,
40 merge as mergemod,
40 merge as mergemod,
41 mergeutil,
41 mergeutil,
42 obsolete,
42 obsolete,
43 patch,
43 patch,
44 pathutil,
44 pathutil,
45 phases,
45 phases,
46 pycompat,
46 pycompat,
47 repair,
47 repair,
48 revlog,
48 revlog,
49 rewriteutil,
49 rewriteutil,
50 scmutil,
50 scmutil,
51 smartset,
51 smartset,
52 state as statemod,
52 state as statemod,
53 subrepoutil,
53 subrepoutil,
54 templatekw,
54 templatekw,
55 templater,
55 templater,
56 util,
56 util,
57 vfs as vfsmod,
57 vfs as vfsmod,
58 )
58 )
59
59
60 from .utils import (
60 from .utils import (
61 dateutil,
61 dateutil,
62 stringutil,
62 stringutil,
63 )
63 )
64
64
65 if pycompat.TYPE_CHECKING:
65 if pycompat.TYPE_CHECKING:
66 from typing import (
66 from typing import (
67 Any,
67 Any,
68 Dict,
68 Dict,
69 )
69 )
70
70
71 for t in (Any, Dict):
71 for t in (Any, Dict):
72 assert t
72 assert t
73
73
74 stringio = util.stringio
74 stringio = util.stringio
75
75
76 # templates of common command options
76 # templates of common command options
77
77
78 dryrunopts = [
78 dryrunopts = [
79 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
79 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
80 ]
80 ]
81
81
82 confirmopts = [
82 confirmopts = [
83 (b'', b'confirm', None, _(b'ask before applying actions')),
83 (b'', b'confirm', None, _(b'ask before applying actions')),
84 ]
84 ]
85
85
86 remoteopts = [
86 remoteopts = [
87 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
87 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
88 (
88 (
89 b'',
89 b'',
90 b'remotecmd',
90 b'remotecmd',
91 b'',
91 b'',
92 _(b'specify hg command to run on the remote side'),
92 _(b'specify hg command to run on the remote side'),
93 _(b'CMD'),
93 _(b'CMD'),
94 ),
94 ),
95 (
95 (
96 b'',
96 b'',
97 b'insecure',
97 b'insecure',
98 None,
98 None,
99 _(b'do not verify server certificate (ignoring web.cacerts config)'),
99 _(b'do not verify server certificate (ignoring web.cacerts config)'),
100 ),
100 ),
101 ]
101 ]
102
102
103 walkopts = [
103 walkopts = [
104 (
104 (
105 b'I',
105 b'I',
106 b'include',
106 b'include',
107 [],
107 [],
108 _(b'include names matching the given patterns'),
108 _(b'include names matching the given patterns'),
109 _(b'PATTERN'),
109 _(b'PATTERN'),
110 ),
110 ),
111 (
111 (
112 b'X',
112 b'X',
113 b'exclude',
113 b'exclude',
114 [],
114 [],
115 _(b'exclude names matching the given patterns'),
115 _(b'exclude names matching the given patterns'),
116 _(b'PATTERN'),
116 _(b'PATTERN'),
117 ),
117 ),
118 ]
118 ]
119
119
120 commitopts = [
120 commitopts = [
121 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
121 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
122 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
122 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
123 ]
123 ]
124
124
125 commitopts2 = [
125 commitopts2 = [
126 (
126 (
127 b'd',
127 b'd',
128 b'date',
128 b'date',
129 b'',
129 b'',
130 _(b'record the specified date as commit date'),
130 _(b'record the specified date as commit date'),
131 _(b'DATE'),
131 _(b'DATE'),
132 ),
132 ),
133 (
133 (
134 b'u',
134 b'u',
135 b'user',
135 b'user',
136 b'',
136 b'',
137 _(b'record the specified user as committer'),
137 _(b'record the specified user as committer'),
138 _(b'USER'),
138 _(b'USER'),
139 ),
139 ),
140 ]
140 ]
141
141
142 commitopts3 = [
142 commitopts3 = [
143 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
143 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
144 (b'U', b'currentuser', None, _(b'record the current user as committer')),
144 (b'U', b'currentuser', None, _(b'record the current user as committer')),
145 ]
145 ]
146
146
147 formatteropts = [
147 formatteropts = [
148 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
148 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
149 ]
149 ]
150
150
151 templateopts = [
151 templateopts = [
152 (
152 (
153 b'',
153 b'',
154 b'style',
154 b'style',
155 b'',
155 b'',
156 _(b'display using template map file (DEPRECATED)'),
156 _(b'display using template map file (DEPRECATED)'),
157 _(b'STYLE'),
157 _(b'STYLE'),
158 ),
158 ),
159 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
159 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
160 ]
160 ]
161
161
162 logopts = [
162 logopts = [
163 (b'p', b'patch', None, _(b'show patch')),
163 (b'p', b'patch', None, _(b'show patch')),
164 (b'g', b'git', None, _(b'use git extended diff format')),
164 (b'g', b'git', None, _(b'use git extended diff format')),
165 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
165 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
166 (b'M', b'no-merges', None, _(b'do not show merges')),
166 (b'M', b'no-merges', None, _(b'do not show merges')),
167 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
167 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
168 (b'G', b'graph', None, _(b"show the revision DAG")),
168 (b'G', b'graph', None, _(b"show the revision DAG")),
169 ] + templateopts
169 ] + templateopts
170
170
171 diffopts = [
171 diffopts = [
172 (b'a', b'text', None, _(b'treat all files as text')),
172 (b'a', b'text', None, _(b'treat all files as text')),
173 (
173 (
174 b'g',
174 b'g',
175 b'git',
175 b'git',
176 None,
176 None,
177 _(b'use git extended diff format (DEFAULT: diff.git)'),
177 _(b'use git extended diff format (DEFAULT: diff.git)'),
178 ),
178 ),
179 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
179 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
180 (b'', b'nodates', None, _(b'omit dates from diff headers')),
180 (b'', b'nodates', None, _(b'omit dates from diff headers')),
181 ]
181 ]
182
182
183 diffwsopts = [
183 diffwsopts = [
184 (
184 (
185 b'w',
185 b'w',
186 b'ignore-all-space',
186 b'ignore-all-space',
187 None,
187 None,
188 _(b'ignore white space when comparing lines'),
188 _(b'ignore white space when comparing lines'),
189 ),
189 ),
190 (
190 (
191 b'b',
191 b'b',
192 b'ignore-space-change',
192 b'ignore-space-change',
193 None,
193 None,
194 _(b'ignore changes in the amount of white space'),
194 _(b'ignore changes in the amount of white space'),
195 ),
195 ),
196 (
196 (
197 b'B',
197 b'B',
198 b'ignore-blank-lines',
198 b'ignore-blank-lines',
199 None,
199 None,
200 _(b'ignore changes whose lines are all blank'),
200 _(b'ignore changes whose lines are all blank'),
201 ),
201 ),
202 (
202 (
203 b'Z',
203 b'Z',
204 b'ignore-space-at-eol',
204 b'ignore-space-at-eol',
205 None,
205 None,
206 _(b'ignore changes in whitespace at EOL'),
206 _(b'ignore changes in whitespace at EOL'),
207 ),
207 ),
208 ]
208 ]
209
209
210 diffopts2 = (
210 diffopts2 = (
211 [
211 [
212 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
212 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
213 (
213 (
214 b'p',
214 b'p',
215 b'show-function',
215 b'show-function',
216 None,
216 None,
217 _(
217 _(
218 b'show which function each change is in (DEFAULT: diff.showfunc)'
218 b'show which function each change is in (DEFAULT: diff.showfunc)'
219 ),
219 ),
220 ),
220 ),
221 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
221 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
222 ]
222 ]
223 + diffwsopts
223 + diffwsopts
224 + [
224 + [
225 (
225 (
226 b'U',
226 b'U',
227 b'unified',
227 b'unified',
228 b'',
228 b'',
229 _(b'number of lines of context to show'),
229 _(b'number of lines of context to show'),
230 _(b'NUM'),
230 _(b'NUM'),
231 ),
231 ),
232 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
232 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
233 (
233 (
234 b'',
234 b'',
235 b'root',
235 b'root',
236 b'',
236 b'',
237 _(b'produce diffs relative to subdirectory'),
237 _(b'produce diffs relative to subdirectory'),
238 _(b'DIR'),
238 _(b'DIR'),
239 ),
239 ),
240 ]
240 ]
241 )
241 )
242
242
243 mergetoolopts = [
243 mergetoolopts = [
244 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
244 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
245 ]
245 ]
246
246
247 similarityopts = [
247 similarityopts = [
248 (
248 (
249 b's',
249 b's',
250 b'similarity',
250 b'similarity',
251 b'',
251 b'',
252 _(b'guess renamed files by similarity (0<=s<=100)'),
252 _(b'guess renamed files by similarity (0<=s<=100)'),
253 _(b'SIMILARITY'),
253 _(b'SIMILARITY'),
254 )
254 )
255 ]
255 ]
256
256
257 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
257 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
258
258
259 debugrevlogopts = [
259 debugrevlogopts = [
260 (b'c', b'changelog', False, _(b'open changelog')),
260 (b'c', b'changelog', False, _(b'open changelog')),
261 (b'm', b'manifest', False, _(b'open manifest')),
261 (b'm', b'manifest', False, _(b'open manifest')),
262 (b'', b'dir', b'', _(b'open directory manifest')),
262 (b'', b'dir', b'', _(b'open directory manifest')),
263 ]
263 ]
264
264
265 # special string such that everything below this line will be ingored in the
265 # special string such that everything below this line will be ingored in the
266 # editor text
266 # editor text
267 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
267 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
268
268
269
269
270 def check_at_most_one_arg(opts, *args):
270 def check_at_most_one_arg(opts, *args):
271 """abort if more than one of the arguments are in opts
271 """abort if more than one of the arguments are in opts
272
272
273 Returns the unique argument or None if none of them were specified.
273 Returns the unique argument or None if none of them were specified.
274 """
274 """
275
275
276 def to_display(name):
276 def to_display(name):
277 return pycompat.sysbytes(name).replace(b'_', b'-')
277 return pycompat.sysbytes(name).replace(b'_', b'-')
278
278
279 previous = None
279 previous = None
280 for x in args:
280 for x in args:
281 if opts.get(x):
281 if opts.get(x):
282 if previous:
282 if previous:
283 raise error.Abort(
283 raise error.Abort(
284 _(b'cannot specify both --%s and --%s')
284 _(b'cannot specify both --%s and --%s')
285 % (to_display(previous), to_display(x))
285 % (to_display(previous), to_display(x))
286 )
286 )
287 previous = x
287 previous = x
288 return previous
288 return previous
289
289
290
290
291 def check_incompatible_arguments(opts, first, others):
291 def check_incompatible_arguments(opts, first, others):
292 """abort if the first argument is given along with any of the others
292 """abort if the first argument is given along with any of the others
293
293
294 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
294 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
295 among themselves, and they're passed as a single collection.
295 among themselves, and they're passed as a single collection.
296 """
296 """
297 for other in others:
297 for other in others:
298 check_at_most_one_arg(opts, first, other)
298 check_at_most_one_arg(opts, first, other)
299
299
300
300
301 def resolvecommitoptions(ui, opts):
301 def resolvecommitoptions(ui, opts):
302 """modify commit options dict to handle related options
302 """modify commit options dict to handle related options
303
303
304 The return value indicates that ``rewrite.update-timestamp`` is the reason
304 The return value indicates that ``rewrite.update-timestamp`` is the reason
305 the ``date`` option is set.
305 the ``date`` option is set.
306 """
306 """
307 check_at_most_one_arg(opts, b'date', b'currentdate')
307 check_at_most_one_arg(opts, b'date', b'currentdate')
308 check_at_most_one_arg(opts, b'user', b'currentuser')
308 check_at_most_one_arg(opts, b'user', b'currentuser')
309
309
310 datemaydiffer = False # date-only change should be ignored?
310 datemaydiffer = False # date-only change should be ignored?
311
311
312 if opts.get(b'currentdate'):
312 if opts.get(b'currentdate'):
313 opts[b'date'] = b'%d %d' % dateutil.makedate()
313 opts[b'date'] = b'%d %d' % dateutil.makedate()
314 elif (
314 elif (
315 not opts.get(b'date')
315 not opts.get(b'date')
316 and ui.configbool(b'rewrite', b'update-timestamp')
316 and ui.configbool(b'rewrite', b'update-timestamp')
317 and opts.get(b'currentdate') is None
317 and opts.get(b'currentdate') is None
318 ):
318 ):
319 opts[b'date'] = b'%d %d' % dateutil.makedate()
319 opts[b'date'] = b'%d %d' % dateutil.makedate()
320 datemaydiffer = True
320 datemaydiffer = True
321
321
322 if opts.get(b'currentuser'):
322 if opts.get(b'currentuser'):
323 opts[b'user'] = ui.username()
323 opts[b'user'] = ui.username()
324
324
325 return datemaydiffer
325 return datemaydiffer
326
326
327
327
328 def checknotesize(ui, opts):
328 def checknotesize(ui, opts):
329 """ make sure note is of valid format """
329 """ make sure note is of valid format """
330
330
331 note = opts.get(b'note')
331 note = opts.get(b'note')
332 if not note:
332 if not note:
333 return
333 return
334
334
335 if len(note) > 255:
335 if len(note) > 255:
336 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
336 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
337 if b'\n' in note:
337 if b'\n' in note:
338 raise error.Abort(_(b"note cannot contain a newline"))
338 raise error.Abort(_(b"note cannot contain a newline"))
339
339
340
340
341 def ishunk(x):
341 def ishunk(x):
342 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
342 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
343 return isinstance(x, hunkclasses)
343 return isinstance(x, hunkclasses)
344
344
345
345
346 def newandmodified(chunks, originalchunks):
346 def newandmodified(chunks, originalchunks):
347 newlyaddedandmodifiedfiles = set()
347 newlyaddedandmodifiedfiles = set()
348 alsorestore = set()
348 alsorestore = set()
349 for chunk in chunks:
349 for chunk in chunks:
350 if (
350 if (
351 ishunk(chunk)
351 ishunk(chunk)
352 and chunk.header.isnewfile()
352 and chunk.header.isnewfile()
353 and chunk not in originalchunks
353 and chunk not in originalchunks
354 ):
354 ):
355 newlyaddedandmodifiedfiles.add(chunk.header.filename())
355 newlyaddedandmodifiedfiles.add(chunk.header.filename())
356 alsorestore.update(
356 alsorestore.update(
357 set(chunk.header.files()) - {chunk.header.filename()}
357 set(chunk.header.files()) - {chunk.header.filename()}
358 )
358 )
359 return newlyaddedandmodifiedfiles, alsorestore
359 return newlyaddedandmodifiedfiles, alsorestore
360
360
361
361
362 def parsealiases(cmd):
362 def parsealiases(cmd):
363 return cmd.split(b"|")
363 return cmd.split(b"|")
364
364
365
365
366 def setupwrapcolorwrite(ui):
366 def setupwrapcolorwrite(ui):
367 # wrap ui.write so diff output can be labeled/colorized
367 # wrap ui.write so diff output can be labeled/colorized
368 def wrapwrite(orig, *args, **kw):
368 def wrapwrite(orig, *args, **kw):
369 label = kw.pop('label', b'')
369 label = kw.pop('label', b'')
370 for chunk, l in patch.difflabel(lambda: args):
370 for chunk, l in patch.difflabel(lambda: args):
371 orig(chunk, label=label + l)
371 orig(chunk, label=label + l)
372
372
373 oldwrite = ui.write
373 oldwrite = ui.write
374
374
375 def wrap(*args, **kwargs):
375 def wrap(*args, **kwargs):
376 return wrapwrite(oldwrite, *args, **kwargs)
376 return wrapwrite(oldwrite, *args, **kwargs)
377
377
378 setattr(ui, 'write', wrap)
378 setattr(ui, 'write', wrap)
379 return oldwrite
379 return oldwrite
380
380
381
381
382 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
382 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
383 try:
383 try:
384 if usecurses:
384 if usecurses:
385 if testfile:
385 if testfile:
386 recordfn = crecordmod.testdecorator(
386 recordfn = crecordmod.testdecorator(
387 testfile, crecordmod.testchunkselector
387 testfile, crecordmod.testchunkselector
388 )
388 )
389 else:
389 else:
390 recordfn = crecordmod.chunkselector
390 recordfn = crecordmod.chunkselector
391
391
392 return crecordmod.filterpatch(
392 return crecordmod.filterpatch(
393 ui, originalhunks, recordfn, operation
393 ui, originalhunks, recordfn, operation
394 )
394 )
395 except crecordmod.fallbackerror as e:
395 except crecordmod.fallbackerror as e:
396 ui.warn(b'%s\n' % e)
396 ui.warn(b'%s\n' % e)
397 ui.warn(_(b'falling back to text mode\n'))
397 ui.warn(_(b'falling back to text mode\n'))
398
398
399 return patch.filterpatch(ui, originalhunks, match, operation)
399 return patch.filterpatch(ui, originalhunks, match, operation)
400
400
401
401
402 def recordfilter(ui, originalhunks, match, operation=None):
402 def recordfilter(ui, originalhunks, match, operation=None):
403 """ Prompts the user to filter the originalhunks and return a list of
403 """ Prompts the user to filter the originalhunks and return a list of
404 selected hunks.
404 selected hunks.
405 *operation* is used for to build ui messages to indicate the user what
405 *operation* is used for to build ui messages to indicate the user what
406 kind of filtering they are doing: reverting, committing, shelving, etc.
406 kind of filtering they are doing: reverting, committing, shelving, etc.
407 (see patch.filterpatch).
407 (see patch.filterpatch).
408 """
408 """
409 usecurses = crecordmod.checkcurses(ui)
409 usecurses = crecordmod.checkcurses(ui)
410 testfile = ui.config(b'experimental', b'crecordtest')
410 testfile = ui.config(b'experimental', b'crecordtest')
411 oldwrite = setupwrapcolorwrite(ui)
411 oldwrite = setupwrapcolorwrite(ui)
412 try:
412 try:
413 newchunks, newopts = filterchunks(
413 newchunks, newopts = filterchunks(
414 ui, originalhunks, usecurses, testfile, match, operation
414 ui, originalhunks, usecurses, testfile, match, operation
415 )
415 )
416 finally:
416 finally:
417 ui.write = oldwrite
417 ui.write = oldwrite
418 return newchunks, newopts
418 return newchunks, newopts
419
419
420
420
421 def dorecord(
421 def dorecord(
422 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
422 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
423 ):
423 ):
424 opts = pycompat.byteskwargs(opts)
424 opts = pycompat.byteskwargs(opts)
425 if not ui.interactive():
425 if not ui.interactive():
426 if cmdsuggest:
426 if cmdsuggest:
427 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
427 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
428 else:
428 else:
429 msg = _(b'running non-interactively')
429 msg = _(b'running non-interactively')
430 raise error.Abort(msg)
430 raise error.Abort(msg)
431
431
432 # make sure username is set before going interactive
432 # make sure username is set before going interactive
433 if not opts.get(b'user'):
433 if not opts.get(b'user'):
434 ui.username() # raise exception, username not provided
434 ui.username() # raise exception, username not provided
435
435
436 def recordfunc(ui, repo, message, match, opts):
436 def recordfunc(ui, repo, message, match, opts):
437 """This is generic record driver.
437 """This is generic record driver.
438
438
439 Its job is to interactively filter local changes, and
439 Its job is to interactively filter local changes, and
440 accordingly prepare working directory into a state in which the
440 accordingly prepare working directory into a state in which the
441 job can be delegated to a non-interactive commit command such as
441 job can be delegated to a non-interactive commit command such as
442 'commit' or 'qrefresh'.
442 'commit' or 'qrefresh'.
443
443
444 After the actual job is done by non-interactive command, the
444 After the actual job is done by non-interactive command, the
445 working directory is restored to its original state.
445 working directory is restored to its original state.
446
446
447 In the end we'll record interesting changes, and everything else
447 In the end we'll record interesting changes, and everything else
448 will be left in place, so the user can continue working.
448 will be left in place, so the user can continue working.
449 """
449 """
450 if not opts.get(b'interactive-unshelve'):
450 if not opts.get(b'interactive-unshelve'):
451 checkunfinished(repo, commit=True)
451 checkunfinished(repo, commit=True)
452 wctx = repo[None]
452 wctx = repo[None]
453 merge = len(wctx.parents()) > 1
453 merge = len(wctx.parents()) > 1
454 if merge:
454 if merge:
455 raise error.Abort(
455 raise error.Abort(
456 _(
456 _(
457 b'cannot partially commit a merge '
457 b'cannot partially commit a merge '
458 b'(use "hg commit" instead)'
458 b'(use "hg commit" instead)'
459 )
459 )
460 )
460 )
461
461
462 def fail(f, msg):
462 def fail(f, msg):
463 raise error.Abort(b'%s: %s' % (f, msg))
463 raise error.Abort(b'%s: %s' % (f, msg))
464
464
465 force = opts.get(b'force')
465 force = opts.get(b'force')
466 if not force:
466 if not force:
467 match = matchmod.badmatch(match, fail)
467 match = matchmod.badmatch(match, fail)
468
468
469 status = repo.status(match=match)
469 status = repo.status(match=match)
470
470
471 overrides = {(b'ui', b'commitsubrepos'): True}
471 overrides = {(b'ui', b'commitsubrepos'): True}
472
472
473 with repo.ui.configoverride(overrides, b'record'):
473 with repo.ui.configoverride(overrides, b'record'):
474 # subrepoutil.precommit() modifies the status
474 # subrepoutil.precommit() modifies the status
475 tmpstatus = scmutil.status(
475 tmpstatus = scmutil.status(
476 copymod.copy(status.modified),
476 copymod.copy(status.modified),
477 copymod.copy(status.added),
477 copymod.copy(status.added),
478 copymod.copy(status.removed),
478 copymod.copy(status.removed),
479 copymod.copy(status.deleted),
479 copymod.copy(status.deleted),
480 copymod.copy(status.unknown),
480 copymod.copy(status.unknown),
481 copymod.copy(status.ignored),
481 copymod.copy(status.ignored),
482 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
482 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
483 )
483 )
484
484
485 # Force allows -X subrepo to skip the subrepo.
485 # Force allows -X subrepo to skip the subrepo.
486 subs, commitsubs, newstate = subrepoutil.precommit(
486 subs, commitsubs, newstate = subrepoutil.precommit(
487 repo.ui, wctx, tmpstatus, match, force=True
487 repo.ui, wctx, tmpstatus, match, force=True
488 )
488 )
489 for s in subs:
489 for s in subs:
490 if s in commitsubs:
490 if s in commitsubs:
491 dirtyreason = wctx.sub(s).dirtyreason(True)
491 dirtyreason = wctx.sub(s).dirtyreason(True)
492 raise error.Abort(dirtyreason)
492 raise error.Abort(dirtyreason)
493
493
494 if not force:
494 if not force:
495 repo.checkcommitpatterns(wctx, match, status, fail)
495 repo.checkcommitpatterns(wctx, match, status, fail)
496 diffopts = patch.difffeatureopts(
496 diffopts = patch.difffeatureopts(
497 ui,
497 ui,
498 opts=opts,
498 opts=opts,
499 whitespace=True,
499 whitespace=True,
500 section=b'commands',
500 section=b'commands',
501 configprefix=b'commit.interactive.',
501 configprefix=b'commit.interactive.',
502 )
502 )
503 diffopts.nodates = True
503 diffopts.nodates = True
504 diffopts.git = True
504 diffopts.git = True
505 diffopts.showfunc = True
505 diffopts.showfunc = True
506 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
506 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
507 originalchunks = patch.parsepatch(originaldiff)
507 originalchunks = patch.parsepatch(originaldiff)
508 match = scmutil.match(repo[None], pats)
508 match = scmutil.match(repo[None], pats)
509
509
510 # 1. filter patch, since we are intending to apply subset of it
510 # 1. filter patch, since we are intending to apply subset of it
511 try:
511 try:
512 chunks, newopts = filterfn(ui, originalchunks, match)
512 chunks, newopts = filterfn(ui, originalchunks, match)
513 except error.PatchError as err:
513 except error.PatchError as err:
514 raise error.Abort(_(b'error parsing patch: %s') % err)
514 raise error.Abort(_(b'error parsing patch: %s') % err)
515 opts.update(newopts)
515 opts.update(newopts)
516
516
517 # We need to keep a backup of files that have been newly added and
517 # We need to keep a backup of files that have been newly added and
518 # modified during the recording process because there is a previous
518 # modified during the recording process because there is a previous
519 # version without the edit in the workdir. We also will need to restore
519 # version without the edit in the workdir. We also will need to restore
520 # files that were the sources of renames so that the patch application
520 # files that were the sources of renames so that the patch application
521 # works.
521 # works.
522 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
522 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
523 chunks, originalchunks
523 chunks, originalchunks
524 )
524 )
525 contenders = set()
525 contenders = set()
526 for h in chunks:
526 for h in chunks:
527 try:
527 try:
528 contenders.update(set(h.files()))
528 contenders.update(set(h.files()))
529 except AttributeError:
529 except AttributeError:
530 pass
530 pass
531
531
532 changed = status.modified + status.added + status.removed
532 changed = status.modified + status.added + status.removed
533 newfiles = [f for f in changed if f in contenders]
533 newfiles = [f for f in changed if f in contenders]
534 if not newfiles:
534 if not newfiles:
535 ui.status(_(b'no changes to record\n'))
535 ui.status(_(b'no changes to record\n'))
536 return 0
536 return 0
537
537
538 modified = set(status.modified)
538 modified = set(status.modified)
539
539
540 # 2. backup changed files, so we can restore them in the end
540 # 2. backup changed files, so we can restore them in the end
541
541
542 if backupall:
542 if backupall:
543 tobackup = changed
543 tobackup = changed
544 else:
544 else:
545 tobackup = [
545 tobackup = [
546 f
546 f
547 for f in newfiles
547 for f in newfiles
548 if f in modified or f in newlyaddedandmodifiedfiles
548 if f in modified or f in newlyaddedandmodifiedfiles
549 ]
549 ]
550 backups = {}
550 backups = {}
551 if tobackup:
551 if tobackup:
552 backupdir = repo.vfs.join(b'record-backups')
552 backupdir = repo.vfs.join(b'record-backups')
553 try:
553 try:
554 os.mkdir(backupdir)
554 os.mkdir(backupdir)
555 except OSError as err:
555 except OSError as err:
556 if err.errno != errno.EEXIST:
556 if err.errno != errno.EEXIST:
557 raise
557 raise
558 try:
558 try:
559 # backup continues
559 # backup continues
560 for f in tobackup:
560 for f in tobackup:
561 fd, tmpname = pycompat.mkstemp(
561 fd, tmpname = pycompat.mkstemp(
562 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
562 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
563 )
563 )
564 os.close(fd)
564 os.close(fd)
565 ui.debug(b'backup %r as %r\n' % (f, tmpname))
565 ui.debug(b'backup %r as %r\n' % (f, tmpname))
566 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
566 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
567 backups[f] = tmpname
567 backups[f] = tmpname
568
568
569 fp = stringio()
569 fp = stringio()
570 for c in chunks:
570 for c in chunks:
571 fname = c.filename()
571 fname = c.filename()
572 if fname in backups:
572 if fname in backups:
573 c.write(fp)
573 c.write(fp)
574 dopatch = fp.tell()
574 dopatch = fp.tell()
575 fp.seek(0)
575 fp.seek(0)
576
576
577 # 2.5 optionally review / modify patch in text editor
577 # 2.5 optionally review / modify patch in text editor
578 if opts.get(b'review', False):
578 if opts.get(b'review', False):
579 patchtext = (
579 patchtext = (
580 crecordmod.diffhelptext
580 crecordmod.diffhelptext
581 + crecordmod.patchhelptext
581 + crecordmod.patchhelptext
582 + fp.read()
582 + fp.read()
583 )
583 )
584 reviewedpatch = ui.edit(
584 reviewedpatch = ui.edit(
585 patchtext, b"", action=b"diff", repopath=repo.path
585 patchtext, b"", action=b"diff", repopath=repo.path
586 )
586 )
587 fp.truncate(0)
587 fp.truncate(0)
588 fp.write(reviewedpatch)
588 fp.write(reviewedpatch)
589 fp.seek(0)
589 fp.seek(0)
590
590
591 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
591 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
592 # 3a. apply filtered patch to clean repo (clean)
592 # 3a. apply filtered patch to clean repo (clean)
593 if backups:
593 if backups:
594 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
594 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
595 mergemod.revert_to(repo[b'.'], matcher=m)
595 mergemod.revert_to(repo[b'.'], matcher=m)
596
596
597 # 3b. (apply)
597 # 3b. (apply)
598 if dopatch:
598 if dopatch:
599 try:
599 try:
600 ui.debug(b'applying patch\n')
600 ui.debug(b'applying patch\n')
601 ui.debug(fp.getvalue())
601 ui.debug(fp.getvalue())
602 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
602 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
603 except error.PatchError as err:
603 except error.PatchError as err:
604 raise error.Abort(pycompat.bytestr(err))
604 raise error.Abort(pycompat.bytestr(err))
605 del fp
605 del fp
606
606
607 # 4. We prepared working directory according to filtered
607 # 4. We prepared working directory according to filtered
608 # patch. Now is the time to delegate the job to
608 # patch. Now is the time to delegate the job to
609 # commit/qrefresh or the like!
609 # commit/qrefresh or the like!
610
610
611 # Make all of the pathnames absolute.
611 # Make all of the pathnames absolute.
612 newfiles = [repo.wjoin(nf) for nf in newfiles]
612 newfiles = [repo.wjoin(nf) for nf in newfiles]
613 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
613 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
614 finally:
614 finally:
615 # 5. finally restore backed-up files
615 # 5. finally restore backed-up files
616 try:
616 try:
617 dirstate = repo.dirstate
617 dirstate = repo.dirstate
618 for realname, tmpname in pycompat.iteritems(backups):
618 for realname, tmpname in pycompat.iteritems(backups):
619 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
619 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
620
620
621 if dirstate[realname] == b'n':
621 if dirstate[realname] == b'n':
622 # without normallookup, restoring timestamp
622 # without normallookup, restoring timestamp
623 # may cause partially committed files
623 # may cause partially committed files
624 # to be treated as unmodified
624 # to be treated as unmodified
625 dirstate.normallookup(realname)
625 dirstate.normallookup(realname)
626
626
627 # copystat=True here and above are a hack to trick any
627 # copystat=True here and above are a hack to trick any
628 # editors that have f open that we haven't modified them.
628 # editors that have f open that we haven't modified them.
629 #
629 #
630 # Also note that this racy as an editor could notice the
630 # Also note that this racy as an editor could notice the
631 # file's mtime before we've finished writing it.
631 # file's mtime before we've finished writing it.
632 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
632 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
633 os.unlink(tmpname)
633 os.unlink(tmpname)
634 if tobackup:
634 if tobackup:
635 os.rmdir(backupdir)
635 os.rmdir(backupdir)
636 except OSError:
636 except OSError:
637 pass
637 pass
638
638
639 def recordinwlock(ui, repo, message, match, opts):
639 def recordinwlock(ui, repo, message, match, opts):
640 with repo.wlock():
640 with repo.wlock():
641 return recordfunc(ui, repo, message, match, opts)
641 return recordfunc(ui, repo, message, match, opts)
642
642
643 return commit(ui, repo, recordinwlock, pats, opts)
643 return commit(ui, repo, recordinwlock, pats, opts)
644
644
645
645
646 class dirnode(object):
646 class dirnode(object):
647 """
647 """
648 Represent a directory in user working copy with information required for
648 Represent a directory in user working copy with information required for
649 the purpose of tersing its status.
649 the purpose of tersing its status.
650
650
651 path is the path to the directory, without a trailing '/'
651 path is the path to the directory, without a trailing '/'
652
652
653 statuses is a set of statuses of all files in this directory (this includes
653 statuses is a set of statuses of all files in this directory (this includes
654 all the files in all the subdirectories too)
654 all the files in all the subdirectories too)
655
655
656 files is a list of files which are direct child of this directory
656 files is a list of files which are direct child of this directory
657
657
658 subdirs is a dictionary of sub-directory name as the key and it's own
658 subdirs is a dictionary of sub-directory name as the key and it's own
659 dirnode object as the value
659 dirnode object as the value
660 """
660 """
661
661
662 def __init__(self, dirpath):
662 def __init__(self, dirpath):
663 self.path = dirpath
663 self.path = dirpath
664 self.statuses = set()
664 self.statuses = set()
665 self.files = []
665 self.files = []
666 self.subdirs = {}
666 self.subdirs = {}
667
667
668 def _addfileindir(self, filename, status):
668 def _addfileindir(self, filename, status):
669 """Add a file in this directory as a direct child."""
669 """Add a file in this directory as a direct child."""
670 self.files.append((filename, status))
670 self.files.append((filename, status))
671
671
672 def addfile(self, filename, status):
672 def addfile(self, filename, status):
673 """
673 """
674 Add a file to this directory or to its direct parent directory.
674 Add a file to this directory or to its direct parent directory.
675
675
676 If the file is not direct child of this directory, we traverse to the
676 If the file is not direct child of this directory, we traverse to the
677 directory of which this file is a direct child of and add the file
677 directory of which this file is a direct child of and add the file
678 there.
678 there.
679 """
679 """
680
680
681 # the filename contains a path separator, it means it's not the direct
681 # the filename contains a path separator, it means it's not the direct
682 # child of this directory
682 # child of this directory
683 if b'/' in filename:
683 if b'/' in filename:
684 subdir, filep = filename.split(b'/', 1)
684 subdir, filep = filename.split(b'/', 1)
685
685
686 # does the dirnode object for subdir exists
686 # does the dirnode object for subdir exists
687 if subdir not in self.subdirs:
687 if subdir not in self.subdirs:
688 subdirpath = pathutil.join(self.path, subdir)
688 subdirpath = pathutil.join(self.path, subdir)
689 self.subdirs[subdir] = dirnode(subdirpath)
689 self.subdirs[subdir] = dirnode(subdirpath)
690
690
691 # try adding the file in subdir
691 # try adding the file in subdir
692 self.subdirs[subdir].addfile(filep, status)
692 self.subdirs[subdir].addfile(filep, status)
693
693
694 else:
694 else:
695 self._addfileindir(filename, status)
695 self._addfileindir(filename, status)
696
696
697 if status not in self.statuses:
697 if status not in self.statuses:
698 self.statuses.add(status)
698 self.statuses.add(status)
699
699
700 def iterfilepaths(self):
700 def iterfilepaths(self):
701 """Yield (status, path) for files directly under this directory."""
701 """Yield (status, path) for files directly under this directory."""
702 for f, st in self.files:
702 for f, st in self.files:
703 yield st, pathutil.join(self.path, f)
703 yield st, pathutil.join(self.path, f)
704
704
705 def tersewalk(self, terseargs):
705 def tersewalk(self, terseargs):
706 """
706 """
707 Yield (status, path) obtained by processing the status of this
707 Yield (status, path) obtained by processing the status of this
708 dirnode.
708 dirnode.
709
709
710 terseargs is the string of arguments passed by the user with `--terse`
710 terseargs is the string of arguments passed by the user with `--terse`
711 flag.
711 flag.
712
712
713 Following are the cases which can happen:
713 Following are the cases which can happen:
714
714
715 1) All the files in the directory (including all the files in its
715 1) All the files in the directory (including all the files in its
716 subdirectories) share the same status and the user has asked us to terse
716 subdirectories) share the same status and the user has asked us to terse
717 that status. -> yield (status, dirpath). dirpath will end in '/'.
717 that status. -> yield (status, dirpath). dirpath will end in '/'.
718
718
719 2) Otherwise, we do following:
719 2) Otherwise, we do following:
720
720
721 a) Yield (status, filepath) for all the files which are in this
721 a) Yield (status, filepath) for all the files which are in this
722 directory (only the ones in this directory, not the subdirs)
722 directory (only the ones in this directory, not the subdirs)
723
723
724 b) Recurse the function on all the subdirectories of this
724 b) Recurse the function on all the subdirectories of this
725 directory
725 directory
726 """
726 """
727
727
728 if len(self.statuses) == 1:
728 if len(self.statuses) == 1:
729 onlyst = self.statuses.pop()
729 onlyst = self.statuses.pop()
730
730
731 # Making sure we terse only when the status abbreviation is
731 # Making sure we terse only when the status abbreviation is
732 # passed as terse argument
732 # passed as terse argument
733 if onlyst in terseargs:
733 if onlyst in terseargs:
734 yield onlyst, self.path + b'/'
734 yield onlyst, self.path + b'/'
735 return
735 return
736
736
737 # add the files to status list
737 # add the files to status list
738 for st, fpath in self.iterfilepaths():
738 for st, fpath in self.iterfilepaths():
739 yield st, fpath
739 yield st, fpath
740
740
741 # recurse on the subdirs
741 # recurse on the subdirs
742 for dirobj in self.subdirs.values():
742 for dirobj in self.subdirs.values():
743 for st, fpath in dirobj.tersewalk(terseargs):
743 for st, fpath in dirobj.tersewalk(terseargs):
744 yield st, fpath
744 yield st, fpath
745
745
746
746
747 def tersedir(statuslist, terseargs):
747 def tersedir(statuslist, terseargs):
748 """
748 """
749 Terse the status if all the files in a directory shares the same status.
749 Terse the status if all the files in a directory shares the same status.
750
750
751 statuslist is scmutil.status() object which contains a list of files for
751 statuslist is scmutil.status() object which contains a list of files for
752 each status.
752 each status.
753 terseargs is string which is passed by the user as the argument to `--terse`
753 terseargs is string which is passed by the user as the argument to `--terse`
754 flag.
754 flag.
755
755
756 The function makes a tree of objects of dirnode class, and at each node it
756 The function makes a tree of objects of dirnode class, and at each node it
757 stores the information required to know whether we can terse a certain
757 stores the information required to know whether we can terse a certain
758 directory or not.
758 directory or not.
759 """
759 """
760 # the order matters here as that is used to produce final list
760 # the order matters here as that is used to produce final list
761 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
761 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
762
762
763 # checking the argument validity
763 # checking the argument validity
764 for s in pycompat.bytestr(terseargs):
764 for s in pycompat.bytestr(terseargs):
765 if s not in allst:
765 if s not in allst:
766 raise error.Abort(_(b"'%s' not recognized") % s)
766 raise error.Abort(_(b"'%s' not recognized") % s)
767
767
768 # creating a dirnode object for the root of the repo
768 # creating a dirnode object for the root of the repo
769 rootobj = dirnode(b'')
769 rootobj = dirnode(b'')
770 pstatus = (
770 pstatus = (
771 b'modified',
771 b'modified',
772 b'added',
772 b'added',
773 b'deleted',
773 b'deleted',
774 b'clean',
774 b'clean',
775 b'unknown',
775 b'unknown',
776 b'ignored',
776 b'ignored',
777 b'removed',
777 b'removed',
778 )
778 )
779
779
780 tersedict = {}
780 tersedict = {}
781 for attrname in pstatus:
781 for attrname in pstatus:
782 statuschar = attrname[0:1]
782 statuschar = attrname[0:1]
783 for f in getattr(statuslist, attrname):
783 for f in getattr(statuslist, attrname):
784 rootobj.addfile(f, statuschar)
784 rootobj.addfile(f, statuschar)
785 tersedict[statuschar] = []
785 tersedict[statuschar] = []
786
786
787 # we won't be tersing the root dir, so add files in it
787 # we won't be tersing the root dir, so add files in it
788 for st, fpath in rootobj.iterfilepaths():
788 for st, fpath in rootobj.iterfilepaths():
789 tersedict[st].append(fpath)
789 tersedict[st].append(fpath)
790
790
791 # process each sub-directory and build tersedict
791 # process each sub-directory and build tersedict
792 for subdir in rootobj.subdirs.values():
792 for subdir in rootobj.subdirs.values():
793 for st, f in subdir.tersewalk(terseargs):
793 for st, f in subdir.tersewalk(terseargs):
794 tersedict[st].append(f)
794 tersedict[st].append(f)
795
795
796 tersedlist = []
796 tersedlist = []
797 for st in allst:
797 for st in allst:
798 tersedict[st].sort()
798 tersedict[st].sort()
799 tersedlist.append(tersedict[st])
799 tersedlist.append(tersedict[st])
800
800
801 return scmutil.status(*tersedlist)
801 return scmutil.status(*tersedlist)
802
802
803
803
804 def _commentlines(raw):
804 def _commentlines(raw):
805 '''Surround lineswith a comment char and a new line'''
805 '''Surround lineswith a comment char and a new line'''
806 lines = raw.splitlines()
806 lines = raw.splitlines()
807 commentedlines = [b'# %s' % line for line in lines]
807 commentedlines = [b'# %s' % line for line in lines]
808 return b'\n'.join(commentedlines) + b'\n'
808 return b'\n'.join(commentedlines) + b'\n'
809
809
810
810
811 @attr.s(frozen=True)
811 @attr.s(frozen=True)
812 class morestatus(object):
812 class morestatus(object):
813 reporoot = attr.ib()
813 reporoot = attr.ib()
814 unfinishedop = attr.ib()
814 unfinishedop = attr.ib()
815 unfinishedmsg = attr.ib()
815 unfinishedmsg = attr.ib()
816 activemerge = attr.ib()
816 activemerge = attr.ib()
817 unresolvedpaths = attr.ib()
817 unresolvedpaths = attr.ib()
818 _formattedpaths = attr.ib(init=False, default=set())
818 _formattedpaths = attr.ib(init=False, default=set())
819 _label = b'status.morestatus'
819 _label = b'status.morestatus'
820
820
821 def formatfile(self, path, fm):
821 def formatfile(self, path, fm):
822 self._formattedpaths.add(path)
822 self._formattedpaths.add(path)
823 if self.activemerge and path in self.unresolvedpaths:
823 if self.activemerge and path in self.unresolvedpaths:
824 fm.data(unresolved=True)
824 fm.data(unresolved=True)
825
825
826 def formatfooter(self, fm):
826 def formatfooter(self, fm):
827 if self.unfinishedop or self.unfinishedmsg:
827 if self.unfinishedop or self.unfinishedmsg:
828 fm.startitem()
828 fm.startitem()
829 fm.data(itemtype=b'morestatus')
829 fm.data(itemtype=b'morestatus')
830
830
831 if self.unfinishedop:
831 if self.unfinishedop:
832 fm.data(unfinished=self.unfinishedop)
832 fm.data(unfinished=self.unfinishedop)
833 statemsg = (
833 statemsg = (
834 _(b'The repository is in an unfinished *%s* state.')
834 _(b'The repository is in an unfinished *%s* state.')
835 % self.unfinishedop
835 % self.unfinishedop
836 )
836 )
837 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
837 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
838 if self.unfinishedmsg:
838 if self.unfinishedmsg:
839 fm.data(unfinishedmsg=self.unfinishedmsg)
839 fm.data(unfinishedmsg=self.unfinishedmsg)
840
840
841 # May also start new data items.
841 # May also start new data items.
842 self._formatconflicts(fm)
842 self._formatconflicts(fm)
843
843
844 if self.unfinishedmsg:
844 if self.unfinishedmsg:
845 fm.plain(
845 fm.plain(
846 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
846 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
847 )
847 )
848
848
849 def _formatconflicts(self, fm):
849 def _formatconflicts(self, fm):
850 if not self.activemerge:
850 if not self.activemerge:
851 return
851 return
852
852
853 if self.unresolvedpaths:
853 if self.unresolvedpaths:
854 mergeliststr = b'\n'.join(
854 mergeliststr = b'\n'.join(
855 [
855 [
856 b' %s'
856 b' %s'
857 % util.pathto(self.reporoot, encoding.getcwd(), path)
857 % util.pathto(self.reporoot, encoding.getcwd(), path)
858 for path in self.unresolvedpaths
858 for path in self.unresolvedpaths
859 ]
859 ]
860 )
860 )
861 msg = (
861 msg = (
862 _(
862 _(
863 '''Unresolved merge conflicts:
863 '''Unresolved merge conflicts:
864
864
865 %s
865 %s
866
866
867 To mark files as resolved: hg resolve --mark FILE'''
867 To mark files as resolved: hg resolve --mark FILE'''
868 )
868 )
869 % mergeliststr
869 % mergeliststr
870 )
870 )
871
871
872 # If any paths with unresolved conflicts were not previously
872 # If any paths with unresolved conflicts were not previously
873 # formatted, output them now.
873 # formatted, output them now.
874 for f in self.unresolvedpaths:
874 for f in self.unresolvedpaths:
875 if f in self._formattedpaths:
875 if f in self._formattedpaths:
876 # Already output.
876 # Already output.
877 continue
877 continue
878 fm.startitem()
878 fm.startitem()
879 # We can't claim to know the status of the file - it may just
879 # We can't claim to know the status of the file - it may just
880 # have been in one of the states that were not requested for
880 # have been in one of the states that were not requested for
881 # display, so it could be anything.
881 # display, so it could be anything.
882 fm.data(itemtype=b'file', path=f, unresolved=True)
882 fm.data(itemtype=b'file', path=f, unresolved=True)
883
883
884 else:
884 else:
885 msg = _(b'No unresolved merge conflicts.')
885 msg = _(b'No unresolved merge conflicts.')
886
886
887 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
887 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
888
888
889
889
890 def readmorestatus(repo):
890 def readmorestatus(repo):
891 """Returns a morestatus object if the repo has unfinished state."""
891 """Returns a morestatus object if the repo has unfinished state."""
892 statetuple = statemod.getrepostate(repo)
892 statetuple = statemod.getrepostate(repo)
893 mergestate = mergemod.mergestate.read(repo)
893 mergestate = mergemod.mergestate.read(repo)
894 activemerge = mergestate.active()
894 activemerge = mergestate.active()
895 if not statetuple and not activemerge:
895 if not statetuple and not activemerge:
896 return None
896 return None
897
897
898 unfinishedop = unfinishedmsg = unresolved = None
898 unfinishedop = unfinishedmsg = unresolved = None
899 if statetuple:
899 if statetuple:
900 unfinishedop, unfinishedmsg = statetuple
900 unfinishedop, unfinishedmsg = statetuple
901 if activemerge:
901 if activemerge:
902 unresolved = sorted(mergestate.unresolved())
902 unresolved = sorted(mergestate.unresolved())
903 return morestatus(
903 return morestatus(
904 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
904 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
905 )
905 )
906
906
907
907
908 def findpossible(cmd, table, strict=False):
908 def findpossible(cmd, table, strict=False):
909 """
909 """
910 Return cmd -> (aliases, command table entry)
910 Return cmd -> (aliases, command table entry)
911 for each matching command.
911 for each matching command.
912 Return debug commands (or their aliases) only if no normal command matches.
912 Return debug commands (or their aliases) only if no normal command matches.
913 """
913 """
914 choice = {}
914 choice = {}
915 debugchoice = {}
915 debugchoice = {}
916
916
917 if cmd in table:
917 if cmd in table:
918 # short-circuit exact matches, "log" alias beats "log|history"
918 # short-circuit exact matches, "log" alias beats "log|history"
919 keys = [cmd]
919 keys = [cmd]
920 else:
920 else:
921 keys = table.keys()
921 keys = table.keys()
922
922
923 allcmds = []
923 allcmds = []
924 for e in keys:
924 for e in keys:
925 aliases = parsealiases(e)
925 aliases = parsealiases(e)
926 allcmds.extend(aliases)
926 allcmds.extend(aliases)
927 found = None
927 found = None
928 if cmd in aliases:
928 if cmd in aliases:
929 found = cmd
929 found = cmd
930 elif not strict:
930 elif not strict:
931 for a in aliases:
931 for a in aliases:
932 if a.startswith(cmd):
932 if a.startswith(cmd):
933 found = a
933 found = a
934 break
934 break
935 if found is not None:
935 if found is not None:
936 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
936 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
937 debugchoice[found] = (aliases, table[e])
937 debugchoice[found] = (aliases, table[e])
938 else:
938 else:
939 choice[found] = (aliases, table[e])
939 choice[found] = (aliases, table[e])
940
940
941 if not choice and debugchoice:
941 if not choice and debugchoice:
942 choice = debugchoice
942 choice = debugchoice
943
943
944 return choice, allcmds
944 return choice, allcmds
945
945
946
946
947 def findcmd(cmd, table, strict=True):
947 def findcmd(cmd, table, strict=True):
948 """Return (aliases, command table entry) for command string."""
948 """Return (aliases, command table entry) for command string."""
949 choice, allcmds = findpossible(cmd, table, strict)
949 choice, allcmds = findpossible(cmd, table, strict)
950
950
951 if cmd in choice:
951 if cmd in choice:
952 return choice[cmd]
952 return choice[cmd]
953
953
954 if len(choice) > 1:
954 if len(choice) > 1:
955 clist = sorted(choice)
955 clist = sorted(choice)
956 raise error.AmbiguousCommand(cmd, clist)
956 raise error.AmbiguousCommand(cmd, clist)
957
957
958 if choice:
958 if choice:
959 return list(choice.values())[0]
959 return list(choice.values())[0]
960
960
961 raise error.UnknownCommand(cmd, allcmds)
961 raise error.UnknownCommand(cmd, allcmds)
962
962
963
963
964 def changebranch(ui, repo, revs, label):
964 def changebranch(ui, repo, revs, label):
965 """ Change the branch name of given revs to label """
965 """ Change the branch name of given revs to label """
966
966
967 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
967 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
968 # abort in case of uncommitted merge or dirty wdir
968 # abort in case of uncommitted merge or dirty wdir
969 bailifchanged(repo)
969 bailifchanged(repo)
970 revs = scmutil.revrange(repo, revs)
970 revs = scmutil.revrange(repo, revs)
971 if not revs:
971 if not revs:
972 raise error.Abort(b"empty revision set")
972 raise error.Abort(b"empty revision set")
973 roots = repo.revs(b'roots(%ld)', revs)
973 roots = repo.revs(b'roots(%ld)', revs)
974 if len(roots) > 1:
974 if len(roots) > 1:
975 raise error.Abort(
975 raise error.Abort(
976 _(b"cannot change branch of non-linear revisions")
976 _(b"cannot change branch of non-linear revisions")
977 )
977 )
978 rewriteutil.precheck(repo, revs, b'change branch of')
978 rewriteutil.precheck(repo, revs, b'change branch of')
979
979
980 root = repo[roots.first()]
980 root = repo[roots.first()]
981 rpb = {parent.branch() for parent in root.parents()}
981 rpb = {parent.branch() for parent in root.parents()}
982 if label not in rpb and label in repo.branchmap():
982 if label not in rpb and label in repo.branchmap():
983 raise error.Abort(_(b"a branch of the same name already exists"))
983 raise error.Abort(_(b"a branch of the same name already exists"))
984
984
985 if repo.revs(b'obsolete() and %ld', revs):
985 if repo.revs(b'obsolete() and %ld', revs):
986 raise error.Abort(
986 raise error.Abort(
987 _(b"cannot change branch of a obsolete changeset")
987 _(b"cannot change branch of a obsolete changeset")
988 )
988 )
989
989
990 # make sure only topological heads
990 # make sure only topological heads
991 if repo.revs(b'heads(%ld) - head()', revs):
991 if repo.revs(b'heads(%ld) - head()', revs):
992 raise error.Abort(_(b"cannot change branch in middle of a stack"))
992 raise error.Abort(_(b"cannot change branch in middle of a stack"))
993
993
994 replacements = {}
994 replacements = {}
995 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
995 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
996 # mercurial.subrepo -> mercurial.cmdutil
996 # mercurial.subrepo -> mercurial.cmdutil
997 from . import context
997 from . import context
998
998
999 for rev in revs:
999 for rev in revs:
1000 ctx = repo[rev]
1000 ctx = repo[rev]
1001 oldbranch = ctx.branch()
1001 oldbranch = ctx.branch()
1002 # check if ctx has same branch
1002 # check if ctx has same branch
1003 if oldbranch == label:
1003 if oldbranch == label:
1004 continue
1004 continue
1005
1005
1006 def filectxfn(repo, newctx, path):
1006 def filectxfn(repo, newctx, path):
1007 try:
1007 try:
1008 return ctx[path]
1008 return ctx[path]
1009 except error.ManifestLookupError:
1009 except error.ManifestLookupError:
1010 return None
1010 return None
1011
1011
1012 ui.debug(
1012 ui.debug(
1013 b"changing branch of '%s' from '%s' to '%s'\n"
1013 b"changing branch of '%s' from '%s' to '%s'\n"
1014 % (hex(ctx.node()), oldbranch, label)
1014 % (hex(ctx.node()), oldbranch, label)
1015 )
1015 )
1016 extra = ctx.extra()
1016 extra = ctx.extra()
1017 extra[b'branch_change'] = hex(ctx.node())
1017 extra[b'branch_change'] = hex(ctx.node())
1018 # While changing branch of set of linear commits, make sure that
1018 # While changing branch of set of linear commits, make sure that
1019 # we base our commits on new parent rather than old parent which
1019 # we base our commits on new parent rather than old parent which
1020 # was obsoleted while changing the branch
1020 # was obsoleted while changing the branch
1021 p1 = ctx.p1().node()
1021 p1 = ctx.p1().node()
1022 p2 = ctx.p2().node()
1022 p2 = ctx.p2().node()
1023 if p1 in replacements:
1023 if p1 in replacements:
1024 p1 = replacements[p1][0]
1024 p1 = replacements[p1][0]
1025 if p2 in replacements:
1025 if p2 in replacements:
1026 p2 = replacements[p2][0]
1026 p2 = replacements[p2][0]
1027
1027
1028 mc = context.memctx(
1028 mc = context.memctx(
1029 repo,
1029 repo,
1030 (p1, p2),
1030 (p1, p2),
1031 ctx.description(),
1031 ctx.description(),
1032 ctx.files(),
1032 ctx.files(),
1033 filectxfn,
1033 filectxfn,
1034 user=ctx.user(),
1034 user=ctx.user(),
1035 date=ctx.date(),
1035 date=ctx.date(),
1036 extra=extra,
1036 extra=extra,
1037 branch=label,
1037 branch=label,
1038 )
1038 )
1039
1039
1040 newnode = repo.commitctx(mc)
1040 newnode = repo.commitctx(mc)
1041 replacements[ctx.node()] = (newnode,)
1041 replacements[ctx.node()] = (newnode,)
1042 ui.debug(b'new node id is %s\n' % hex(newnode))
1042 ui.debug(b'new node id is %s\n' % hex(newnode))
1043
1043
1044 # create obsmarkers and move bookmarks
1044 # create obsmarkers and move bookmarks
1045 scmutil.cleanupnodes(
1045 scmutil.cleanupnodes(
1046 repo, replacements, b'branch-change', fixphase=True
1046 repo, replacements, b'branch-change', fixphase=True
1047 )
1047 )
1048
1048
1049 # move the working copy too
1049 # move the working copy too
1050 wctx = repo[None]
1050 wctx = repo[None]
1051 # in-progress merge is a bit too complex for now.
1051 # in-progress merge is a bit too complex for now.
1052 if len(wctx.parents()) == 1:
1052 if len(wctx.parents()) == 1:
1053 newid = replacements.get(wctx.p1().node())
1053 newid = replacements.get(wctx.p1().node())
1054 if newid is not None:
1054 if newid is not None:
1055 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1055 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1056 # mercurial.cmdutil
1056 # mercurial.cmdutil
1057 from . import hg
1057 from . import hg
1058
1058
1059 hg.update(repo, newid[0], quietempty=True)
1059 hg.update(repo, newid[0], quietempty=True)
1060
1060
1061 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1061 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1062
1062
1063
1063
1064 def findrepo(p):
1064 def findrepo(p):
1065 while not os.path.isdir(os.path.join(p, b".hg")):
1065 while not os.path.isdir(os.path.join(p, b".hg")):
1066 oldp, p = p, os.path.dirname(p)
1066 oldp, p = p, os.path.dirname(p)
1067 if p == oldp:
1067 if p == oldp:
1068 return None
1068 return None
1069
1069
1070 return p
1070 return p
1071
1071
1072
1072
1073 def bailifchanged(repo, merge=True, hint=None):
1073 def bailifchanged(repo, merge=True, hint=None):
1074 """ enforce the precondition that working directory must be clean.
1074 """ enforce the precondition that working directory must be clean.
1075
1075
1076 'merge' can be set to false if a pending uncommitted merge should be
1076 'merge' can be set to false if a pending uncommitted merge should be
1077 ignored (such as when 'update --check' runs).
1077 ignored (such as when 'update --check' runs).
1078
1078
1079 'hint' is the usual hint given to Abort exception.
1079 'hint' is the usual hint given to Abort exception.
1080 """
1080 """
1081
1081
1082 if merge and repo.dirstate.p2() != nullid:
1082 if merge and repo.dirstate.p2() != nullid:
1083 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1083 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1084 st = repo.status()
1084 st = repo.status()
1085 if st.modified or st.added or st.removed or st.deleted:
1085 if st.modified or st.added or st.removed or st.deleted:
1086 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1086 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1087 ctx = repo[None]
1087 ctx = repo[None]
1088 for s in sorted(ctx.substate):
1088 for s in sorted(ctx.substate):
1089 ctx.sub(s).bailifchanged(hint=hint)
1089 ctx.sub(s).bailifchanged(hint=hint)
1090
1090
1091
1091
1092 def logmessage(ui, opts):
1092 def logmessage(ui, opts):
1093 """ get the log message according to -m and -l option """
1093 """ get the log message according to -m and -l option """
1094
1094
1095 check_at_most_one_arg(opts, b'message', b'logfile')
1095 check_at_most_one_arg(opts, b'message', b'logfile')
1096
1096
1097 message = opts.get(b'message')
1097 message = opts.get(b'message')
1098 logfile = opts.get(b'logfile')
1098 logfile = opts.get(b'logfile')
1099
1099
1100 if not message and logfile:
1100 if not message and logfile:
1101 try:
1101 try:
1102 if isstdiofilename(logfile):
1102 if isstdiofilename(logfile):
1103 message = ui.fin.read()
1103 message = ui.fin.read()
1104 else:
1104 else:
1105 message = b'\n'.join(util.readfile(logfile).splitlines())
1105 message = b'\n'.join(util.readfile(logfile).splitlines())
1106 except IOError as inst:
1106 except IOError as inst:
1107 raise error.Abort(
1107 raise error.Abort(
1108 _(b"can't read commit message '%s': %s")
1108 _(b"can't read commit message '%s': %s")
1109 % (logfile, encoding.strtolocal(inst.strerror))
1109 % (logfile, encoding.strtolocal(inst.strerror))
1110 )
1110 )
1111 return message
1111 return message
1112
1112
1113
1113
1114 def mergeeditform(ctxorbool, baseformname):
1114 def mergeeditform(ctxorbool, baseformname):
1115 """return appropriate editform name (referencing a committemplate)
1115 """return appropriate editform name (referencing a committemplate)
1116
1116
1117 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1117 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1118 merging is committed.
1118 merging is committed.
1119
1119
1120 This returns baseformname with '.merge' appended if it is a merge,
1120 This returns baseformname with '.merge' appended if it is a merge,
1121 otherwise '.normal' is appended.
1121 otherwise '.normal' is appended.
1122 """
1122 """
1123 if isinstance(ctxorbool, bool):
1123 if isinstance(ctxorbool, bool):
1124 if ctxorbool:
1124 if ctxorbool:
1125 return baseformname + b".merge"
1125 return baseformname + b".merge"
1126 elif len(ctxorbool.parents()) > 1:
1126 elif len(ctxorbool.parents()) > 1:
1127 return baseformname + b".merge"
1127 return baseformname + b".merge"
1128
1128
1129 return baseformname + b".normal"
1129 return baseformname + b".normal"
1130
1130
1131
1131
1132 def getcommiteditor(
1132 def getcommiteditor(
1133 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1133 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1134 ):
1134 ):
1135 """get appropriate commit message editor according to '--edit' option
1135 """get appropriate commit message editor according to '--edit' option
1136
1136
1137 'finishdesc' is a function to be called with edited commit message
1137 'finishdesc' is a function to be called with edited commit message
1138 (= 'description' of the new changeset) just after editing, but
1138 (= 'description' of the new changeset) just after editing, but
1139 before checking empty-ness. It should return actual text to be
1139 before checking empty-ness. It should return actual text to be
1140 stored into history. This allows to change description before
1140 stored into history. This allows to change description before
1141 storing.
1141 storing.
1142
1142
1143 'extramsg' is a extra message to be shown in the editor instead of
1143 'extramsg' is a extra message to be shown in the editor instead of
1144 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1144 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1145 is automatically added.
1145 is automatically added.
1146
1146
1147 'editform' is a dot-separated list of names, to distinguish
1147 'editform' is a dot-separated list of names, to distinguish
1148 the purpose of commit text editing.
1148 the purpose of commit text editing.
1149
1149
1150 'getcommiteditor' returns 'commitforceeditor' regardless of
1150 'getcommiteditor' returns 'commitforceeditor' regardless of
1151 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1151 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1152 they are specific for usage in MQ.
1152 they are specific for usage in MQ.
1153 """
1153 """
1154 if edit or finishdesc or extramsg:
1154 if edit or finishdesc or extramsg:
1155 return lambda r, c, s: commitforceeditor(
1155 return lambda r, c, s: commitforceeditor(
1156 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1156 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1157 )
1157 )
1158 elif editform:
1158 elif editform:
1159 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1159 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1160 else:
1160 else:
1161 return commiteditor
1161 return commiteditor
1162
1162
1163
1163
1164 def _escapecommandtemplate(tmpl):
1164 def _escapecommandtemplate(tmpl):
1165 parts = []
1165 parts = []
1166 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1166 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1167 if typ == b'string':
1167 if typ == b'string':
1168 parts.append(stringutil.escapestr(tmpl[start:end]))
1168 parts.append(stringutil.escapestr(tmpl[start:end]))
1169 else:
1169 else:
1170 parts.append(tmpl[start:end])
1170 parts.append(tmpl[start:end])
1171 return b''.join(parts)
1171 return b''.join(parts)
1172
1172
1173
1173
1174 def rendercommandtemplate(ui, tmpl, props):
1174 def rendercommandtemplate(ui, tmpl, props):
1175 r"""Expand a literal template 'tmpl' in a way suitable for command line
1175 r"""Expand a literal template 'tmpl' in a way suitable for command line
1176
1176
1177 '\' in outermost string is not taken as an escape character because it
1177 '\' in outermost string is not taken as an escape character because it
1178 is a directory separator on Windows.
1178 is a directory separator on Windows.
1179
1179
1180 >>> from . import ui as uimod
1180 >>> from . import ui as uimod
1181 >>> ui = uimod.ui()
1181 >>> ui = uimod.ui()
1182 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1182 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1183 'c:\\foo'
1183 'c:\\foo'
1184 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1184 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1185 'c:{path}'
1185 'c:{path}'
1186 """
1186 """
1187 if not tmpl:
1187 if not tmpl:
1188 return tmpl
1188 return tmpl
1189 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1189 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1190 return t.renderdefault(props)
1190 return t.renderdefault(props)
1191
1191
1192
1192
1193 def rendertemplate(ctx, tmpl, props=None):
1193 def rendertemplate(ctx, tmpl, props=None):
1194 """Expand a literal template 'tmpl' byte-string against one changeset
1194 """Expand a literal template 'tmpl' byte-string against one changeset
1195
1195
1196 Each props item must be a stringify-able value or a callable returning
1196 Each props item must be a stringify-able value or a callable returning
1197 such value, i.e. no bare list nor dict should be passed.
1197 such value, i.e. no bare list nor dict should be passed.
1198 """
1198 """
1199 repo = ctx.repo()
1199 repo = ctx.repo()
1200 tres = formatter.templateresources(repo.ui, repo)
1200 tres = formatter.templateresources(repo.ui, repo)
1201 t = formatter.maketemplater(
1201 t = formatter.maketemplater(
1202 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1202 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1203 )
1203 )
1204 mapping = {b'ctx': ctx}
1204 mapping = {b'ctx': ctx}
1205 if props:
1205 if props:
1206 mapping.update(props)
1206 mapping.update(props)
1207 return t.renderdefault(mapping)
1207 return t.renderdefault(mapping)
1208
1208
1209
1209
1210 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1210 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1211 r"""Convert old-style filename format string to template string
1211 r"""Convert old-style filename format string to template string
1212
1212
1213 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1213 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1214 'foo-{reporoot|basename}-{seqno}.patch'
1214 'foo-{reporoot|basename}-{seqno}.patch'
1215 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1215 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1216 '{rev}{tags % "{tag}"}{node}'
1216 '{rev}{tags % "{tag}"}{node}'
1217
1217
1218 '\' in outermost strings has to be escaped because it is a directory
1218 '\' in outermost strings has to be escaped because it is a directory
1219 separator on Windows:
1219 separator on Windows:
1220
1220
1221 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1221 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1222 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1222 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1223 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1223 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1224 '\\\\\\\\foo\\\\bar.patch'
1224 '\\\\\\\\foo\\\\bar.patch'
1225 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1225 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1226 '\\\\{tags % "{tag}"}'
1226 '\\\\{tags % "{tag}"}'
1227
1227
1228 but inner strings follow the template rules (i.e. '\' is taken as an
1228 but inner strings follow the template rules (i.e. '\' is taken as an
1229 escape character):
1229 escape character):
1230
1230
1231 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1231 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1232 '{"c:\\tmp"}'
1232 '{"c:\\tmp"}'
1233 """
1233 """
1234 expander = {
1234 expander = {
1235 b'H': b'{node}',
1235 b'H': b'{node}',
1236 b'R': b'{rev}',
1236 b'R': b'{rev}',
1237 b'h': b'{node|short}',
1237 b'h': b'{node|short}',
1238 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1238 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1239 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1239 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1240 b'%': b'%',
1240 b'%': b'%',
1241 b'b': b'{reporoot|basename}',
1241 b'b': b'{reporoot|basename}',
1242 }
1242 }
1243 if total is not None:
1243 if total is not None:
1244 expander[b'N'] = b'{total}'
1244 expander[b'N'] = b'{total}'
1245 if seqno is not None:
1245 if seqno is not None:
1246 expander[b'n'] = b'{seqno}'
1246 expander[b'n'] = b'{seqno}'
1247 if total is not None and seqno is not None:
1247 if total is not None and seqno is not None:
1248 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1248 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1249 if pathname is not None:
1249 if pathname is not None:
1250 expander[b's'] = b'{pathname|basename}'
1250 expander[b's'] = b'{pathname|basename}'
1251 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1251 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1252 expander[b'p'] = b'{pathname}'
1252 expander[b'p'] = b'{pathname}'
1253
1253
1254 newname = []
1254 newname = []
1255 for typ, start, end in templater.scantemplate(pat, raw=True):
1255 for typ, start, end in templater.scantemplate(pat, raw=True):
1256 if typ != b'string':
1256 if typ != b'string':
1257 newname.append(pat[start:end])
1257 newname.append(pat[start:end])
1258 continue
1258 continue
1259 i = start
1259 i = start
1260 while i < end:
1260 while i < end:
1261 n = pat.find(b'%', i, end)
1261 n = pat.find(b'%', i, end)
1262 if n < 0:
1262 if n < 0:
1263 newname.append(stringutil.escapestr(pat[i:end]))
1263 newname.append(stringutil.escapestr(pat[i:end]))
1264 break
1264 break
1265 newname.append(stringutil.escapestr(pat[i:n]))
1265 newname.append(stringutil.escapestr(pat[i:n]))
1266 if n + 2 > end:
1266 if n + 2 > end:
1267 raise error.Abort(
1267 raise error.Abort(
1268 _(b"incomplete format spec in output filename")
1268 _(b"incomplete format spec in output filename")
1269 )
1269 )
1270 c = pat[n + 1 : n + 2]
1270 c = pat[n + 1 : n + 2]
1271 i = n + 2
1271 i = n + 2
1272 try:
1272 try:
1273 newname.append(expander[c])
1273 newname.append(expander[c])
1274 except KeyError:
1274 except KeyError:
1275 raise error.Abort(
1275 raise error.Abort(
1276 _(b"invalid format spec '%%%s' in output filename") % c
1276 _(b"invalid format spec '%%%s' in output filename") % c
1277 )
1277 )
1278 return b''.join(newname)
1278 return b''.join(newname)
1279
1279
1280
1280
1281 def makefilename(ctx, pat, **props):
1281 def makefilename(ctx, pat, **props):
1282 if not pat:
1282 if not pat:
1283 return pat
1283 return pat
1284 tmpl = _buildfntemplate(pat, **props)
1284 tmpl = _buildfntemplate(pat, **props)
1285 # BUG: alias expansion shouldn't be made against template fragments
1285 # BUG: alias expansion shouldn't be made against template fragments
1286 # rewritten from %-format strings, but we have no easy way to partially
1286 # rewritten from %-format strings, but we have no easy way to partially
1287 # disable the expansion.
1287 # disable the expansion.
1288 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1288 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1289
1289
1290
1290
1291 def isstdiofilename(pat):
1291 def isstdiofilename(pat):
1292 """True if the given pat looks like a filename denoting stdin/stdout"""
1292 """True if the given pat looks like a filename denoting stdin/stdout"""
1293 return not pat or pat == b'-'
1293 return not pat or pat == b'-'
1294
1294
1295
1295
1296 class _unclosablefile(object):
1296 class _unclosablefile(object):
1297 def __init__(self, fp):
1297 def __init__(self, fp):
1298 self._fp = fp
1298 self._fp = fp
1299
1299
1300 def close(self):
1300 def close(self):
1301 pass
1301 pass
1302
1302
1303 def __iter__(self):
1303 def __iter__(self):
1304 return iter(self._fp)
1304 return iter(self._fp)
1305
1305
1306 def __getattr__(self, attr):
1306 def __getattr__(self, attr):
1307 return getattr(self._fp, attr)
1307 return getattr(self._fp, attr)
1308
1308
1309 def __enter__(self):
1309 def __enter__(self):
1310 return self
1310 return self
1311
1311
1312 def __exit__(self, exc_type, exc_value, exc_tb):
1312 def __exit__(self, exc_type, exc_value, exc_tb):
1313 pass
1313 pass
1314
1314
1315
1315
1316 def makefileobj(ctx, pat, mode=b'wb', **props):
1316 def makefileobj(ctx, pat, mode=b'wb', **props):
1317 writable = mode not in (b'r', b'rb')
1317 writable = mode not in (b'r', b'rb')
1318
1318
1319 if isstdiofilename(pat):
1319 if isstdiofilename(pat):
1320 repo = ctx.repo()
1320 repo = ctx.repo()
1321 if writable:
1321 if writable:
1322 fp = repo.ui.fout
1322 fp = repo.ui.fout
1323 else:
1323 else:
1324 fp = repo.ui.fin
1324 fp = repo.ui.fin
1325 return _unclosablefile(fp)
1325 return _unclosablefile(fp)
1326 fn = makefilename(ctx, pat, **props)
1326 fn = makefilename(ctx, pat, **props)
1327 return open(fn, mode)
1327 return open(fn, mode)
1328
1328
1329
1329
1330 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1330 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1331 """opens the changelog, manifest, a filelog or a given revlog"""
1331 """opens the changelog, manifest, a filelog or a given revlog"""
1332 cl = opts[b'changelog']
1332 cl = opts[b'changelog']
1333 mf = opts[b'manifest']
1333 mf = opts[b'manifest']
1334 dir = opts[b'dir']
1334 dir = opts[b'dir']
1335 msg = None
1335 msg = None
1336 if cl and mf:
1336 if cl and mf:
1337 msg = _(b'cannot specify --changelog and --manifest at the same time')
1337 msg = _(b'cannot specify --changelog and --manifest at the same time')
1338 elif cl and dir:
1338 elif cl and dir:
1339 msg = _(b'cannot specify --changelog and --dir at the same time')
1339 msg = _(b'cannot specify --changelog and --dir at the same time')
1340 elif cl or mf or dir:
1340 elif cl or mf or dir:
1341 if file_:
1341 if file_:
1342 msg = _(b'cannot specify filename with --changelog or --manifest')
1342 msg = _(b'cannot specify filename with --changelog or --manifest')
1343 elif not repo:
1343 elif not repo:
1344 msg = _(
1344 msg = _(
1345 b'cannot specify --changelog or --manifest or --dir '
1345 b'cannot specify --changelog or --manifest or --dir '
1346 b'without a repository'
1346 b'without a repository'
1347 )
1347 )
1348 if msg:
1348 if msg:
1349 raise error.Abort(msg)
1349 raise error.Abort(msg)
1350
1350
1351 r = None
1351 r = None
1352 if repo:
1352 if repo:
1353 if cl:
1353 if cl:
1354 r = repo.unfiltered().changelog
1354 r = repo.unfiltered().changelog
1355 elif dir:
1355 elif dir:
1356 if b'treemanifest' not in repo.requirements:
1356 if b'treemanifest' not in repo.requirements:
1357 raise error.Abort(
1357 raise error.Abort(
1358 _(
1358 _(
1359 b"--dir can only be used on repos with "
1359 b"--dir can only be used on repos with "
1360 b"treemanifest enabled"
1360 b"treemanifest enabled"
1361 )
1361 )
1362 )
1362 )
1363 if not dir.endswith(b'/'):
1363 if not dir.endswith(b'/'):
1364 dir = dir + b'/'
1364 dir = dir + b'/'
1365 dirlog = repo.manifestlog.getstorage(dir)
1365 dirlog = repo.manifestlog.getstorage(dir)
1366 if len(dirlog):
1366 if len(dirlog):
1367 r = dirlog
1367 r = dirlog
1368 elif mf:
1368 elif mf:
1369 r = repo.manifestlog.getstorage(b'')
1369 r = repo.manifestlog.getstorage(b'')
1370 elif file_:
1370 elif file_:
1371 filelog = repo.file(file_)
1371 filelog = repo.file(file_)
1372 if len(filelog):
1372 if len(filelog):
1373 r = filelog
1373 r = filelog
1374
1374
1375 # Not all storage may be revlogs. If requested, try to return an actual
1375 # Not all storage may be revlogs. If requested, try to return an actual
1376 # revlog instance.
1376 # revlog instance.
1377 if returnrevlog:
1377 if returnrevlog:
1378 if isinstance(r, revlog.revlog):
1378 if isinstance(r, revlog.revlog):
1379 pass
1379 pass
1380 elif util.safehasattr(r, b'_revlog'):
1380 elif util.safehasattr(r, b'_revlog'):
1381 r = r._revlog # pytype: disable=attribute-error
1381 r = r._revlog # pytype: disable=attribute-error
1382 elif r is not None:
1382 elif r is not None:
1383 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1383 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1384
1384
1385 if not r:
1385 if not r:
1386 if not returnrevlog:
1386 if not returnrevlog:
1387 raise error.Abort(_(b'cannot give path to non-revlog'))
1387 raise error.Abort(_(b'cannot give path to non-revlog'))
1388
1388
1389 if not file_:
1389 if not file_:
1390 raise error.CommandError(cmd, _(b'invalid arguments'))
1390 raise error.CommandError(cmd, _(b'invalid arguments'))
1391 if not os.path.isfile(file_):
1391 if not os.path.isfile(file_):
1392 raise error.Abort(_(b"revlog '%s' not found") % file_)
1392 raise error.Abort(_(b"revlog '%s' not found") % file_)
1393 r = revlog.revlog(
1393 r = revlog.revlog(
1394 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1394 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1395 )
1395 )
1396 return r
1396 return r
1397
1397
1398
1398
1399 def openrevlog(repo, cmd, file_, opts):
1399 def openrevlog(repo, cmd, file_, opts):
1400 """Obtain a revlog backing storage of an item.
1400 """Obtain a revlog backing storage of an item.
1401
1401
1402 This is similar to ``openstorage()`` except it always returns a revlog.
1402 This is similar to ``openstorage()`` except it always returns a revlog.
1403
1403
1404 In most cases, a caller cares about the main storage object - not the
1404 In most cases, a caller cares about the main storage object - not the
1405 revlog backing it. Therefore, this function should only be used by code
1405 revlog backing it. Therefore, this function should only be used by code
1406 that needs to examine low-level revlog implementation details. e.g. debug
1406 that needs to examine low-level revlog implementation details. e.g. debug
1407 commands.
1407 commands.
1408 """
1408 """
1409 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1409 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1410
1410
1411
1411
1412 def copy(ui, repo, pats, opts, rename=False):
1412 def copy(ui, repo, pats, opts, rename=False):
1413 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1413 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1414
1414
1415 # called with the repo lock held
1415 # called with the repo lock held
1416 #
1416 #
1417 # hgsep => pathname that uses "/" to separate directories
1417 # hgsep => pathname that uses "/" to separate directories
1418 # ossep => pathname that uses os.sep to separate directories
1418 # ossep => pathname that uses os.sep to separate directories
1419 cwd = repo.getcwd()
1419 cwd = repo.getcwd()
1420 targets = {}
1420 targets = {}
1421 forget = opts.get(b"forget")
1421 forget = opts.get(b"forget")
1422 after = opts.get(b"after")
1422 after = opts.get(b"after")
1423 dryrun = opts.get(b"dry_run")
1423 dryrun = opts.get(b"dry_run")
1424 ctx = repo[None]
1424 ctx = repo[None]
1425 pctx = ctx.p1()
1425 pctx = ctx.p1()
1426
1426
1427 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1427 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1428
1428
1429 if forget:
1429 if forget:
1430 match = scmutil.match(wctx, pats, opts)
1430 rev = opts[b'at_rev']
1431
1431 if rev:
1432 current_copies = wctx.p1copies()
1432 ctx = scmutil.revsingle(repo, rev)
1433 current_copies.update(wctx.p2copies())
1433 else:
1434
1434 ctx = repo[None]
1435 for f in wctx.walk(match):
1435 if ctx.rev() is None:
1436 new_ctx = ctx
1437 else:
1438 if len(ctx.parents()) > 1:
1439 raise error.Abort(_(b'cannot unmark copy in merge commit'))
1440 # avoid cycle context -> subrepo -> cmdutil
1441 from . import context
1442
1443 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1444 new_ctx = context.overlayworkingctx(repo)
1445 new_ctx.setbase(ctx.p1())
1446 mergemod.graft(repo, ctx, wctx=new_ctx)
1447
1448 match = scmutil.match(ctx, pats, opts)
1449
1450 current_copies = ctx.p1copies()
1451 current_copies.update(ctx.p2copies())
1452
1453 uipathfn = scmutil.getuipathfn(repo)
1454 for f in ctx.walk(match):
1436 if f in current_copies:
1455 if f in current_copies:
1437 wctx[f].markcopied(None)
1456 new_ctx[f].markcopied(None)
1438 elif match.exact(f):
1457 elif match.exact(f):
1439 ui.warn(
1458 ui.warn(
1440 _(
1459 _(
1441 b'%s: not unmarking as copy - file is not marked as copied\n'
1460 b'%s: not unmarking as copy - file is not marked as copied\n'
1442 )
1461 )
1443 % uipathfn(f)
1462 % uipathfn(f)
1444 )
1463 )
1464
1465 if ctx.rev() is not None:
1466 with repo.lock():
1467 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1468 new_node = mem_ctx.commit()
1469
1470 if repo.dirstate.p1() == ctx.node():
1471 with repo.dirstate.parentchange():
1472 scmutil.movedirstate(repo, repo[new_node])
1473 replacements = {ctx.node(): [new_node]}
1474 scmutil.cleanupnodes(
1475 repo, replacements, b'uncopy', fixphase=True
1476 )
1477
1445 return
1478 return
1446
1479
1480 if opts.get(b'at_rev'):
1481 raise error.Abort(_("--at-rev is only supported with --forget"))
1482
1447 def walkpat(pat):
1483 def walkpat(pat):
1448 srcs = []
1484 srcs = []
1449 m = scmutil.match(ctx, [pat], opts, globbed=True)
1485 m = scmutil.match(ctx, [pat], opts, globbed=True)
1450 for abs in ctx.walk(m):
1486 for abs in ctx.walk(m):
1451 rel = uipathfn(abs)
1487 rel = uipathfn(abs)
1452 exact = m.exact(abs)
1488 exact = m.exact(abs)
1453 if abs not in ctx:
1489 if abs not in ctx:
1454 if abs in pctx:
1490 if abs in pctx:
1455 if not after:
1491 if not after:
1456 if exact:
1492 if exact:
1457 ui.warn(
1493 ui.warn(
1458 _(
1494 _(
1459 b'%s: not copying - file has been marked '
1495 b'%s: not copying - file has been marked '
1460 b'for remove\n'
1496 b'for remove\n'
1461 )
1497 )
1462 % rel
1498 % rel
1463 )
1499 )
1464 continue
1500 continue
1465 else:
1501 else:
1466 if exact:
1502 if exact:
1467 ui.warn(
1503 ui.warn(
1468 _(b'%s: not copying - file is not managed\n') % rel
1504 _(b'%s: not copying - file is not managed\n') % rel
1469 )
1505 )
1470 continue
1506 continue
1471
1507
1472 # abs: hgsep
1508 # abs: hgsep
1473 # rel: ossep
1509 # rel: ossep
1474 srcs.append((abs, rel, exact))
1510 srcs.append((abs, rel, exact))
1475 return srcs
1511 return srcs
1476
1512
1477 # abssrc: hgsep
1513 # abssrc: hgsep
1478 # relsrc: ossep
1514 # relsrc: ossep
1479 # otarget: ossep
1515 # otarget: ossep
1480 def copyfile(abssrc, relsrc, otarget, exact):
1516 def copyfile(abssrc, relsrc, otarget, exact):
1481 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1517 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1482 if b'/' in abstarget:
1518 if b'/' in abstarget:
1483 # We cannot normalize abstarget itself, this would prevent
1519 # We cannot normalize abstarget itself, this would prevent
1484 # case only renames, like a => A.
1520 # case only renames, like a => A.
1485 abspath, absname = abstarget.rsplit(b'/', 1)
1521 abspath, absname = abstarget.rsplit(b'/', 1)
1486 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1522 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1487 reltarget = repo.pathto(abstarget, cwd)
1523 reltarget = repo.pathto(abstarget, cwd)
1488 target = repo.wjoin(abstarget)
1524 target = repo.wjoin(abstarget)
1489 src = repo.wjoin(abssrc)
1525 src = repo.wjoin(abssrc)
1490 state = repo.dirstate[abstarget]
1526 state = repo.dirstate[abstarget]
1491
1527
1492 scmutil.checkportable(ui, abstarget)
1528 scmutil.checkportable(ui, abstarget)
1493
1529
1494 # check for collisions
1530 # check for collisions
1495 prevsrc = targets.get(abstarget)
1531 prevsrc = targets.get(abstarget)
1496 if prevsrc is not None:
1532 if prevsrc is not None:
1497 ui.warn(
1533 ui.warn(
1498 _(b'%s: not overwriting - %s collides with %s\n')
1534 _(b'%s: not overwriting - %s collides with %s\n')
1499 % (
1535 % (
1500 reltarget,
1536 reltarget,
1501 repo.pathto(abssrc, cwd),
1537 repo.pathto(abssrc, cwd),
1502 repo.pathto(prevsrc, cwd),
1538 repo.pathto(prevsrc, cwd),
1503 )
1539 )
1504 )
1540 )
1505 return True # report a failure
1541 return True # report a failure
1506
1542
1507 # check for overwrites
1543 # check for overwrites
1508 exists = os.path.lexists(target)
1544 exists = os.path.lexists(target)
1509 samefile = False
1545 samefile = False
1510 if exists and abssrc != abstarget:
1546 if exists and abssrc != abstarget:
1511 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1547 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1512 abstarget
1548 abstarget
1513 ):
1549 ):
1514 if not rename:
1550 if not rename:
1515 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1551 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1516 return True # report a failure
1552 return True # report a failure
1517 exists = False
1553 exists = False
1518 samefile = True
1554 samefile = True
1519
1555
1520 if not after and exists or after and state in b'mn':
1556 if not after and exists or after and state in b'mn':
1521 if not opts[b'force']:
1557 if not opts[b'force']:
1522 if state in b'mn':
1558 if state in b'mn':
1523 msg = _(b'%s: not overwriting - file already committed\n')
1559 msg = _(b'%s: not overwriting - file already committed\n')
1524 if after:
1560 if after:
1525 flags = b'--after --force'
1561 flags = b'--after --force'
1526 else:
1562 else:
1527 flags = b'--force'
1563 flags = b'--force'
1528 if rename:
1564 if rename:
1529 hint = (
1565 hint = (
1530 _(
1566 _(
1531 b"('hg rename %s' to replace the file by "
1567 b"('hg rename %s' to replace the file by "
1532 b'recording a rename)\n'
1568 b'recording a rename)\n'
1533 )
1569 )
1534 % flags
1570 % flags
1535 )
1571 )
1536 else:
1572 else:
1537 hint = (
1573 hint = (
1538 _(
1574 _(
1539 b"('hg copy %s' to replace the file by "
1575 b"('hg copy %s' to replace the file by "
1540 b'recording a copy)\n'
1576 b'recording a copy)\n'
1541 )
1577 )
1542 % flags
1578 % flags
1543 )
1579 )
1544 else:
1580 else:
1545 msg = _(b'%s: not overwriting - file exists\n')
1581 msg = _(b'%s: not overwriting - file exists\n')
1546 if rename:
1582 if rename:
1547 hint = _(
1583 hint = _(
1548 b"('hg rename --after' to record the rename)\n"
1584 b"('hg rename --after' to record the rename)\n"
1549 )
1585 )
1550 else:
1586 else:
1551 hint = _(b"('hg copy --after' to record the copy)\n")
1587 hint = _(b"('hg copy --after' to record the copy)\n")
1552 ui.warn(msg % reltarget)
1588 ui.warn(msg % reltarget)
1553 ui.warn(hint)
1589 ui.warn(hint)
1554 return True # report a failure
1590 return True # report a failure
1555
1591
1556 if after:
1592 if after:
1557 if not exists:
1593 if not exists:
1558 if rename:
1594 if rename:
1559 ui.warn(
1595 ui.warn(
1560 _(b'%s: not recording move - %s does not exist\n')
1596 _(b'%s: not recording move - %s does not exist\n')
1561 % (relsrc, reltarget)
1597 % (relsrc, reltarget)
1562 )
1598 )
1563 else:
1599 else:
1564 ui.warn(
1600 ui.warn(
1565 _(b'%s: not recording copy - %s does not exist\n')
1601 _(b'%s: not recording copy - %s does not exist\n')
1566 % (relsrc, reltarget)
1602 % (relsrc, reltarget)
1567 )
1603 )
1568 return True # report a failure
1604 return True # report a failure
1569 elif not dryrun:
1605 elif not dryrun:
1570 try:
1606 try:
1571 if exists:
1607 if exists:
1572 os.unlink(target)
1608 os.unlink(target)
1573 targetdir = os.path.dirname(target) or b'.'
1609 targetdir = os.path.dirname(target) or b'.'
1574 if not os.path.isdir(targetdir):
1610 if not os.path.isdir(targetdir):
1575 os.makedirs(targetdir)
1611 os.makedirs(targetdir)
1576 if samefile:
1612 if samefile:
1577 tmp = target + b"~hgrename"
1613 tmp = target + b"~hgrename"
1578 os.rename(src, tmp)
1614 os.rename(src, tmp)
1579 os.rename(tmp, target)
1615 os.rename(tmp, target)
1580 else:
1616 else:
1581 # Preserve stat info on renames, not on copies; this matches
1617 # Preserve stat info on renames, not on copies; this matches
1582 # Linux CLI behavior.
1618 # Linux CLI behavior.
1583 util.copyfile(src, target, copystat=rename)
1619 util.copyfile(src, target, copystat=rename)
1584 srcexists = True
1620 srcexists = True
1585 except IOError as inst:
1621 except IOError as inst:
1586 if inst.errno == errno.ENOENT:
1622 if inst.errno == errno.ENOENT:
1587 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1623 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1588 srcexists = False
1624 srcexists = False
1589 else:
1625 else:
1590 ui.warn(
1626 ui.warn(
1591 _(b'%s: cannot copy - %s\n')
1627 _(b'%s: cannot copy - %s\n')
1592 % (relsrc, encoding.strtolocal(inst.strerror))
1628 % (relsrc, encoding.strtolocal(inst.strerror))
1593 )
1629 )
1594 return True # report a failure
1630 return True # report a failure
1595
1631
1596 if ui.verbose or not exact:
1632 if ui.verbose or not exact:
1597 if rename:
1633 if rename:
1598 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1634 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1599 else:
1635 else:
1600 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1636 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1601
1637
1602 targets[abstarget] = abssrc
1638 targets[abstarget] = abssrc
1603
1639
1604 # fix up dirstate
1640 # fix up dirstate
1605 scmutil.dirstatecopy(
1641 scmutil.dirstatecopy(
1606 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1642 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1607 )
1643 )
1608 if rename and not dryrun:
1644 if rename and not dryrun:
1609 if not after and srcexists and not samefile:
1645 if not after and srcexists and not samefile:
1610 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1646 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1611 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1647 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1612 ctx.forget([abssrc])
1648 ctx.forget([abssrc])
1613
1649
1614 # pat: ossep
1650 # pat: ossep
1615 # dest ossep
1651 # dest ossep
1616 # srcs: list of (hgsep, hgsep, ossep, bool)
1652 # srcs: list of (hgsep, hgsep, ossep, bool)
1617 # return: function that takes hgsep and returns ossep
1653 # return: function that takes hgsep and returns ossep
1618 def targetpathfn(pat, dest, srcs):
1654 def targetpathfn(pat, dest, srcs):
1619 if os.path.isdir(pat):
1655 if os.path.isdir(pat):
1620 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1656 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1621 abspfx = util.localpath(abspfx)
1657 abspfx = util.localpath(abspfx)
1622 if destdirexists:
1658 if destdirexists:
1623 striplen = len(os.path.split(abspfx)[0])
1659 striplen = len(os.path.split(abspfx)[0])
1624 else:
1660 else:
1625 striplen = len(abspfx)
1661 striplen = len(abspfx)
1626 if striplen:
1662 if striplen:
1627 striplen += len(pycompat.ossep)
1663 striplen += len(pycompat.ossep)
1628 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1664 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1629 elif destdirexists:
1665 elif destdirexists:
1630 res = lambda p: os.path.join(
1666 res = lambda p: os.path.join(
1631 dest, os.path.basename(util.localpath(p))
1667 dest, os.path.basename(util.localpath(p))
1632 )
1668 )
1633 else:
1669 else:
1634 res = lambda p: dest
1670 res = lambda p: dest
1635 return res
1671 return res
1636
1672
1637 # pat: ossep
1673 # pat: ossep
1638 # dest ossep
1674 # dest ossep
1639 # srcs: list of (hgsep, hgsep, ossep, bool)
1675 # srcs: list of (hgsep, hgsep, ossep, bool)
1640 # return: function that takes hgsep and returns ossep
1676 # return: function that takes hgsep and returns ossep
1641 def targetpathafterfn(pat, dest, srcs):
1677 def targetpathafterfn(pat, dest, srcs):
1642 if matchmod.patkind(pat):
1678 if matchmod.patkind(pat):
1643 # a mercurial pattern
1679 # a mercurial pattern
1644 res = lambda p: os.path.join(
1680 res = lambda p: os.path.join(
1645 dest, os.path.basename(util.localpath(p))
1681 dest, os.path.basename(util.localpath(p))
1646 )
1682 )
1647 else:
1683 else:
1648 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1684 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1649 if len(abspfx) < len(srcs[0][0]):
1685 if len(abspfx) < len(srcs[0][0]):
1650 # A directory. Either the target path contains the last
1686 # A directory. Either the target path contains the last
1651 # component of the source path or it does not.
1687 # component of the source path or it does not.
1652 def evalpath(striplen):
1688 def evalpath(striplen):
1653 score = 0
1689 score = 0
1654 for s in srcs:
1690 for s in srcs:
1655 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1691 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1656 if os.path.lexists(t):
1692 if os.path.lexists(t):
1657 score += 1
1693 score += 1
1658 return score
1694 return score
1659
1695
1660 abspfx = util.localpath(abspfx)
1696 abspfx = util.localpath(abspfx)
1661 striplen = len(abspfx)
1697 striplen = len(abspfx)
1662 if striplen:
1698 if striplen:
1663 striplen += len(pycompat.ossep)
1699 striplen += len(pycompat.ossep)
1664 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1700 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1665 score = evalpath(striplen)
1701 score = evalpath(striplen)
1666 striplen1 = len(os.path.split(abspfx)[0])
1702 striplen1 = len(os.path.split(abspfx)[0])
1667 if striplen1:
1703 if striplen1:
1668 striplen1 += len(pycompat.ossep)
1704 striplen1 += len(pycompat.ossep)
1669 if evalpath(striplen1) > score:
1705 if evalpath(striplen1) > score:
1670 striplen = striplen1
1706 striplen = striplen1
1671 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1707 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1672 else:
1708 else:
1673 # a file
1709 # a file
1674 if destdirexists:
1710 if destdirexists:
1675 res = lambda p: os.path.join(
1711 res = lambda p: os.path.join(
1676 dest, os.path.basename(util.localpath(p))
1712 dest, os.path.basename(util.localpath(p))
1677 )
1713 )
1678 else:
1714 else:
1679 res = lambda p: dest
1715 res = lambda p: dest
1680 return res
1716 return res
1681
1717
1682 pats = scmutil.expandpats(pats)
1718 pats = scmutil.expandpats(pats)
1683 if not pats:
1719 if not pats:
1684 raise error.Abort(_(b'no source or destination specified'))
1720 raise error.Abort(_(b'no source or destination specified'))
1685 if len(pats) == 1:
1721 if len(pats) == 1:
1686 raise error.Abort(_(b'no destination specified'))
1722 raise error.Abort(_(b'no destination specified'))
1687 dest = pats.pop()
1723 dest = pats.pop()
1688 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1724 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1689 if not destdirexists:
1725 if not destdirexists:
1690 if len(pats) > 1 or matchmod.patkind(pats[0]):
1726 if len(pats) > 1 or matchmod.patkind(pats[0]):
1691 raise error.Abort(
1727 raise error.Abort(
1692 _(
1728 _(
1693 b'with multiple sources, destination must be an '
1729 b'with multiple sources, destination must be an '
1694 b'existing directory'
1730 b'existing directory'
1695 )
1731 )
1696 )
1732 )
1697 if util.endswithsep(dest):
1733 if util.endswithsep(dest):
1698 raise error.Abort(_(b'destination %s is not a directory') % dest)
1734 raise error.Abort(_(b'destination %s is not a directory') % dest)
1699
1735
1700 tfn = targetpathfn
1736 tfn = targetpathfn
1701 if after:
1737 if after:
1702 tfn = targetpathafterfn
1738 tfn = targetpathafterfn
1703 copylist = []
1739 copylist = []
1704 for pat in pats:
1740 for pat in pats:
1705 srcs = walkpat(pat)
1741 srcs = walkpat(pat)
1706 if not srcs:
1742 if not srcs:
1707 continue
1743 continue
1708 copylist.append((tfn(pat, dest, srcs), srcs))
1744 copylist.append((tfn(pat, dest, srcs), srcs))
1709 if not copylist:
1745 if not copylist:
1710 raise error.Abort(_(b'no files to copy'))
1746 raise error.Abort(_(b'no files to copy'))
1711
1747
1712 errors = 0
1748 errors = 0
1713 for targetpath, srcs in copylist:
1749 for targetpath, srcs in copylist:
1714 for abssrc, relsrc, exact in srcs:
1750 for abssrc, relsrc, exact in srcs:
1715 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1751 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1716 errors += 1
1752 errors += 1
1717
1753
1718 return errors != 0
1754 return errors != 0
1719
1755
1720
1756
1721 ## facility to let extension process additional data into an import patch
1757 ## facility to let extension process additional data into an import patch
1722 # list of identifier to be executed in order
1758 # list of identifier to be executed in order
1723 extrapreimport = [] # run before commit
1759 extrapreimport = [] # run before commit
1724 extrapostimport = [] # run after commit
1760 extrapostimport = [] # run after commit
1725 # mapping from identifier to actual import function
1761 # mapping from identifier to actual import function
1726 #
1762 #
1727 # 'preimport' are run before the commit is made and are provided the following
1763 # 'preimport' are run before the commit is made and are provided the following
1728 # arguments:
1764 # arguments:
1729 # - repo: the localrepository instance,
1765 # - repo: the localrepository instance,
1730 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1766 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1731 # - extra: the future extra dictionary of the changeset, please mutate it,
1767 # - extra: the future extra dictionary of the changeset, please mutate it,
1732 # - opts: the import options.
1768 # - opts: the import options.
1733 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1769 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1734 # mutation of in memory commit and more. Feel free to rework the code to get
1770 # mutation of in memory commit and more. Feel free to rework the code to get
1735 # there.
1771 # there.
1736 extrapreimportmap = {}
1772 extrapreimportmap = {}
1737 # 'postimport' are run after the commit is made and are provided the following
1773 # 'postimport' are run after the commit is made and are provided the following
1738 # argument:
1774 # argument:
1739 # - ctx: the changectx created by import.
1775 # - ctx: the changectx created by import.
1740 extrapostimportmap = {}
1776 extrapostimportmap = {}
1741
1777
1742
1778
1743 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1779 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1744 """Utility function used by commands.import to import a single patch
1780 """Utility function used by commands.import to import a single patch
1745
1781
1746 This function is explicitly defined here to help the evolve extension to
1782 This function is explicitly defined here to help the evolve extension to
1747 wrap this part of the import logic.
1783 wrap this part of the import logic.
1748
1784
1749 The API is currently a bit ugly because it a simple code translation from
1785 The API is currently a bit ugly because it a simple code translation from
1750 the import command. Feel free to make it better.
1786 the import command. Feel free to make it better.
1751
1787
1752 :patchdata: a dictionary containing parsed patch data (such as from
1788 :patchdata: a dictionary containing parsed patch data (such as from
1753 ``patch.extract()``)
1789 ``patch.extract()``)
1754 :parents: nodes that will be parent of the created commit
1790 :parents: nodes that will be parent of the created commit
1755 :opts: the full dict of option passed to the import command
1791 :opts: the full dict of option passed to the import command
1756 :msgs: list to save commit message to.
1792 :msgs: list to save commit message to.
1757 (used in case we need to save it when failing)
1793 (used in case we need to save it when failing)
1758 :updatefunc: a function that update a repo to a given node
1794 :updatefunc: a function that update a repo to a given node
1759 updatefunc(<repo>, <node>)
1795 updatefunc(<repo>, <node>)
1760 """
1796 """
1761 # avoid cycle context -> subrepo -> cmdutil
1797 # avoid cycle context -> subrepo -> cmdutil
1762 from . import context
1798 from . import context
1763
1799
1764 tmpname = patchdata.get(b'filename')
1800 tmpname = patchdata.get(b'filename')
1765 message = patchdata.get(b'message')
1801 message = patchdata.get(b'message')
1766 user = opts.get(b'user') or patchdata.get(b'user')
1802 user = opts.get(b'user') or patchdata.get(b'user')
1767 date = opts.get(b'date') or patchdata.get(b'date')
1803 date = opts.get(b'date') or patchdata.get(b'date')
1768 branch = patchdata.get(b'branch')
1804 branch = patchdata.get(b'branch')
1769 nodeid = patchdata.get(b'nodeid')
1805 nodeid = patchdata.get(b'nodeid')
1770 p1 = patchdata.get(b'p1')
1806 p1 = patchdata.get(b'p1')
1771 p2 = patchdata.get(b'p2')
1807 p2 = patchdata.get(b'p2')
1772
1808
1773 nocommit = opts.get(b'no_commit')
1809 nocommit = opts.get(b'no_commit')
1774 importbranch = opts.get(b'import_branch')
1810 importbranch = opts.get(b'import_branch')
1775 update = not opts.get(b'bypass')
1811 update = not opts.get(b'bypass')
1776 strip = opts[b"strip"]
1812 strip = opts[b"strip"]
1777 prefix = opts[b"prefix"]
1813 prefix = opts[b"prefix"]
1778 sim = float(opts.get(b'similarity') or 0)
1814 sim = float(opts.get(b'similarity') or 0)
1779
1815
1780 if not tmpname:
1816 if not tmpname:
1781 return None, None, False
1817 return None, None, False
1782
1818
1783 rejects = False
1819 rejects = False
1784
1820
1785 cmdline_message = logmessage(ui, opts)
1821 cmdline_message = logmessage(ui, opts)
1786 if cmdline_message:
1822 if cmdline_message:
1787 # pickup the cmdline msg
1823 # pickup the cmdline msg
1788 message = cmdline_message
1824 message = cmdline_message
1789 elif message:
1825 elif message:
1790 # pickup the patch msg
1826 # pickup the patch msg
1791 message = message.strip()
1827 message = message.strip()
1792 else:
1828 else:
1793 # launch the editor
1829 # launch the editor
1794 message = None
1830 message = None
1795 ui.debug(b'message:\n%s\n' % (message or b''))
1831 ui.debug(b'message:\n%s\n' % (message or b''))
1796
1832
1797 if len(parents) == 1:
1833 if len(parents) == 1:
1798 parents.append(repo[nullid])
1834 parents.append(repo[nullid])
1799 if opts.get(b'exact'):
1835 if opts.get(b'exact'):
1800 if not nodeid or not p1:
1836 if not nodeid or not p1:
1801 raise error.Abort(_(b'not a Mercurial patch'))
1837 raise error.Abort(_(b'not a Mercurial patch'))
1802 p1 = repo[p1]
1838 p1 = repo[p1]
1803 p2 = repo[p2 or nullid]
1839 p2 = repo[p2 or nullid]
1804 elif p2:
1840 elif p2:
1805 try:
1841 try:
1806 p1 = repo[p1]
1842 p1 = repo[p1]
1807 p2 = repo[p2]
1843 p2 = repo[p2]
1808 # Without any options, consider p2 only if the
1844 # Without any options, consider p2 only if the
1809 # patch is being applied on top of the recorded
1845 # patch is being applied on top of the recorded
1810 # first parent.
1846 # first parent.
1811 if p1 != parents[0]:
1847 if p1 != parents[0]:
1812 p1 = parents[0]
1848 p1 = parents[0]
1813 p2 = repo[nullid]
1849 p2 = repo[nullid]
1814 except error.RepoError:
1850 except error.RepoError:
1815 p1, p2 = parents
1851 p1, p2 = parents
1816 if p2.node() == nullid:
1852 if p2.node() == nullid:
1817 ui.warn(
1853 ui.warn(
1818 _(
1854 _(
1819 b"warning: import the patch as a normal revision\n"
1855 b"warning: import the patch as a normal revision\n"
1820 b"(use --exact to import the patch as a merge)\n"
1856 b"(use --exact to import the patch as a merge)\n"
1821 )
1857 )
1822 )
1858 )
1823 else:
1859 else:
1824 p1, p2 = parents
1860 p1, p2 = parents
1825
1861
1826 n = None
1862 n = None
1827 if update:
1863 if update:
1828 if p1 != parents[0]:
1864 if p1 != parents[0]:
1829 updatefunc(repo, p1.node())
1865 updatefunc(repo, p1.node())
1830 if p2 != parents[1]:
1866 if p2 != parents[1]:
1831 repo.setparents(p1.node(), p2.node())
1867 repo.setparents(p1.node(), p2.node())
1832
1868
1833 if opts.get(b'exact') or importbranch:
1869 if opts.get(b'exact') or importbranch:
1834 repo.dirstate.setbranch(branch or b'default')
1870 repo.dirstate.setbranch(branch or b'default')
1835
1871
1836 partial = opts.get(b'partial', False)
1872 partial = opts.get(b'partial', False)
1837 files = set()
1873 files = set()
1838 try:
1874 try:
1839 patch.patch(
1875 patch.patch(
1840 ui,
1876 ui,
1841 repo,
1877 repo,
1842 tmpname,
1878 tmpname,
1843 strip=strip,
1879 strip=strip,
1844 prefix=prefix,
1880 prefix=prefix,
1845 files=files,
1881 files=files,
1846 eolmode=None,
1882 eolmode=None,
1847 similarity=sim / 100.0,
1883 similarity=sim / 100.0,
1848 )
1884 )
1849 except error.PatchError as e:
1885 except error.PatchError as e:
1850 if not partial:
1886 if not partial:
1851 raise error.Abort(pycompat.bytestr(e))
1887 raise error.Abort(pycompat.bytestr(e))
1852 if partial:
1888 if partial:
1853 rejects = True
1889 rejects = True
1854
1890
1855 files = list(files)
1891 files = list(files)
1856 if nocommit:
1892 if nocommit:
1857 if message:
1893 if message:
1858 msgs.append(message)
1894 msgs.append(message)
1859 else:
1895 else:
1860 if opts.get(b'exact') or p2:
1896 if opts.get(b'exact') or p2:
1861 # If you got here, you either use --force and know what
1897 # If you got here, you either use --force and know what
1862 # you are doing or used --exact or a merge patch while
1898 # you are doing or used --exact or a merge patch while
1863 # being updated to its first parent.
1899 # being updated to its first parent.
1864 m = None
1900 m = None
1865 else:
1901 else:
1866 m = scmutil.matchfiles(repo, files or [])
1902 m = scmutil.matchfiles(repo, files or [])
1867 editform = mergeeditform(repo[None], b'import.normal')
1903 editform = mergeeditform(repo[None], b'import.normal')
1868 if opts.get(b'exact'):
1904 if opts.get(b'exact'):
1869 editor = None
1905 editor = None
1870 else:
1906 else:
1871 editor = getcommiteditor(
1907 editor = getcommiteditor(
1872 editform=editform, **pycompat.strkwargs(opts)
1908 editform=editform, **pycompat.strkwargs(opts)
1873 )
1909 )
1874 extra = {}
1910 extra = {}
1875 for idfunc in extrapreimport:
1911 for idfunc in extrapreimport:
1876 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1912 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1877 overrides = {}
1913 overrides = {}
1878 if partial:
1914 if partial:
1879 overrides[(b'ui', b'allowemptycommit')] = True
1915 overrides[(b'ui', b'allowemptycommit')] = True
1880 if opts.get(b'secret'):
1916 if opts.get(b'secret'):
1881 overrides[(b'phases', b'new-commit')] = b'secret'
1917 overrides[(b'phases', b'new-commit')] = b'secret'
1882 with repo.ui.configoverride(overrides, b'import'):
1918 with repo.ui.configoverride(overrides, b'import'):
1883 n = repo.commit(
1919 n = repo.commit(
1884 message, user, date, match=m, editor=editor, extra=extra
1920 message, user, date, match=m, editor=editor, extra=extra
1885 )
1921 )
1886 for idfunc in extrapostimport:
1922 for idfunc in extrapostimport:
1887 extrapostimportmap[idfunc](repo[n])
1923 extrapostimportmap[idfunc](repo[n])
1888 else:
1924 else:
1889 if opts.get(b'exact') or importbranch:
1925 if opts.get(b'exact') or importbranch:
1890 branch = branch or b'default'
1926 branch = branch or b'default'
1891 else:
1927 else:
1892 branch = p1.branch()
1928 branch = p1.branch()
1893 store = patch.filestore()
1929 store = patch.filestore()
1894 try:
1930 try:
1895 files = set()
1931 files = set()
1896 try:
1932 try:
1897 patch.patchrepo(
1933 patch.patchrepo(
1898 ui,
1934 ui,
1899 repo,
1935 repo,
1900 p1,
1936 p1,
1901 store,
1937 store,
1902 tmpname,
1938 tmpname,
1903 strip,
1939 strip,
1904 prefix,
1940 prefix,
1905 files,
1941 files,
1906 eolmode=None,
1942 eolmode=None,
1907 )
1943 )
1908 except error.PatchError as e:
1944 except error.PatchError as e:
1909 raise error.Abort(stringutil.forcebytestr(e))
1945 raise error.Abort(stringutil.forcebytestr(e))
1910 if opts.get(b'exact'):
1946 if opts.get(b'exact'):
1911 editor = None
1947 editor = None
1912 else:
1948 else:
1913 editor = getcommiteditor(editform=b'import.bypass')
1949 editor = getcommiteditor(editform=b'import.bypass')
1914 memctx = context.memctx(
1950 memctx = context.memctx(
1915 repo,
1951 repo,
1916 (p1.node(), p2.node()),
1952 (p1.node(), p2.node()),
1917 message,
1953 message,
1918 files=files,
1954 files=files,
1919 filectxfn=store,
1955 filectxfn=store,
1920 user=user,
1956 user=user,
1921 date=date,
1957 date=date,
1922 branch=branch,
1958 branch=branch,
1923 editor=editor,
1959 editor=editor,
1924 )
1960 )
1925 n = memctx.commit()
1961 n = memctx.commit()
1926 finally:
1962 finally:
1927 store.close()
1963 store.close()
1928 if opts.get(b'exact') and nocommit:
1964 if opts.get(b'exact') and nocommit:
1929 # --exact with --no-commit is still useful in that it does merge
1965 # --exact with --no-commit is still useful in that it does merge
1930 # and branch bits
1966 # and branch bits
1931 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
1967 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
1932 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
1968 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
1933 raise error.Abort(_(b'patch is damaged or loses information'))
1969 raise error.Abort(_(b'patch is damaged or loses information'))
1934 msg = _(b'applied to working directory')
1970 msg = _(b'applied to working directory')
1935 if n:
1971 if n:
1936 # i18n: refers to a short changeset id
1972 # i18n: refers to a short changeset id
1937 msg = _(b'created %s') % short(n)
1973 msg = _(b'created %s') % short(n)
1938 return msg, n, rejects
1974 return msg, n, rejects
1939
1975
1940
1976
1941 # facility to let extensions include additional data in an exported patch
1977 # facility to let extensions include additional data in an exported patch
1942 # list of identifiers to be executed in order
1978 # list of identifiers to be executed in order
1943 extraexport = []
1979 extraexport = []
1944 # mapping from identifier to actual export function
1980 # mapping from identifier to actual export function
1945 # function as to return a string to be added to the header or None
1981 # function as to return a string to be added to the header or None
1946 # it is given two arguments (sequencenumber, changectx)
1982 # it is given two arguments (sequencenumber, changectx)
1947 extraexportmap = {}
1983 extraexportmap = {}
1948
1984
1949
1985
1950 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1986 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1951 node = scmutil.binnode(ctx)
1987 node = scmutil.binnode(ctx)
1952 parents = [p.node() for p in ctx.parents() if p]
1988 parents = [p.node() for p in ctx.parents() if p]
1953 branch = ctx.branch()
1989 branch = ctx.branch()
1954 if switch_parent:
1990 if switch_parent:
1955 parents.reverse()
1991 parents.reverse()
1956
1992
1957 if parents:
1993 if parents:
1958 prev = parents[0]
1994 prev = parents[0]
1959 else:
1995 else:
1960 prev = nullid
1996 prev = nullid
1961
1997
1962 fm.context(ctx=ctx)
1998 fm.context(ctx=ctx)
1963 fm.plain(b'# HG changeset patch\n')
1999 fm.plain(b'# HG changeset patch\n')
1964 fm.write(b'user', b'# User %s\n', ctx.user())
2000 fm.write(b'user', b'# User %s\n', ctx.user())
1965 fm.plain(b'# Date %d %d\n' % ctx.date())
2001 fm.plain(b'# Date %d %d\n' % ctx.date())
1966 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2002 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
1967 fm.condwrite(
2003 fm.condwrite(
1968 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2004 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
1969 )
2005 )
1970 fm.write(b'node', b'# Node ID %s\n', hex(node))
2006 fm.write(b'node', b'# Node ID %s\n', hex(node))
1971 fm.plain(b'# Parent %s\n' % hex(prev))
2007 fm.plain(b'# Parent %s\n' % hex(prev))
1972 if len(parents) > 1:
2008 if len(parents) > 1:
1973 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2009 fm.plain(b'# Parent %s\n' % hex(parents[1]))
1974 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2010 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
1975
2011
1976 # TODO: redesign extraexportmap function to support formatter
2012 # TODO: redesign extraexportmap function to support formatter
1977 for headerid in extraexport:
2013 for headerid in extraexport:
1978 header = extraexportmap[headerid](seqno, ctx)
2014 header = extraexportmap[headerid](seqno, ctx)
1979 if header is not None:
2015 if header is not None:
1980 fm.plain(b'# %s\n' % header)
2016 fm.plain(b'# %s\n' % header)
1981
2017
1982 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2018 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
1983 fm.plain(b'\n')
2019 fm.plain(b'\n')
1984
2020
1985 if fm.isplain():
2021 if fm.isplain():
1986 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2022 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1987 for chunk, label in chunkiter:
2023 for chunk, label in chunkiter:
1988 fm.plain(chunk, label=label)
2024 fm.plain(chunk, label=label)
1989 else:
2025 else:
1990 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2026 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1991 # TODO: make it structured?
2027 # TODO: make it structured?
1992 fm.data(diff=b''.join(chunkiter))
2028 fm.data(diff=b''.join(chunkiter))
1993
2029
1994
2030
1995 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2031 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1996 """Export changesets to stdout or a single file"""
2032 """Export changesets to stdout or a single file"""
1997 for seqno, rev in enumerate(revs, 1):
2033 for seqno, rev in enumerate(revs, 1):
1998 ctx = repo[rev]
2034 ctx = repo[rev]
1999 if not dest.startswith(b'<'):
2035 if not dest.startswith(b'<'):
2000 repo.ui.note(b"%s\n" % dest)
2036 repo.ui.note(b"%s\n" % dest)
2001 fm.startitem()
2037 fm.startitem()
2002 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2038 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2003
2039
2004
2040
2005 def _exportfntemplate(
2041 def _exportfntemplate(
2006 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2042 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2007 ):
2043 ):
2008 """Export changesets to possibly multiple files"""
2044 """Export changesets to possibly multiple files"""
2009 total = len(revs)
2045 total = len(revs)
2010 revwidth = max(len(str(rev)) for rev in revs)
2046 revwidth = max(len(str(rev)) for rev in revs)
2011 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2047 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2012
2048
2013 for seqno, rev in enumerate(revs, 1):
2049 for seqno, rev in enumerate(revs, 1):
2014 ctx = repo[rev]
2050 ctx = repo[rev]
2015 dest = makefilename(
2051 dest = makefilename(
2016 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2052 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2017 )
2053 )
2018 filemap.setdefault(dest, []).append((seqno, rev))
2054 filemap.setdefault(dest, []).append((seqno, rev))
2019
2055
2020 for dest in filemap:
2056 for dest in filemap:
2021 with formatter.maybereopen(basefm, dest) as fm:
2057 with formatter.maybereopen(basefm, dest) as fm:
2022 repo.ui.note(b"%s\n" % dest)
2058 repo.ui.note(b"%s\n" % dest)
2023 for seqno, rev in filemap[dest]:
2059 for seqno, rev in filemap[dest]:
2024 fm.startitem()
2060 fm.startitem()
2025 ctx = repo[rev]
2061 ctx = repo[rev]
2026 _exportsingle(
2062 _exportsingle(
2027 repo, ctx, fm, match, switch_parent, seqno, diffopts
2063 repo, ctx, fm, match, switch_parent, seqno, diffopts
2028 )
2064 )
2029
2065
2030
2066
2031 def _prefetchchangedfiles(repo, revs, match):
2067 def _prefetchchangedfiles(repo, revs, match):
2032 allfiles = set()
2068 allfiles = set()
2033 for rev in revs:
2069 for rev in revs:
2034 for file in repo[rev].files():
2070 for file in repo[rev].files():
2035 if not match or match(file):
2071 if not match or match(file):
2036 allfiles.add(file)
2072 allfiles.add(file)
2037 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
2073 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
2038
2074
2039
2075
2040 def export(
2076 def export(
2041 repo,
2077 repo,
2042 revs,
2078 revs,
2043 basefm,
2079 basefm,
2044 fntemplate=b'hg-%h.patch',
2080 fntemplate=b'hg-%h.patch',
2045 switch_parent=False,
2081 switch_parent=False,
2046 opts=None,
2082 opts=None,
2047 match=None,
2083 match=None,
2048 ):
2084 ):
2049 '''export changesets as hg patches
2085 '''export changesets as hg patches
2050
2086
2051 Args:
2087 Args:
2052 repo: The repository from which we're exporting revisions.
2088 repo: The repository from which we're exporting revisions.
2053 revs: A list of revisions to export as revision numbers.
2089 revs: A list of revisions to export as revision numbers.
2054 basefm: A formatter to which patches should be written.
2090 basefm: A formatter to which patches should be written.
2055 fntemplate: An optional string to use for generating patch file names.
2091 fntemplate: An optional string to use for generating patch file names.
2056 switch_parent: If True, show diffs against second parent when not nullid.
2092 switch_parent: If True, show diffs against second parent when not nullid.
2057 Default is false, which always shows diff against p1.
2093 Default is false, which always shows diff against p1.
2058 opts: diff options to use for generating the patch.
2094 opts: diff options to use for generating the patch.
2059 match: If specified, only export changes to files matching this matcher.
2095 match: If specified, only export changes to files matching this matcher.
2060
2096
2061 Returns:
2097 Returns:
2062 Nothing.
2098 Nothing.
2063
2099
2064 Side Effect:
2100 Side Effect:
2065 "HG Changeset Patch" data is emitted to one of the following
2101 "HG Changeset Patch" data is emitted to one of the following
2066 destinations:
2102 destinations:
2067 fntemplate specified: Each rev is written to a unique file named using
2103 fntemplate specified: Each rev is written to a unique file named using
2068 the given template.
2104 the given template.
2069 Otherwise: All revs will be written to basefm.
2105 Otherwise: All revs will be written to basefm.
2070 '''
2106 '''
2071 _prefetchchangedfiles(repo, revs, match)
2107 _prefetchchangedfiles(repo, revs, match)
2072
2108
2073 if not fntemplate:
2109 if not fntemplate:
2074 _exportfile(
2110 _exportfile(
2075 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2111 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2076 )
2112 )
2077 else:
2113 else:
2078 _exportfntemplate(
2114 _exportfntemplate(
2079 repo, revs, basefm, fntemplate, switch_parent, opts, match
2115 repo, revs, basefm, fntemplate, switch_parent, opts, match
2080 )
2116 )
2081
2117
2082
2118
2083 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2119 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2084 """Export changesets to the given file stream"""
2120 """Export changesets to the given file stream"""
2085 _prefetchchangedfiles(repo, revs, match)
2121 _prefetchchangedfiles(repo, revs, match)
2086
2122
2087 dest = getattr(fp, 'name', b'<unnamed>')
2123 dest = getattr(fp, 'name', b'<unnamed>')
2088 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2124 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2089 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2125 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2090
2126
2091
2127
2092 def showmarker(fm, marker, index=None):
2128 def showmarker(fm, marker, index=None):
2093 """utility function to display obsolescence marker in a readable way
2129 """utility function to display obsolescence marker in a readable way
2094
2130
2095 To be used by debug function."""
2131 To be used by debug function."""
2096 if index is not None:
2132 if index is not None:
2097 fm.write(b'index', b'%i ', index)
2133 fm.write(b'index', b'%i ', index)
2098 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2134 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2099 succs = marker.succnodes()
2135 succs = marker.succnodes()
2100 fm.condwrite(
2136 fm.condwrite(
2101 succs,
2137 succs,
2102 b'succnodes',
2138 b'succnodes',
2103 b'%s ',
2139 b'%s ',
2104 fm.formatlist(map(hex, succs), name=b'node'),
2140 fm.formatlist(map(hex, succs), name=b'node'),
2105 )
2141 )
2106 fm.write(b'flag', b'%X ', marker.flags())
2142 fm.write(b'flag', b'%X ', marker.flags())
2107 parents = marker.parentnodes()
2143 parents = marker.parentnodes()
2108 if parents is not None:
2144 if parents is not None:
2109 fm.write(
2145 fm.write(
2110 b'parentnodes',
2146 b'parentnodes',
2111 b'{%s} ',
2147 b'{%s} ',
2112 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2148 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2113 )
2149 )
2114 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2150 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2115 meta = marker.metadata().copy()
2151 meta = marker.metadata().copy()
2116 meta.pop(b'date', None)
2152 meta.pop(b'date', None)
2117 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2153 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2118 fm.write(
2154 fm.write(
2119 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2155 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2120 )
2156 )
2121 fm.plain(b'\n')
2157 fm.plain(b'\n')
2122
2158
2123
2159
2124 def finddate(ui, repo, date):
2160 def finddate(ui, repo, date):
2125 """Find the tipmost changeset that matches the given date spec"""
2161 """Find the tipmost changeset that matches the given date spec"""
2126
2162
2127 df = dateutil.matchdate(date)
2163 df = dateutil.matchdate(date)
2128 m = scmutil.matchall(repo)
2164 m = scmutil.matchall(repo)
2129 results = {}
2165 results = {}
2130
2166
2131 def prep(ctx, fns):
2167 def prep(ctx, fns):
2132 d = ctx.date()
2168 d = ctx.date()
2133 if df(d[0]):
2169 if df(d[0]):
2134 results[ctx.rev()] = d
2170 results[ctx.rev()] = d
2135
2171
2136 for ctx in walkchangerevs(repo, m, {b'rev': None}, prep):
2172 for ctx in walkchangerevs(repo, m, {b'rev': None}, prep):
2137 rev = ctx.rev()
2173 rev = ctx.rev()
2138 if rev in results:
2174 if rev in results:
2139 ui.status(
2175 ui.status(
2140 _(b"found revision %d from %s\n")
2176 _(b"found revision %d from %s\n")
2141 % (rev, dateutil.datestr(results[rev]))
2177 % (rev, dateutil.datestr(results[rev]))
2142 )
2178 )
2143 return b'%d' % rev
2179 return b'%d' % rev
2144
2180
2145 raise error.Abort(_(b"revision matching date not found"))
2181 raise error.Abort(_(b"revision matching date not found"))
2146
2182
2147
2183
2148 def increasingwindows(windowsize=8, sizelimit=512):
2184 def increasingwindows(windowsize=8, sizelimit=512):
2149 while True:
2185 while True:
2150 yield windowsize
2186 yield windowsize
2151 if windowsize < sizelimit:
2187 if windowsize < sizelimit:
2152 windowsize *= 2
2188 windowsize *= 2
2153
2189
2154
2190
2155 def _walkrevs(repo, opts):
2191 def _walkrevs(repo, opts):
2156 # Default --rev value depends on --follow but --follow behavior
2192 # Default --rev value depends on --follow but --follow behavior
2157 # depends on revisions resolved from --rev...
2193 # depends on revisions resolved from --rev...
2158 follow = opts.get(b'follow') or opts.get(b'follow_first')
2194 follow = opts.get(b'follow') or opts.get(b'follow_first')
2159 if opts.get(b'rev'):
2195 if opts.get(b'rev'):
2160 revs = scmutil.revrange(repo, opts[b'rev'])
2196 revs = scmutil.revrange(repo, opts[b'rev'])
2161 elif follow and repo.dirstate.p1() == nullid:
2197 elif follow and repo.dirstate.p1() == nullid:
2162 revs = smartset.baseset()
2198 revs = smartset.baseset()
2163 elif follow:
2199 elif follow:
2164 revs = repo.revs(b'reverse(:.)')
2200 revs = repo.revs(b'reverse(:.)')
2165 else:
2201 else:
2166 revs = smartset.spanset(repo)
2202 revs = smartset.spanset(repo)
2167 revs.reverse()
2203 revs.reverse()
2168 return revs
2204 return revs
2169
2205
2170
2206
2171 class FileWalkError(Exception):
2207 class FileWalkError(Exception):
2172 pass
2208 pass
2173
2209
2174
2210
2175 def walkfilerevs(repo, match, follow, revs, fncache):
2211 def walkfilerevs(repo, match, follow, revs, fncache):
2176 '''Walks the file history for the matched files.
2212 '''Walks the file history for the matched files.
2177
2213
2178 Returns the changeset revs that are involved in the file history.
2214 Returns the changeset revs that are involved in the file history.
2179
2215
2180 Throws FileWalkError if the file history can't be walked using
2216 Throws FileWalkError if the file history can't be walked using
2181 filelogs alone.
2217 filelogs alone.
2182 '''
2218 '''
2183 wanted = set()
2219 wanted = set()
2184 copies = []
2220 copies = []
2185 minrev, maxrev = min(revs), max(revs)
2221 minrev, maxrev = min(revs), max(revs)
2186
2222
2187 def filerevs(filelog, last):
2223 def filerevs(filelog, last):
2188 """
2224 """
2189 Only files, no patterns. Check the history of each file.
2225 Only files, no patterns. Check the history of each file.
2190
2226
2191 Examines filelog entries within minrev, maxrev linkrev range
2227 Examines filelog entries within minrev, maxrev linkrev range
2192 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2228 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2193 tuples in backwards order
2229 tuples in backwards order
2194 """
2230 """
2195 cl_count = len(repo)
2231 cl_count = len(repo)
2196 revs = []
2232 revs = []
2197 for j in pycompat.xrange(0, last + 1):
2233 for j in pycompat.xrange(0, last + 1):
2198 linkrev = filelog.linkrev(j)
2234 linkrev = filelog.linkrev(j)
2199 if linkrev < minrev:
2235 if linkrev < minrev:
2200 continue
2236 continue
2201 # only yield rev for which we have the changelog, it can
2237 # only yield rev for which we have the changelog, it can
2202 # happen while doing "hg log" during a pull or commit
2238 # happen while doing "hg log" during a pull or commit
2203 if linkrev >= cl_count:
2239 if linkrev >= cl_count:
2204 break
2240 break
2205
2241
2206 parentlinkrevs = []
2242 parentlinkrevs = []
2207 for p in filelog.parentrevs(j):
2243 for p in filelog.parentrevs(j):
2208 if p != nullrev:
2244 if p != nullrev:
2209 parentlinkrevs.append(filelog.linkrev(p))
2245 parentlinkrevs.append(filelog.linkrev(p))
2210 n = filelog.node(j)
2246 n = filelog.node(j)
2211 revs.append(
2247 revs.append(
2212 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2248 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2213 )
2249 )
2214
2250
2215 return reversed(revs)
2251 return reversed(revs)
2216
2252
2217 def iterfiles():
2253 def iterfiles():
2218 pctx = repo[b'.']
2254 pctx = repo[b'.']
2219 for filename in match.files():
2255 for filename in match.files():
2220 if follow:
2256 if follow:
2221 if filename not in pctx:
2257 if filename not in pctx:
2222 raise error.Abort(
2258 raise error.Abort(
2223 _(
2259 _(
2224 b'cannot follow file not in parent '
2260 b'cannot follow file not in parent '
2225 b'revision: "%s"'
2261 b'revision: "%s"'
2226 )
2262 )
2227 % filename
2263 % filename
2228 )
2264 )
2229 yield filename, pctx[filename].filenode()
2265 yield filename, pctx[filename].filenode()
2230 else:
2266 else:
2231 yield filename, None
2267 yield filename, None
2232 for filename_node in copies:
2268 for filename_node in copies:
2233 yield filename_node
2269 yield filename_node
2234
2270
2235 for file_, node in iterfiles():
2271 for file_, node in iterfiles():
2236 filelog = repo.file(file_)
2272 filelog = repo.file(file_)
2237 if not len(filelog):
2273 if not len(filelog):
2238 if node is None:
2274 if node is None:
2239 # A zero count may be a directory or deleted file, so
2275 # A zero count may be a directory or deleted file, so
2240 # try to find matching entries on the slow path.
2276 # try to find matching entries on the slow path.
2241 if follow:
2277 if follow:
2242 raise error.Abort(
2278 raise error.Abort(
2243 _(b'cannot follow nonexistent file: "%s"') % file_
2279 _(b'cannot follow nonexistent file: "%s"') % file_
2244 )
2280 )
2245 raise FileWalkError(b"Cannot walk via filelog")
2281 raise FileWalkError(b"Cannot walk via filelog")
2246 else:
2282 else:
2247 continue
2283 continue
2248
2284
2249 if node is None:
2285 if node is None:
2250 last = len(filelog) - 1
2286 last = len(filelog) - 1
2251 else:
2287 else:
2252 last = filelog.rev(node)
2288 last = filelog.rev(node)
2253
2289
2254 # keep track of all ancestors of the file
2290 # keep track of all ancestors of the file
2255 ancestors = {filelog.linkrev(last)}
2291 ancestors = {filelog.linkrev(last)}
2256
2292
2257 # iterate from latest to oldest revision
2293 # iterate from latest to oldest revision
2258 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2294 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2259 if not follow:
2295 if not follow:
2260 if rev > maxrev:
2296 if rev > maxrev:
2261 continue
2297 continue
2262 else:
2298 else:
2263 # Note that last might not be the first interesting
2299 # Note that last might not be the first interesting
2264 # rev to us:
2300 # rev to us:
2265 # if the file has been changed after maxrev, we'll
2301 # if the file has been changed after maxrev, we'll
2266 # have linkrev(last) > maxrev, and we still need
2302 # have linkrev(last) > maxrev, and we still need
2267 # to explore the file graph
2303 # to explore the file graph
2268 if rev not in ancestors:
2304 if rev not in ancestors:
2269 continue
2305 continue
2270 # XXX insert 1327 fix here
2306 # XXX insert 1327 fix here
2271 if flparentlinkrevs:
2307 if flparentlinkrevs:
2272 ancestors.update(flparentlinkrevs)
2308 ancestors.update(flparentlinkrevs)
2273
2309
2274 fncache.setdefault(rev, []).append(file_)
2310 fncache.setdefault(rev, []).append(file_)
2275 wanted.add(rev)
2311 wanted.add(rev)
2276 if copied:
2312 if copied:
2277 copies.append(copied)
2313 copies.append(copied)
2278
2314
2279 return wanted
2315 return wanted
2280
2316
2281
2317
2282 class _followfilter(object):
2318 class _followfilter(object):
2283 def __init__(self, repo, onlyfirst=False):
2319 def __init__(self, repo, onlyfirst=False):
2284 self.repo = repo
2320 self.repo = repo
2285 self.startrev = nullrev
2321 self.startrev = nullrev
2286 self.roots = set()
2322 self.roots = set()
2287 self.onlyfirst = onlyfirst
2323 self.onlyfirst = onlyfirst
2288
2324
2289 def match(self, rev):
2325 def match(self, rev):
2290 def realparents(rev):
2326 def realparents(rev):
2291 if self.onlyfirst:
2327 if self.onlyfirst:
2292 return self.repo.changelog.parentrevs(rev)[0:1]
2328 return self.repo.changelog.parentrevs(rev)[0:1]
2293 else:
2329 else:
2294 return filter(
2330 return filter(
2295 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2331 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2296 )
2332 )
2297
2333
2298 if self.startrev == nullrev:
2334 if self.startrev == nullrev:
2299 self.startrev = rev
2335 self.startrev = rev
2300 return True
2336 return True
2301
2337
2302 if rev > self.startrev:
2338 if rev > self.startrev:
2303 # forward: all descendants
2339 # forward: all descendants
2304 if not self.roots:
2340 if not self.roots:
2305 self.roots.add(self.startrev)
2341 self.roots.add(self.startrev)
2306 for parent in realparents(rev):
2342 for parent in realparents(rev):
2307 if parent in self.roots:
2343 if parent in self.roots:
2308 self.roots.add(rev)
2344 self.roots.add(rev)
2309 return True
2345 return True
2310 else:
2346 else:
2311 # backwards: all parents
2347 # backwards: all parents
2312 if not self.roots:
2348 if not self.roots:
2313 self.roots.update(realparents(self.startrev))
2349 self.roots.update(realparents(self.startrev))
2314 if rev in self.roots:
2350 if rev in self.roots:
2315 self.roots.remove(rev)
2351 self.roots.remove(rev)
2316 self.roots.update(realparents(rev))
2352 self.roots.update(realparents(rev))
2317 return True
2353 return True
2318
2354
2319 return False
2355 return False
2320
2356
2321
2357
2322 def walkchangerevs(repo, match, opts, prepare):
2358 def walkchangerevs(repo, match, opts, prepare):
2323 '''Iterate over files and the revs in which they changed.
2359 '''Iterate over files and the revs in which they changed.
2324
2360
2325 Callers most commonly need to iterate backwards over the history
2361 Callers most commonly need to iterate backwards over the history
2326 in which they are interested. Doing so has awful (quadratic-looking)
2362 in which they are interested. Doing so has awful (quadratic-looking)
2327 performance, so we use iterators in a "windowed" way.
2363 performance, so we use iterators in a "windowed" way.
2328
2364
2329 We walk a window of revisions in the desired order. Within the
2365 We walk a window of revisions in the desired order. Within the
2330 window, we first walk forwards to gather data, then in the desired
2366 window, we first walk forwards to gather data, then in the desired
2331 order (usually backwards) to display it.
2367 order (usually backwards) to display it.
2332
2368
2333 This function returns an iterator yielding contexts. Before
2369 This function returns an iterator yielding contexts. Before
2334 yielding each context, the iterator will first call the prepare
2370 yielding each context, the iterator will first call the prepare
2335 function on each context in the window in forward order.'''
2371 function on each context in the window in forward order.'''
2336
2372
2337 allfiles = opts.get(b'all_files')
2373 allfiles = opts.get(b'all_files')
2338 follow = opts.get(b'follow') or opts.get(b'follow_first')
2374 follow = opts.get(b'follow') or opts.get(b'follow_first')
2339 revs = _walkrevs(repo, opts)
2375 revs = _walkrevs(repo, opts)
2340 if not revs:
2376 if not revs:
2341 return []
2377 return []
2342 wanted = set()
2378 wanted = set()
2343 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2379 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2344 fncache = {}
2380 fncache = {}
2345 change = repo.__getitem__
2381 change = repo.__getitem__
2346
2382
2347 # First step is to fill wanted, the set of revisions that we want to yield.
2383 # First step is to fill wanted, the set of revisions that we want to yield.
2348 # When it does not induce extra cost, we also fill fncache for revisions in
2384 # When it does not induce extra cost, we also fill fncache for revisions in
2349 # wanted: a cache of filenames that were changed (ctx.files()) and that
2385 # wanted: a cache of filenames that were changed (ctx.files()) and that
2350 # match the file filtering conditions.
2386 # match the file filtering conditions.
2351
2387
2352 if match.always() or allfiles:
2388 if match.always() or allfiles:
2353 # No files, no patterns. Display all revs.
2389 # No files, no patterns. Display all revs.
2354 wanted = revs
2390 wanted = revs
2355 elif not slowpath:
2391 elif not slowpath:
2356 # We only have to read through the filelog to find wanted revisions
2392 # We only have to read through the filelog to find wanted revisions
2357
2393
2358 try:
2394 try:
2359 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2395 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2360 except FileWalkError:
2396 except FileWalkError:
2361 slowpath = True
2397 slowpath = True
2362
2398
2363 # We decided to fall back to the slowpath because at least one
2399 # We decided to fall back to the slowpath because at least one
2364 # of the paths was not a file. Check to see if at least one of them
2400 # of the paths was not a file. Check to see if at least one of them
2365 # existed in history, otherwise simply return
2401 # existed in history, otherwise simply return
2366 for path in match.files():
2402 for path in match.files():
2367 if path == b'.' or path in repo.store:
2403 if path == b'.' or path in repo.store:
2368 break
2404 break
2369 else:
2405 else:
2370 return []
2406 return []
2371
2407
2372 if slowpath:
2408 if slowpath:
2373 # We have to read the changelog to match filenames against
2409 # We have to read the changelog to match filenames against
2374 # changed files
2410 # changed files
2375
2411
2376 if follow:
2412 if follow:
2377 raise error.Abort(
2413 raise error.Abort(
2378 _(b'can only follow copies/renames for explicit filenames')
2414 _(b'can only follow copies/renames for explicit filenames')
2379 )
2415 )
2380
2416
2381 # The slow path checks files modified in every changeset.
2417 # The slow path checks files modified in every changeset.
2382 # This is really slow on large repos, so compute the set lazily.
2418 # This is really slow on large repos, so compute the set lazily.
2383 class lazywantedset(object):
2419 class lazywantedset(object):
2384 def __init__(self):
2420 def __init__(self):
2385 self.set = set()
2421 self.set = set()
2386 self.revs = set(revs)
2422 self.revs = set(revs)
2387
2423
2388 # No need to worry about locality here because it will be accessed
2424 # No need to worry about locality here because it will be accessed
2389 # in the same order as the increasing window below.
2425 # in the same order as the increasing window below.
2390 def __contains__(self, value):
2426 def __contains__(self, value):
2391 if value in self.set:
2427 if value in self.set:
2392 return True
2428 return True
2393 elif not value in self.revs:
2429 elif not value in self.revs:
2394 return False
2430 return False
2395 else:
2431 else:
2396 self.revs.discard(value)
2432 self.revs.discard(value)
2397 ctx = change(value)
2433 ctx = change(value)
2398 if allfiles:
2434 if allfiles:
2399 matches = list(ctx.manifest().walk(match))
2435 matches = list(ctx.manifest().walk(match))
2400 else:
2436 else:
2401 matches = [f for f in ctx.files() if match(f)]
2437 matches = [f for f in ctx.files() if match(f)]
2402 if matches:
2438 if matches:
2403 fncache[value] = matches
2439 fncache[value] = matches
2404 self.set.add(value)
2440 self.set.add(value)
2405 return True
2441 return True
2406 return False
2442 return False
2407
2443
2408 def discard(self, value):
2444 def discard(self, value):
2409 self.revs.discard(value)
2445 self.revs.discard(value)
2410 self.set.discard(value)
2446 self.set.discard(value)
2411
2447
2412 wanted = lazywantedset()
2448 wanted = lazywantedset()
2413
2449
2414 # it might be worthwhile to do this in the iterator if the rev range
2450 # it might be worthwhile to do this in the iterator if the rev range
2415 # is descending and the prune args are all within that range
2451 # is descending and the prune args are all within that range
2416 for rev in opts.get(b'prune', ()):
2452 for rev in opts.get(b'prune', ()):
2417 rev = repo[rev].rev()
2453 rev = repo[rev].rev()
2418 ff = _followfilter(repo)
2454 ff = _followfilter(repo)
2419 stop = min(revs[0], revs[-1])
2455 stop = min(revs[0], revs[-1])
2420 for x in pycompat.xrange(rev, stop - 1, -1):
2456 for x in pycompat.xrange(rev, stop - 1, -1):
2421 if ff.match(x):
2457 if ff.match(x):
2422 wanted = wanted - [x]
2458 wanted = wanted - [x]
2423
2459
2424 # Now that wanted is correctly initialized, we can iterate over the
2460 # Now that wanted is correctly initialized, we can iterate over the
2425 # revision range, yielding only revisions in wanted.
2461 # revision range, yielding only revisions in wanted.
2426 def iterate():
2462 def iterate():
2427 if follow and match.always():
2463 if follow and match.always():
2428 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2464 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2429
2465
2430 def want(rev):
2466 def want(rev):
2431 return ff.match(rev) and rev in wanted
2467 return ff.match(rev) and rev in wanted
2432
2468
2433 else:
2469 else:
2434
2470
2435 def want(rev):
2471 def want(rev):
2436 return rev in wanted
2472 return rev in wanted
2437
2473
2438 it = iter(revs)
2474 it = iter(revs)
2439 stopiteration = False
2475 stopiteration = False
2440 for windowsize in increasingwindows():
2476 for windowsize in increasingwindows():
2441 nrevs = []
2477 nrevs = []
2442 for i in pycompat.xrange(windowsize):
2478 for i in pycompat.xrange(windowsize):
2443 rev = next(it, None)
2479 rev = next(it, None)
2444 if rev is None:
2480 if rev is None:
2445 stopiteration = True
2481 stopiteration = True
2446 break
2482 break
2447 elif want(rev):
2483 elif want(rev):
2448 nrevs.append(rev)
2484 nrevs.append(rev)
2449 for rev in sorted(nrevs):
2485 for rev in sorted(nrevs):
2450 fns = fncache.get(rev)
2486 fns = fncache.get(rev)
2451 ctx = change(rev)
2487 ctx = change(rev)
2452 if not fns:
2488 if not fns:
2453
2489
2454 def fns_generator():
2490 def fns_generator():
2455 if allfiles:
2491 if allfiles:
2456
2492
2457 def bad(f, msg):
2493 def bad(f, msg):
2458 pass
2494 pass
2459
2495
2460 for f in ctx.matches(matchmod.badmatch(match, bad)):
2496 for f in ctx.matches(matchmod.badmatch(match, bad)):
2461 yield f
2497 yield f
2462 else:
2498 else:
2463 for f in ctx.files():
2499 for f in ctx.files():
2464 if match(f):
2500 if match(f):
2465 yield f
2501 yield f
2466
2502
2467 fns = fns_generator()
2503 fns = fns_generator()
2468 prepare(ctx, fns)
2504 prepare(ctx, fns)
2469 for rev in nrevs:
2505 for rev in nrevs:
2470 yield change(rev)
2506 yield change(rev)
2471
2507
2472 if stopiteration:
2508 if stopiteration:
2473 break
2509 break
2474
2510
2475 return iterate()
2511 return iterate()
2476
2512
2477
2513
2478 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2514 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2479 bad = []
2515 bad = []
2480
2516
2481 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2517 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2482 names = []
2518 names = []
2483 wctx = repo[None]
2519 wctx = repo[None]
2484 cca = None
2520 cca = None
2485 abort, warn = scmutil.checkportabilityalert(ui)
2521 abort, warn = scmutil.checkportabilityalert(ui)
2486 if abort or warn:
2522 if abort or warn:
2487 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2523 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2488
2524
2489 match = repo.narrowmatch(match, includeexact=True)
2525 match = repo.narrowmatch(match, includeexact=True)
2490 badmatch = matchmod.badmatch(match, badfn)
2526 badmatch = matchmod.badmatch(match, badfn)
2491 dirstate = repo.dirstate
2527 dirstate = repo.dirstate
2492 # We don't want to just call wctx.walk here, since it would return a lot of
2528 # We don't want to just call wctx.walk here, since it would return a lot of
2493 # clean files, which we aren't interested in and takes time.
2529 # clean files, which we aren't interested in and takes time.
2494 for f in sorted(
2530 for f in sorted(
2495 dirstate.walk(
2531 dirstate.walk(
2496 badmatch,
2532 badmatch,
2497 subrepos=sorted(wctx.substate),
2533 subrepos=sorted(wctx.substate),
2498 unknown=True,
2534 unknown=True,
2499 ignored=False,
2535 ignored=False,
2500 full=False,
2536 full=False,
2501 )
2537 )
2502 ):
2538 ):
2503 exact = match.exact(f)
2539 exact = match.exact(f)
2504 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2540 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2505 if cca:
2541 if cca:
2506 cca(f)
2542 cca(f)
2507 names.append(f)
2543 names.append(f)
2508 if ui.verbose or not exact:
2544 if ui.verbose or not exact:
2509 ui.status(
2545 ui.status(
2510 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2546 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2511 )
2547 )
2512
2548
2513 for subpath in sorted(wctx.substate):
2549 for subpath in sorted(wctx.substate):
2514 sub = wctx.sub(subpath)
2550 sub = wctx.sub(subpath)
2515 try:
2551 try:
2516 submatch = matchmod.subdirmatcher(subpath, match)
2552 submatch = matchmod.subdirmatcher(subpath, match)
2517 subprefix = repo.wvfs.reljoin(prefix, subpath)
2553 subprefix = repo.wvfs.reljoin(prefix, subpath)
2518 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2554 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2519 if opts.get('subrepos'):
2555 if opts.get('subrepos'):
2520 bad.extend(
2556 bad.extend(
2521 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2557 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2522 )
2558 )
2523 else:
2559 else:
2524 bad.extend(
2560 bad.extend(
2525 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2561 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2526 )
2562 )
2527 except error.LookupError:
2563 except error.LookupError:
2528 ui.status(
2564 ui.status(
2529 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2565 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2530 )
2566 )
2531
2567
2532 if not opts.get('dry_run'):
2568 if not opts.get('dry_run'):
2533 rejected = wctx.add(names, prefix)
2569 rejected = wctx.add(names, prefix)
2534 bad.extend(f for f in rejected if f in match.files())
2570 bad.extend(f for f in rejected if f in match.files())
2535 return bad
2571 return bad
2536
2572
2537
2573
2538 def addwebdirpath(repo, serverpath, webconf):
2574 def addwebdirpath(repo, serverpath, webconf):
2539 webconf[serverpath] = repo.root
2575 webconf[serverpath] = repo.root
2540 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2576 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2541
2577
2542 for r in repo.revs(b'filelog("path:.hgsub")'):
2578 for r in repo.revs(b'filelog("path:.hgsub")'):
2543 ctx = repo[r]
2579 ctx = repo[r]
2544 for subpath in ctx.substate:
2580 for subpath in ctx.substate:
2545 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2581 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2546
2582
2547
2583
2548 def forget(
2584 def forget(
2549 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2585 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2550 ):
2586 ):
2551 if dryrun and interactive:
2587 if dryrun and interactive:
2552 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2588 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2553 bad = []
2589 bad = []
2554 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2590 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2555 wctx = repo[None]
2591 wctx = repo[None]
2556 forgot = []
2592 forgot = []
2557
2593
2558 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2594 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2559 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2595 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2560 if explicitonly:
2596 if explicitonly:
2561 forget = [f for f in forget if match.exact(f)]
2597 forget = [f for f in forget if match.exact(f)]
2562
2598
2563 for subpath in sorted(wctx.substate):
2599 for subpath in sorted(wctx.substate):
2564 sub = wctx.sub(subpath)
2600 sub = wctx.sub(subpath)
2565 submatch = matchmod.subdirmatcher(subpath, match)
2601 submatch = matchmod.subdirmatcher(subpath, match)
2566 subprefix = repo.wvfs.reljoin(prefix, subpath)
2602 subprefix = repo.wvfs.reljoin(prefix, subpath)
2567 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2603 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2568 try:
2604 try:
2569 subbad, subforgot = sub.forget(
2605 subbad, subforgot = sub.forget(
2570 submatch,
2606 submatch,
2571 subprefix,
2607 subprefix,
2572 subuipathfn,
2608 subuipathfn,
2573 dryrun=dryrun,
2609 dryrun=dryrun,
2574 interactive=interactive,
2610 interactive=interactive,
2575 )
2611 )
2576 bad.extend([subpath + b'/' + f for f in subbad])
2612 bad.extend([subpath + b'/' + f for f in subbad])
2577 forgot.extend([subpath + b'/' + f for f in subforgot])
2613 forgot.extend([subpath + b'/' + f for f in subforgot])
2578 except error.LookupError:
2614 except error.LookupError:
2579 ui.status(
2615 ui.status(
2580 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2616 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2581 )
2617 )
2582
2618
2583 if not explicitonly:
2619 if not explicitonly:
2584 for f in match.files():
2620 for f in match.files():
2585 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2621 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2586 if f not in forgot:
2622 if f not in forgot:
2587 if repo.wvfs.exists(f):
2623 if repo.wvfs.exists(f):
2588 # Don't complain if the exact case match wasn't given.
2624 # Don't complain if the exact case match wasn't given.
2589 # But don't do this until after checking 'forgot', so
2625 # But don't do this until after checking 'forgot', so
2590 # that subrepo files aren't normalized, and this op is
2626 # that subrepo files aren't normalized, and this op is
2591 # purely from data cached by the status walk above.
2627 # purely from data cached by the status walk above.
2592 if repo.dirstate.normalize(f) in repo.dirstate:
2628 if repo.dirstate.normalize(f) in repo.dirstate:
2593 continue
2629 continue
2594 ui.warn(
2630 ui.warn(
2595 _(
2631 _(
2596 b'not removing %s: '
2632 b'not removing %s: '
2597 b'file is already untracked\n'
2633 b'file is already untracked\n'
2598 )
2634 )
2599 % uipathfn(f)
2635 % uipathfn(f)
2600 )
2636 )
2601 bad.append(f)
2637 bad.append(f)
2602
2638
2603 if interactive:
2639 if interactive:
2604 responses = _(
2640 responses = _(
2605 b'[Ynsa?]'
2641 b'[Ynsa?]'
2606 b'$$ &Yes, forget this file'
2642 b'$$ &Yes, forget this file'
2607 b'$$ &No, skip this file'
2643 b'$$ &No, skip this file'
2608 b'$$ &Skip remaining files'
2644 b'$$ &Skip remaining files'
2609 b'$$ Include &all remaining files'
2645 b'$$ Include &all remaining files'
2610 b'$$ &? (display help)'
2646 b'$$ &? (display help)'
2611 )
2647 )
2612 for filename in forget[:]:
2648 for filename in forget[:]:
2613 r = ui.promptchoice(
2649 r = ui.promptchoice(
2614 _(b'forget %s %s') % (uipathfn(filename), responses)
2650 _(b'forget %s %s') % (uipathfn(filename), responses)
2615 )
2651 )
2616 if r == 4: # ?
2652 if r == 4: # ?
2617 while r == 4:
2653 while r == 4:
2618 for c, t in ui.extractchoices(responses)[1]:
2654 for c, t in ui.extractchoices(responses)[1]:
2619 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2655 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2620 r = ui.promptchoice(
2656 r = ui.promptchoice(
2621 _(b'forget %s %s') % (uipathfn(filename), responses)
2657 _(b'forget %s %s') % (uipathfn(filename), responses)
2622 )
2658 )
2623 if r == 0: # yes
2659 if r == 0: # yes
2624 continue
2660 continue
2625 elif r == 1: # no
2661 elif r == 1: # no
2626 forget.remove(filename)
2662 forget.remove(filename)
2627 elif r == 2: # Skip
2663 elif r == 2: # Skip
2628 fnindex = forget.index(filename)
2664 fnindex = forget.index(filename)
2629 del forget[fnindex:]
2665 del forget[fnindex:]
2630 break
2666 break
2631 elif r == 3: # All
2667 elif r == 3: # All
2632 break
2668 break
2633
2669
2634 for f in forget:
2670 for f in forget:
2635 if ui.verbose or not match.exact(f) or interactive:
2671 if ui.verbose or not match.exact(f) or interactive:
2636 ui.status(
2672 ui.status(
2637 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2673 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2638 )
2674 )
2639
2675
2640 if not dryrun:
2676 if not dryrun:
2641 rejected = wctx.forget(forget, prefix)
2677 rejected = wctx.forget(forget, prefix)
2642 bad.extend(f for f in rejected if f in match.files())
2678 bad.extend(f for f in rejected if f in match.files())
2643 forgot.extend(f for f in forget if f not in rejected)
2679 forgot.extend(f for f in forget if f not in rejected)
2644 return bad, forgot
2680 return bad, forgot
2645
2681
2646
2682
2647 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2683 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2648 ret = 1
2684 ret = 1
2649
2685
2650 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2686 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2651 for f in ctx.matches(m):
2687 for f in ctx.matches(m):
2652 fm.startitem()
2688 fm.startitem()
2653 fm.context(ctx=ctx)
2689 fm.context(ctx=ctx)
2654 if needsfctx:
2690 if needsfctx:
2655 fc = ctx[f]
2691 fc = ctx[f]
2656 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2692 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2657 fm.data(path=f)
2693 fm.data(path=f)
2658 fm.plain(fmt % uipathfn(f))
2694 fm.plain(fmt % uipathfn(f))
2659 ret = 0
2695 ret = 0
2660
2696
2661 for subpath in sorted(ctx.substate):
2697 for subpath in sorted(ctx.substate):
2662 submatch = matchmod.subdirmatcher(subpath, m)
2698 submatch = matchmod.subdirmatcher(subpath, m)
2663 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2699 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2664 if subrepos or m.exact(subpath) or any(submatch.files()):
2700 if subrepos or m.exact(subpath) or any(submatch.files()):
2665 sub = ctx.sub(subpath)
2701 sub = ctx.sub(subpath)
2666 try:
2702 try:
2667 recurse = m.exact(subpath) or subrepos
2703 recurse = m.exact(subpath) or subrepos
2668 if (
2704 if (
2669 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2705 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2670 == 0
2706 == 0
2671 ):
2707 ):
2672 ret = 0
2708 ret = 0
2673 except error.LookupError:
2709 except error.LookupError:
2674 ui.status(
2710 ui.status(
2675 _(b"skipping missing subrepository: %s\n")
2711 _(b"skipping missing subrepository: %s\n")
2676 % uipathfn(subpath)
2712 % uipathfn(subpath)
2677 )
2713 )
2678
2714
2679 return ret
2715 return ret
2680
2716
2681
2717
2682 def remove(
2718 def remove(
2683 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2719 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2684 ):
2720 ):
2685 ret = 0
2721 ret = 0
2686 s = repo.status(match=m, clean=True)
2722 s = repo.status(match=m, clean=True)
2687 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2723 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2688
2724
2689 wctx = repo[None]
2725 wctx = repo[None]
2690
2726
2691 if warnings is None:
2727 if warnings is None:
2692 warnings = []
2728 warnings = []
2693 warn = True
2729 warn = True
2694 else:
2730 else:
2695 warn = False
2731 warn = False
2696
2732
2697 subs = sorted(wctx.substate)
2733 subs = sorted(wctx.substate)
2698 progress = ui.makeprogress(
2734 progress = ui.makeprogress(
2699 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2735 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2700 )
2736 )
2701 for subpath in subs:
2737 for subpath in subs:
2702 submatch = matchmod.subdirmatcher(subpath, m)
2738 submatch = matchmod.subdirmatcher(subpath, m)
2703 subprefix = repo.wvfs.reljoin(prefix, subpath)
2739 subprefix = repo.wvfs.reljoin(prefix, subpath)
2704 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2740 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2705 if subrepos or m.exact(subpath) or any(submatch.files()):
2741 if subrepos or m.exact(subpath) or any(submatch.files()):
2706 progress.increment()
2742 progress.increment()
2707 sub = wctx.sub(subpath)
2743 sub = wctx.sub(subpath)
2708 try:
2744 try:
2709 if sub.removefiles(
2745 if sub.removefiles(
2710 submatch,
2746 submatch,
2711 subprefix,
2747 subprefix,
2712 subuipathfn,
2748 subuipathfn,
2713 after,
2749 after,
2714 force,
2750 force,
2715 subrepos,
2751 subrepos,
2716 dryrun,
2752 dryrun,
2717 warnings,
2753 warnings,
2718 ):
2754 ):
2719 ret = 1
2755 ret = 1
2720 except error.LookupError:
2756 except error.LookupError:
2721 warnings.append(
2757 warnings.append(
2722 _(b"skipping missing subrepository: %s\n")
2758 _(b"skipping missing subrepository: %s\n")
2723 % uipathfn(subpath)
2759 % uipathfn(subpath)
2724 )
2760 )
2725 progress.complete()
2761 progress.complete()
2726
2762
2727 # warn about failure to delete explicit files/dirs
2763 # warn about failure to delete explicit files/dirs
2728 deleteddirs = pathutil.dirs(deleted)
2764 deleteddirs = pathutil.dirs(deleted)
2729 files = m.files()
2765 files = m.files()
2730 progress = ui.makeprogress(
2766 progress = ui.makeprogress(
2731 _(b'deleting'), total=len(files), unit=_(b'files')
2767 _(b'deleting'), total=len(files), unit=_(b'files')
2732 )
2768 )
2733 for f in files:
2769 for f in files:
2734
2770
2735 def insubrepo():
2771 def insubrepo():
2736 for subpath in wctx.substate:
2772 for subpath in wctx.substate:
2737 if f.startswith(subpath + b'/'):
2773 if f.startswith(subpath + b'/'):
2738 return True
2774 return True
2739 return False
2775 return False
2740
2776
2741 progress.increment()
2777 progress.increment()
2742 isdir = f in deleteddirs or wctx.hasdir(f)
2778 isdir = f in deleteddirs or wctx.hasdir(f)
2743 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2779 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2744 continue
2780 continue
2745
2781
2746 if repo.wvfs.exists(f):
2782 if repo.wvfs.exists(f):
2747 if repo.wvfs.isdir(f):
2783 if repo.wvfs.isdir(f):
2748 warnings.append(
2784 warnings.append(
2749 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2785 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2750 )
2786 )
2751 else:
2787 else:
2752 warnings.append(
2788 warnings.append(
2753 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2789 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2754 )
2790 )
2755 # missing files will generate a warning elsewhere
2791 # missing files will generate a warning elsewhere
2756 ret = 1
2792 ret = 1
2757 progress.complete()
2793 progress.complete()
2758
2794
2759 if force:
2795 if force:
2760 list = modified + deleted + clean + added
2796 list = modified + deleted + clean + added
2761 elif after:
2797 elif after:
2762 list = deleted
2798 list = deleted
2763 remaining = modified + added + clean
2799 remaining = modified + added + clean
2764 progress = ui.makeprogress(
2800 progress = ui.makeprogress(
2765 _(b'skipping'), total=len(remaining), unit=_(b'files')
2801 _(b'skipping'), total=len(remaining), unit=_(b'files')
2766 )
2802 )
2767 for f in remaining:
2803 for f in remaining:
2768 progress.increment()
2804 progress.increment()
2769 if ui.verbose or (f in files):
2805 if ui.verbose or (f in files):
2770 warnings.append(
2806 warnings.append(
2771 _(b'not removing %s: file still exists\n') % uipathfn(f)
2807 _(b'not removing %s: file still exists\n') % uipathfn(f)
2772 )
2808 )
2773 ret = 1
2809 ret = 1
2774 progress.complete()
2810 progress.complete()
2775 else:
2811 else:
2776 list = deleted + clean
2812 list = deleted + clean
2777 progress = ui.makeprogress(
2813 progress = ui.makeprogress(
2778 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2814 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2779 )
2815 )
2780 for f in modified:
2816 for f in modified:
2781 progress.increment()
2817 progress.increment()
2782 warnings.append(
2818 warnings.append(
2783 _(
2819 _(
2784 b'not removing %s: file is modified (use -f'
2820 b'not removing %s: file is modified (use -f'
2785 b' to force removal)\n'
2821 b' to force removal)\n'
2786 )
2822 )
2787 % uipathfn(f)
2823 % uipathfn(f)
2788 )
2824 )
2789 ret = 1
2825 ret = 1
2790 for f in added:
2826 for f in added:
2791 progress.increment()
2827 progress.increment()
2792 warnings.append(
2828 warnings.append(
2793 _(
2829 _(
2794 b"not removing %s: file has been marked for add"
2830 b"not removing %s: file has been marked for add"
2795 b" (use 'hg forget' to undo add)\n"
2831 b" (use 'hg forget' to undo add)\n"
2796 )
2832 )
2797 % uipathfn(f)
2833 % uipathfn(f)
2798 )
2834 )
2799 ret = 1
2835 ret = 1
2800 progress.complete()
2836 progress.complete()
2801
2837
2802 list = sorted(list)
2838 list = sorted(list)
2803 progress = ui.makeprogress(
2839 progress = ui.makeprogress(
2804 _(b'deleting'), total=len(list), unit=_(b'files')
2840 _(b'deleting'), total=len(list), unit=_(b'files')
2805 )
2841 )
2806 for f in list:
2842 for f in list:
2807 if ui.verbose or not m.exact(f):
2843 if ui.verbose or not m.exact(f):
2808 progress.increment()
2844 progress.increment()
2809 ui.status(
2845 ui.status(
2810 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2846 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2811 )
2847 )
2812 progress.complete()
2848 progress.complete()
2813
2849
2814 if not dryrun:
2850 if not dryrun:
2815 with repo.wlock():
2851 with repo.wlock():
2816 if not after:
2852 if not after:
2817 for f in list:
2853 for f in list:
2818 if f in added:
2854 if f in added:
2819 continue # we never unlink added files on remove
2855 continue # we never unlink added files on remove
2820 rmdir = repo.ui.configbool(
2856 rmdir = repo.ui.configbool(
2821 b'experimental', b'removeemptydirs'
2857 b'experimental', b'removeemptydirs'
2822 )
2858 )
2823 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2859 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2824 repo[None].forget(list)
2860 repo[None].forget(list)
2825
2861
2826 if warn:
2862 if warn:
2827 for warning in warnings:
2863 for warning in warnings:
2828 ui.warn(warning)
2864 ui.warn(warning)
2829
2865
2830 return ret
2866 return ret
2831
2867
2832
2868
2833 def _catfmtneedsdata(fm):
2869 def _catfmtneedsdata(fm):
2834 return not fm.datahint() or b'data' in fm.datahint()
2870 return not fm.datahint() or b'data' in fm.datahint()
2835
2871
2836
2872
2837 def _updatecatformatter(fm, ctx, matcher, path, decode):
2873 def _updatecatformatter(fm, ctx, matcher, path, decode):
2838 """Hook for adding data to the formatter used by ``hg cat``.
2874 """Hook for adding data to the formatter used by ``hg cat``.
2839
2875
2840 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2876 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2841 this method first."""
2877 this method first."""
2842
2878
2843 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2879 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2844 # wasn't requested.
2880 # wasn't requested.
2845 data = b''
2881 data = b''
2846 if _catfmtneedsdata(fm):
2882 if _catfmtneedsdata(fm):
2847 data = ctx[path].data()
2883 data = ctx[path].data()
2848 if decode:
2884 if decode:
2849 data = ctx.repo().wwritedata(path, data)
2885 data = ctx.repo().wwritedata(path, data)
2850 fm.startitem()
2886 fm.startitem()
2851 fm.context(ctx=ctx)
2887 fm.context(ctx=ctx)
2852 fm.write(b'data', b'%s', data)
2888 fm.write(b'data', b'%s', data)
2853 fm.data(path=path)
2889 fm.data(path=path)
2854
2890
2855
2891
2856 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2892 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2857 err = 1
2893 err = 1
2858 opts = pycompat.byteskwargs(opts)
2894 opts = pycompat.byteskwargs(opts)
2859
2895
2860 def write(path):
2896 def write(path):
2861 filename = None
2897 filename = None
2862 if fntemplate:
2898 if fntemplate:
2863 filename = makefilename(
2899 filename = makefilename(
2864 ctx, fntemplate, pathname=os.path.join(prefix, path)
2900 ctx, fntemplate, pathname=os.path.join(prefix, path)
2865 )
2901 )
2866 # attempt to create the directory if it does not already exist
2902 # attempt to create the directory if it does not already exist
2867 try:
2903 try:
2868 os.makedirs(os.path.dirname(filename))
2904 os.makedirs(os.path.dirname(filename))
2869 except OSError:
2905 except OSError:
2870 pass
2906 pass
2871 with formatter.maybereopen(basefm, filename) as fm:
2907 with formatter.maybereopen(basefm, filename) as fm:
2872 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2908 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2873
2909
2874 # Automation often uses hg cat on single files, so special case it
2910 # Automation often uses hg cat on single files, so special case it
2875 # for performance to avoid the cost of parsing the manifest.
2911 # for performance to avoid the cost of parsing the manifest.
2876 if len(matcher.files()) == 1 and not matcher.anypats():
2912 if len(matcher.files()) == 1 and not matcher.anypats():
2877 file = matcher.files()[0]
2913 file = matcher.files()[0]
2878 mfl = repo.manifestlog
2914 mfl = repo.manifestlog
2879 mfnode = ctx.manifestnode()
2915 mfnode = ctx.manifestnode()
2880 try:
2916 try:
2881 if mfnode and mfl[mfnode].find(file)[0]:
2917 if mfnode and mfl[mfnode].find(file)[0]:
2882 if _catfmtneedsdata(basefm):
2918 if _catfmtneedsdata(basefm):
2883 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2919 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2884 write(file)
2920 write(file)
2885 return 0
2921 return 0
2886 except KeyError:
2922 except KeyError:
2887 pass
2923 pass
2888
2924
2889 if _catfmtneedsdata(basefm):
2925 if _catfmtneedsdata(basefm):
2890 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2926 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2891
2927
2892 for abs in ctx.walk(matcher):
2928 for abs in ctx.walk(matcher):
2893 write(abs)
2929 write(abs)
2894 err = 0
2930 err = 0
2895
2931
2896 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2932 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2897 for subpath in sorted(ctx.substate):
2933 for subpath in sorted(ctx.substate):
2898 sub = ctx.sub(subpath)
2934 sub = ctx.sub(subpath)
2899 try:
2935 try:
2900 submatch = matchmod.subdirmatcher(subpath, matcher)
2936 submatch = matchmod.subdirmatcher(subpath, matcher)
2901 subprefix = os.path.join(prefix, subpath)
2937 subprefix = os.path.join(prefix, subpath)
2902 if not sub.cat(
2938 if not sub.cat(
2903 submatch,
2939 submatch,
2904 basefm,
2940 basefm,
2905 fntemplate,
2941 fntemplate,
2906 subprefix,
2942 subprefix,
2907 **pycompat.strkwargs(opts)
2943 **pycompat.strkwargs(opts)
2908 ):
2944 ):
2909 err = 0
2945 err = 0
2910 except error.RepoLookupError:
2946 except error.RepoLookupError:
2911 ui.status(
2947 ui.status(
2912 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2948 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2913 )
2949 )
2914
2950
2915 return err
2951 return err
2916
2952
2917
2953
2918 def commit(ui, repo, commitfunc, pats, opts):
2954 def commit(ui, repo, commitfunc, pats, opts):
2919 '''commit the specified files or all outstanding changes'''
2955 '''commit the specified files or all outstanding changes'''
2920 date = opts.get(b'date')
2956 date = opts.get(b'date')
2921 if date:
2957 if date:
2922 opts[b'date'] = dateutil.parsedate(date)
2958 opts[b'date'] = dateutil.parsedate(date)
2923 message = logmessage(ui, opts)
2959 message = logmessage(ui, opts)
2924 matcher = scmutil.match(repo[None], pats, opts)
2960 matcher = scmutil.match(repo[None], pats, opts)
2925
2961
2926 dsguard = None
2962 dsguard = None
2927 # extract addremove carefully -- this function can be called from a command
2963 # extract addremove carefully -- this function can be called from a command
2928 # that doesn't support addremove
2964 # that doesn't support addremove
2929 if opts.get(b'addremove'):
2965 if opts.get(b'addremove'):
2930 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2966 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2931 with dsguard or util.nullcontextmanager():
2967 with dsguard or util.nullcontextmanager():
2932 if dsguard:
2968 if dsguard:
2933 relative = scmutil.anypats(pats, opts)
2969 relative = scmutil.anypats(pats, opts)
2934 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2970 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2935 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2971 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2936 raise error.Abort(
2972 raise error.Abort(
2937 _(b"failed to mark all new/missing files as added/removed")
2973 _(b"failed to mark all new/missing files as added/removed")
2938 )
2974 )
2939
2975
2940 return commitfunc(ui, repo, message, matcher, opts)
2976 return commitfunc(ui, repo, message, matcher, opts)
2941
2977
2942
2978
2943 def samefile(f, ctx1, ctx2):
2979 def samefile(f, ctx1, ctx2):
2944 if f in ctx1.manifest():
2980 if f in ctx1.manifest():
2945 a = ctx1.filectx(f)
2981 a = ctx1.filectx(f)
2946 if f in ctx2.manifest():
2982 if f in ctx2.manifest():
2947 b = ctx2.filectx(f)
2983 b = ctx2.filectx(f)
2948 return not a.cmp(b) and a.flags() == b.flags()
2984 return not a.cmp(b) and a.flags() == b.flags()
2949 else:
2985 else:
2950 return False
2986 return False
2951 else:
2987 else:
2952 return f not in ctx2.manifest()
2988 return f not in ctx2.manifest()
2953
2989
2954
2990
2955 def amend(ui, repo, old, extra, pats, opts):
2991 def amend(ui, repo, old, extra, pats, opts):
2956 # avoid cycle context -> subrepo -> cmdutil
2992 # avoid cycle context -> subrepo -> cmdutil
2957 from . import context
2993 from . import context
2958
2994
2959 # amend will reuse the existing user if not specified, but the obsolete
2995 # amend will reuse the existing user if not specified, but the obsolete
2960 # marker creation requires that the current user's name is specified.
2996 # marker creation requires that the current user's name is specified.
2961 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2997 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2962 ui.username() # raise exception if username not set
2998 ui.username() # raise exception if username not set
2963
2999
2964 ui.note(_(b'amending changeset %s\n') % old)
3000 ui.note(_(b'amending changeset %s\n') % old)
2965 base = old.p1()
3001 base = old.p1()
2966
3002
2967 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
3003 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2968 # Participating changesets:
3004 # Participating changesets:
2969 #
3005 #
2970 # wctx o - workingctx that contains changes from working copy
3006 # wctx o - workingctx that contains changes from working copy
2971 # | to go into amending commit
3007 # | to go into amending commit
2972 # |
3008 # |
2973 # old o - changeset to amend
3009 # old o - changeset to amend
2974 # |
3010 # |
2975 # base o - first parent of the changeset to amend
3011 # base o - first parent of the changeset to amend
2976 wctx = repo[None]
3012 wctx = repo[None]
2977
3013
2978 # Copy to avoid mutating input
3014 # Copy to avoid mutating input
2979 extra = extra.copy()
3015 extra = extra.copy()
2980 # Update extra dict from amended commit (e.g. to preserve graft
3016 # Update extra dict from amended commit (e.g. to preserve graft
2981 # source)
3017 # source)
2982 extra.update(old.extra())
3018 extra.update(old.extra())
2983
3019
2984 # Also update it from the from the wctx
3020 # Also update it from the from the wctx
2985 extra.update(wctx.extra())
3021 extra.update(wctx.extra())
2986
3022
2987 # date-only change should be ignored?
3023 # date-only change should be ignored?
2988 datemaydiffer = resolvecommitoptions(ui, opts)
3024 datemaydiffer = resolvecommitoptions(ui, opts)
2989
3025
2990 date = old.date()
3026 date = old.date()
2991 if opts.get(b'date'):
3027 if opts.get(b'date'):
2992 date = dateutil.parsedate(opts.get(b'date'))
3028 date = dateutil.parsedate(opts.get(b'date'))
2993 user = opts.get(b'user') or old.user()
3029 user = opts.get(b'user') or old.user()
2994
3030
2995 if len(old.parents()) > 1:
3031 if len(old.parents()) > 1:
2996 # ctx.files() isn't reliable for merges, so fall back to the
3032 # ctx.files() isn't reliable for merges, so fall back to the
2997 # slower repo.status() method
3033 # slower repo.status() method
2998 st = base.status(old)
3034 st = base.status(old)
2999 files = set(st.modified) | set(st.added) | set(st.removed)
3035 files = set(st.modified) | set(st.added) | set(st.removed)
3000 else:
3036 else:
3001 files = set(old.files())
3037 files = set(old.files())
3002
3038
3003 # add/remove the files to the working copy if the "addremove" option
3039 # add/remove the files to the working copy if the "addremove" option
3004 # was specified.
3040 # was specified.
3005 matcher = scmutil.match(wctx, pats, opts)
3041 matcher = scmutil.match(wctx, pats, opts)
3006 relative = scmutil.anypats(pats, opts)
3042 relative = scmutil.anypats(pats, opts)
3007 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
3043 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
3008 if opts.get(b'addremove') and scmutil.addremove(
3044 if opts.get(b'addremove') and scmutil.addremove(
3009 repo, matcher, b"", uipathfn, opts
3045 repo, matcher, b"", uipathfn, opts
3010 ):
3046 ):
3011 raise error.Abort(
3047 raise error.Abort(
3012 _(b"failed to mark all new/missing files as added/removed")
3048 _(b"failed to mark all new/missing files as added/removed")
3013 )
3049 )
3014
3050
3015 # Check subrepos. This depends on in-place wctx._status update in
3051 # Check subrepos. This depends on in-place wctx._status update in
3016 # subrepo.precommit(). To minimize the risk of this hack, we do
3052 # subrepo.precommit(). To minimize the risk of this hack, we do
3017 # nothing if .hgsub does not exist.
3053 # nothing if .hgsub does not exist.
3018 if b'.hgsub' in wctx or b'.hgsub' in old:
3054 if b'.hgsub' in wctx or b'.hgsub' in old:
3019 subs, commitsubs, newsubstate = subrepoutil.precommit(
3055 subs, commitsubs, newsubstate = subrepoutil.precommit(
3020 ui, wctx, wctx._status, matcher
3056 ui, wctx, wctx._status, matcher
3021 )
3057 )
3022 # amend should abort if commitsubrepos is enabled
3058 # amend should abort if commitsubrepos is enabled
3023 assert not commitsubs
3059 assert not commitsubs
3024 if subs:
3060 if subs:
3025 subrepoutil.writestate(repo, newsubstate)
3061 subrepoutil.writestate(repo, newsubstate)
3026
3062
3027 ms = mergemod.mergestate.read(repo)
3063 ms = mergemod.mergestate.read(repo)
3028 mergeutil.checkunresolved(ms)
3064 mergeutil.checkunresolved(ms)
3029
3065
3030 filestoamend = set(f for f in wctx.files() if matcher(f))
3066 filestoamend = set(f for f in wctx.files() if matcher(f))
3031
3067
3032 changes = len(filestoamend) > 0
3068 changes = len(filestoamend) > 0
3033 if changes:
3069 if changes:
3034 # Recompute copies (avoid recording a -> b -> a)
3070 # Recompute copies (avoid recording a -> b -> a)
3035 copied = copies.pathcopies(base, wctx, matcher)
3071 copied = copies.pathcopies(base, wctx, matcher)
3036 if old.p2:
3072 if old.p2:
3037 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3073 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3038
3074
3039 # Prune files which were reverted by the updates: if old
3075 # Prune files which were reverted by the updates: if old
3040 # introduced file X and the file was renamed in the working
3076 # introduced file X and the file was renamed in the working
3041 # copy, then those two files are the same and
3077 # copy, then those two files are the same and
3042 # we can discard X from our list of files. Likewise if X
3078 # we can discard X from our list of files. Likewise if X
3043 # was removed, it's no longer relevant. If X is missing (aka
3079 # was removed, it's no longer relevant. If X is missing (aka
3044 # deleted), old X must be preserved.
3080 # deleted), old X must be preserved.
3045 files.update(filestoamend)
3081 files.update(filestoamend)
3046 files = [
3082 files = [
3047 f
3083 f
3048 for f in files
3084 for f in files
3049 if (f not in filestoamend or not samefile(f, wctx, base))
3085 if (f not in filestoamend or not samefile(f, wctx, base))
3050 ]
3086 ]
3051
3087
3052 def filectxfn(repo, ctx_, path):
3088 def filectxfn(repo, ctx_, path):
3053 try:
3089 try:
3054 # If the file being considered is not amongst the files
3090 # If the file being considered is not amongst the files
3055 # to be amended, we should return the file context from the
3091 # to be amended, we should return the file context from the
3056 # old changeset. This avoids issues when only some files in
3092 # old changeset. This avoids issues when only some files in
3057 # the working copy are being amended but there are also
3093 # the working copy are being amended but there are also
3058 # changes to other files from the old changeset.
3094 # changes to other files from the old changeset.
3059 if path not in filestoamend:
3095 if path not in filestoamend:
3060 return old.filectx(path)
3096 return old.filectx(path)
3061
3097
3062 # Return None for removed files.
3098 # Return None for removed files.
3063 if path in wctx.removed():
3099 if path in wctx.removed():
3064 return None
3100 return None
3065
3101
3066 fctx = wctx[path]
3102 fctx = wctx[path]
3067 flags = fctx.flags()
3103 flags = fctx.flags()
3068 mctx = context.memfilectx(
3104 mctx = context.memfilectx(
3069 repo,
3105 repo,
3070 ctx_,
3106 ctx_,
3071 fctx.path(),
3107 fctx.path(),
3072 fctx.data(),
3108 fctx.data(),
3073 islink=b'l' in flags,
3109 islink=b'l' in flags,
3074 isexec=b'x' in flags,
3110 isexec=b'x' in flags,
3075 copysource=copied.get(path),
3111 copysource=copied.get(path),
3076 )
3112 )
3077 return mctx
3113 return mctx
3078 except KeyError:
3114 except KeyError:
3079 return None
3115 return None
3080
3116
3081 else:
3117 else:
3082 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3118 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3083
3119
3084 # Use version of files as in the old cset
3120 # Use version of files as in the old cset
3085 def filectxfn(repo, ctx_, path):
3121 def filectxfn(repo, ctx_, path):
3086 try:
3122 try:
3087 return old.filectx(path)
3123 return old.filectx(path)
3088 except KeyError:
3124 except KeyError:
3089 return None
3125 return None
3090
3126
3091 # See if we got a message from -m or -l, if not, open the editor with
3127 # See if we got a message from -m or -l, if not, open the editor with
3092 # the message of the changeset to amend.
3128 # the message of the changeset to amend.
3093 message = logmessage(ui, opts)
3129 message = logmessage(ui, opts)
3094
3130
3095 editform = mergeeditform(old, b'commit.amend')
3131 editform = mergeeditform(old, b'commit.amend')
3096
3132
3097 if not message:
3133 if not message:
3098 message = old.description()
3134 message = old.description()
3099 # Default if message isn't provided and --edit is not passed is to
3135 # Default if message isn't provided and --edit is not passed is to
3100 # invoke editor, but allow --no-edit. If somehow we don't have any
3136 # invoke editor, but allow --no-edit. If somehow we don't have any
3101 # description, let's always start the editor.
3137 # description, let's always start the editor.
3102 doedit = not message or opts.get(b'edit') in [True, None]
3138 doedit = not message or opts.get(b'edit') in [True, None]
3103 else:
3139 else:
3104 # Default if message is provided is to not invoke editor, but allow
3140 # Default if message is provided is to not invoke editor, but allow
3105 # --edit.
3141 # --edit.
3106 doedit = opts.get(b'edit') is True
3142 doedit = opts.get(b'edit') is True
3107 editor = getcommiteditor(edit=doedit, editform=editform)
3143 editor = getcommiteditor(edit=doedit, editform=editform)
3108
3144
3109 pureextra = extra.copy()
3145 pureextra = extra.copy()
3110 extra[b'amend_source'] = old.hex()
3146 extra[b'amend_source'] = old.hex()
3111
3147
3112 new = context.memctx(
3148 new = context.memctx(
3113 repo,
3149 repo,
3114 parents=[base.node(), old.p2().node()],
3150 parents=[base.node(), old.p2().node()],
3115 text=message,
3151 text=message,
3116 files=files,
3152 files=files,
3117 filectxfn=filectxfn,
3153 filectxfn=filectxfn,
3118 user=user,
3154 user=user,
3119 date=date,
3155 date=date,
3120 extra=extra,
3156 extra=extra,
3121 editor=editor,
3157 editor=editor,
3122 )
3158 )
3123
3159
3124 newdesc = changelog.stripdesc(new.description())
3160 newdesc = changelog.stripdesc(new.description())
3125 if (
3161 if (
3126 (not changes)
3162 (not changes)
3127 and newdesc == old.description()
3163 and newdesc == old.description()
3128 and user == old.user()
3164 and user == old.user()
3129 and (date == old.date() or datemaydiffer)
3165 and (date == old.date() or datemaydiffer)
3130 and pureextra == old.extra()
3166 and pureextra == old.extra()
3131 ):
3167 ):
3132 # nothing changed. continuing here would create a new node
3168 # nothing changed. continuing here would create a new node
3133 # anyway because of the amend_source noise.
3169 # anyway because of the amend_source noise.
3134 #
3170 #
3135 # This not what we expect from amend.
3171 # This not what we expect from amend.
3136 return old.node()
3172 return old.node()
3137
3173
3138 commitphase = None
3174 commitphase = None
3139 if opts.get(b'secret'):
3175 if opts.get(b'secret'):
3140 commitphase = phases.secret
3176 commitphase = phases.secret
3141 newid = repo.commitctx(new)
3177 newid = repo.commitctx(new)
3142
3178
3143 # Reroute the working copy parent to the new changeset
3179 # Reroute the working copy parent to the new changeset
3144 repo.setparents(newid, nullid)
3180 repo.setparents(newid, nullid)
3145 mapping = {old.node(): (newid,)}
3181 mapping = {old.node(): (newid,)}
3146 obsmetadata = None
3182 obsmetadata = None
3147 if opts.get(b'note'):
3183 if opts.get(b'note'):
3148 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3184 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3149 backup = ui.configbool(b'rewrite', b'backup-bundle')
3185 backup = ui.configbool(b'rewrite', b'backup-bundle')
3150 scmutil.cleanupnodes(
3186 scmutil.cleanupnodes(
3151 repo,
3187 repo,
3152 mapping,
3188 mapping,
3153 b'amend',
3189 b'amend',
3154 metadata=obsmetadata,
3190 metadata=obsmetadata,
3155 fixphase=True,
3191 fixphase=True,
3156 targetphase=commitphase,
3192 targetphase=commitphase,
3157 backup=backup,
3193 backup=backup,
3158 )
3194 )
3159
3195
3160 # Fixing the dirstate because localrepo.commitctx does not update
3196 # Fixing the dirstate because localrepo.commitctx does not update
3161 # it. This is rather convenient because we did not need to update
3197 # it. This is rather convenient because we did not need to update
3162 # the dirstate for all the files in the new commit which commitctx
3198 # the dirstate for all the files in the new commit which commitctx
3163 # could have done if it updated the dirstate. Now, we can
3199 # could have done if it updated the dirstate. Now, we can
3164 # selectively update the dirstate only for the amended files.
3200 # selectively update the dirstate only for the amended files.
3165 dirstate = repo.dirstate
3201 dirstate = repo.dirstate
3166
3202
3167 # Update the state of the files which were added and modified in the
3203 # Update the state of the files which were added and modified in the
3168 # amend to "normal" in the dirstate. We need to use "normallookup" since
3204 # amend to "normal" in the dirstate. We need to use "normallookup" since
3169 # the files may have changed since the command started; using "normal"
3205 # the files may have changed since the command started; using "normal"
3170 # would mark them as clean but with uncommitted contents.
3206 # would mark them as clean but with uncommitted contents.
3171 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3207 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3172 for f in normalfiles:
3208 for f in normalfiles:
3173 dirstate.normallookup(f)
3209 dirstate.normallookup(f)
3174
3210
3175 # Update the state of files which were removed in the amend
3211 # Update the state of files which were removed in the amend
3176 # to "removed" in the dirstate.
3212 # to "removed" in the dirstate.
3177 removedfiles = set(wctx.removed()) & filestoamend
3213 removedfiles = set(wctx.removed()) & filestoamend
3178 for f in removedfiles:
3214 for f in removedfiles:
3179 dirstate.drop(f)
3215 dirstate.drop(f)
3180
3216
3181 return newid
3217 return newid
3182
3218
3183
3219
3184 def commiteditor(repo, ctx, subs, editform=b''):
3220 def commiteditor(repo, ctx, subs, editform=b''):
3185 if ctx.description():
3221 if ctx.description():
3186 return ctx.description()
3222 return ctx.description()
3187 return commitforceeditor(
3223 return commitforceeditor(
3188 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3224 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3189 )
3225 )
3190
3226
3191
3227
3192 def commitforceeditor(
3228 def commitforceeditor(
3193 repo,
3229 repo,
3194 ctx,
3230 ctx,
3195 subs,
3231 subs,
3196 finishdesc=None,
3232 finishdesc=None,
3197 extramsg=None,
3233 extramsg=None,
3198 editform=b'',
3234 editform=b'',
3199 unchangedmessagedetection=False,
3235 unchangedmessagedetection=False,
3200 ):
3236 ):
3201 if not extramsg:
3237 if not extramsg:
3202 extramsg = _(b"Leave message empty to abort commit.")
3238 extramsg = _(b"Leave message empty to abort commit.")
3203
3239
3204 forms = [e for e in editform.split(b'.') if e]
3240 forms = [e for e in editform.split(b'.') if e]
3205 forms.insert(0, b'changeset')
3241 forms.insert(0, b'changeset')
3206 templatetext = None
3242 templatetext = None
3207 while forms:
3243 while forms:
3208 ref = b'.'.join(forms)
3244 ref = b'.'.join(forms)
3209 if repo.ui.config(b'committemplate', ref):
3245 if repo.ui.config(b'committemplate', ref):
3210 templatetext = committext = buildcommittemplate(
3246 templatetext = committext = buildcommittemplate(
3211 repo, ctx, subs, extramsg, ref
3247 repo, ctx, subs, extramsg, ref
3212 )
3248 )
3213 break
3249 break
3214 forms.pop()
3250 forms.pop()
3215 else:
3251 else:
3216 committext = buildcommittext(repo, ctx, subs, extramsg)
3252 committext = buildcommittext(repo, ctx, subs, extramsg)
3217
3253
3218 # run editor in the repository root
3254 # run editor in the repository root
3219 olddir = encoding.getcwd()
3255 olddir = encoding.getcwd()
3220 os.chdir(repo.root)
3256 os.chdir(repo.root)
3221
3257
3222 # make in-memory changes visible to external process
3258 # make in-memory changes visible to external process
3223 tr = repo.currenttransaction()
3259 tr = repo.currenttransaction()
3224 repo.dirstate.write(tr)
3260 repo.dirstate.write(tr)
3225 pending = tr and tr.writepending() and repo.root
3261 pending = tr and tr.writepending() and repo.root
3226
3262
3227 editortext = repo.ui.edit(
3263 editortext = repo.ui.edit(
3228 committext,
3264 committext,
3229 ctx.user(),
3265 ctx.user(),
3230 ctx.extra(),
3266 ctx.extra(),
3231 editform=editform,
3267 editform=editform,
3232 pending=pending,
3268 pending=pending,
3233 repopath=repo.path,
3269 repopath=repo.path,
3234 action=b'commit',
3270 action=b'commit',
3235 )
3271 )
3236 text = editortext
3272 text = editortext
3237
3273
3238 # strip away anything below this special string (used for editors that want
3274 # strip away anything below this special string (used for editors that want
3239 # to display the diff)
3275 # to display the diff)
3240 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3276 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3241 if stripbelow:
3277 if stripbelow:
3242 text = text[: stripbelow.start()]
3278 text = text[: stripbelow.start()]
3243
3279
3244 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3280 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3245 os.chdir(olddir)
3281 os.chdir(olddir)
3246
3282
3247 if finishdesc:
3283 if finishdesc:
3248 text = finishdesc(text)
3284 text = finishdesc(text)
3249 if not text.strip():
3285 if not text.strip():
3250 raise error.Abort(_(b"empty commit message"))
3286 raise error.Abort(_(b"empty commit message"))
3251 if unchangedmessagedetection and editortext == templatetext:
3287 if unchangedmessagedetection and editortext == templatetext:
3252 raise error.Abort(_(b"commit message unchanged"))
3288 raise error.Abort(_(b"commit message unchanged"))
3253
3289
3254 return text
3290 return text
3255
3291
3256
3292
3257 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3293 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3258 ui = repo.ui
3294 ui = repo.ui
3259 spec = formatter.templatespec(ref, None, None)
3295 spec = formatter.templatespec(ref, None, None)
3260 t = logcmdutil.changesettemplater(ui, repo, spec)
3296 t = logcmdutil.changesettemplater(ui, repo, spec)
3261 t.t.cache.update(
3297 t.t.cache.update(
3262 (k, templater.unquotestring(v))
3298 (k, templater.unquotestring(v))
3263 for k, v in repo.ui.configitems(b'committemplate')
3299 for k, v in repo.ui.configitems(b'committemplate')
3264 )
3300 )
3265
3301
3266 if not extramsg:
3302 if not extramsg:
3267 extramsg = b'' # ensure that extramsg is string
3303 extramsg = b'' # ensure that extramsg is string
3268
3304
3269 ui.pushbuffer()
3305 ui.pushbuffer()
3270 t.show(ctx, extramsg=extramsg)
3306 t.show(ctx, extramsg=extramsg)
3271 return ui.popbuffer()
3307 return ui.popbuffer()
3272
3308
3273
3309
3274 def hgprefix(msg):
3310 def hgprefix(msg):
3275 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3311 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3276
3312
3277
3313
3278 def buildcommittext(repo, ctx, subs, extramsg):
3314 def buildcommittext(repo, ctx, subs, extramsg):
3279 edittext = []
3315 edittext = []
3280 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3316 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3281 if ctx.description():
3317 if ctx.description():
3282 edittext.append(ctx.description())
3318 edittext.append(ctx.description())
3283 edittext.append(b"")
3319 edittext.append(b"")
3284 edittext.append(b"") # Empty line between message and comments.
3320 edittext.append(b"") # Empty line between message and comments.
3285 edittext.append(
3321 edittext.append(
3286 hgprefix(
3322 hgprefix(
3287 _(
3323 _(
3288 b"Enter commit message."
3324 b"Enter commit message."
3289 b" Lines beginning with 'HG:' are removed."
3325 b" Lines beginning with 'HG:' are removed."
3290 )
3326 )
3291 )
3327 )
3292 )
3328 )
3293 edittext.append(hgprefix(extramsg))
3329 edittext.append(hgprefix(extramsg))
3294 edittext.append(b"HG: --")
3330 edittext.append(b"HG: --")
3295 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3331 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3296 if ctx.p2():
3332 if ctx.p2():
3297 edittext.append(hgprefix(_(b"branch merge")))
3333 edittext.append(hgprefix(_(b"branch merge")))
3298 if ctx.branch():
3334 if ctx.branch():
3299 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3335 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3300 if bookmarks.isactivewdirparent(repo):
3336 if bookmarks.isactivewdirparent(repo):
3301 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3337 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3302 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3338 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3303 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3339 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3304 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3340 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3305 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3341 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3306 if not added and not modified and not removed:
3342 if not added and not modified and not removed:
3307 edittext.append(hgprefix(_(b"no files changed")))
3343 edittext.append(hgprefix(_(b"no files changed")))
3308 edittext.append(b"")
3344 edittext.append(b"")
3309
3345
3310 return b"\n".join(edittext)
3346 return b"\n".join(edittext)
3311
3347
3312
3348
3313 def commitstatus(repo, node, branch, bheads=None, opts=None):
3349 def commitstatus(repo, node, branch, bheads=None, opts=None):
3314 if opts is None:
3350 if opts is None:
3315 opts = {}
3351 opts = {}
3316 ctx = repo[node]
3352 ctx = repo[node]
3317 parents = ctx.parents()
3353 parents = ctx.parents()
3318
3354
3319 if (
3355 if (
3320 not opts.get(b'amend')
3356 not opts.get(b'amend')
3321 and bheads
3357 and bheads
3322 and node not in bheads
3358 and node not in bheads
3323 and not [
3359 and not [
3324 x for x in parents if x.node() in bheads and x.branch() == branch
3360 x for x in parents if x.node() in bheads and x.branch() == branch
3325 ]
3361 ]
3326 ):
3362 ):
3327 repo.ui.status(_(b'created new head\n'))
3363 repo.ui.status(_(b'created new head\n'))
3328 # The message is not printed for initial roots. For the other
3364 # The message is not printed for initial roots. For the other
3329 # changesets, it is printed in the following situations:
3365 # changesets, it is printed in the following situations:
3330 #
3366 #
3331 # Par column: for the 2 parents with ...
3367 # Par column: for the 2 parents with ...
3332 # N: null or no parent
3368 # N: null or no parent
3333 # B: parent is on another named branch
3369 # B: parent is on another named branch
3334 # C: parent is a regular non head changeset
3370 # C: parent is a regular non head changeset
3335 # H: parent was a branch head of the current branch
3371 # H: parent was a branch head of the current branch
3336 # Msg column: whether we print "created new head" message
3372 # Msg column: whether we print "created new head" message
3337 # In the following, it is assumed that there already exists some
3373 # In the following, it is assumed that there already exists some
3338 # initial branch heads of the current branch, otherwise nothing is
3374 # initial branch heads of the current branch, otherwise nothing is
3339 # printed anyway.
3375 # printed anyway.
3340 #
3376 #
3341 # Par Msg Comment
3377 # Par Msg Comment
3342 # N N y additional topo root
3378 # N N y additional topo root
3343 #
3379 #
3344 # B N y additional branch root
3380 # B N y additional branch root
3345 # C N y additional topo head
3381 # C N y additional topo head
3346 # H N n usual case
3382 # H N n usual case
3347 #
3383 #
3348 # B B y weird additional branch root
3384 # B B y weird additional branch root
3349 # C B y branch merge
3385 # C B y branch merge
3350 # H B n merge with named branch
3386 # H B n merge with named branch
3351 #
3387 #
3352 # C C y additional head from merge
3388 # C C y additional head from merge
3353 # C H n merge with a head
3389 # C H n merge with a head
3354 #
3390 #
3355 # H H n head merge: head count decreases
3391 # H H n head merge: head count decreases
3356
3392
3357 if not opts.get(b'close_branch'):
3393 if not opts.get(b'close_branch'):
3358 for r in parents:
3394 for r in parents:
3359 if r.closesbranch() and r.branch() == branch:
3395 if r.closesbranch() and r.branch() == branch:
3360 repo.ui.status(
3396 repo.ui.status(
3361 _(b'reopening closed branch head %d\n') % r.rev()
3397 _(b'reopening closed branch head %d\n') % r.rev()
3362 )
3398 )
3363
3399
3364 if repo.ui.debugflag:
3400 if repo.ui.debugflag:
3365 repo.ui.write(
3401 repo.ui.write(
3366 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3402 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3367 )
3403 )
3368 elif repo.ui.verbose:
3404 elif repo.ui.verbose:
3369 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3405 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3370
3406
3371
3407
3372 def postcommitstatus(repo, pats, opts):
3408 def postcommitstatus(repo, pats, opts):
3373 return repo.status(match=scmutil.match(repo[None], pats, opts))
3409 return repo.status(match=scmutil.match(repo[None], pats, opts))
3374
3410
3375
3411
3376 def revert(ui, repo, ctx, parents, *pats, **opts):
3412 def revert(ui, repo, ctx, parents, *pats, **opts):
3377 opts = pycompat.byteskwargs(opts)
3413 opts = pycompat.byteskwargs(opts)
3378 parent, p2 = parents
3414 parent, p2 = parents
3379 node = ctx.node()
3415 node = ctx.node()
3380
3416
3381 mf = ctx.manifest()
3417 mf = ctx.manifest()
3382 if node == p2:
3418 if node == p2:
3383 parent = p2
3419 parent = p2
3384
3420
3385 # need all matching names in dirstate and manifest of target rev,
3421 # need all matching names in dirstate and manifest of target rev,
3386 # so have to walk both. do not print errors if files exist in one
3422 # so have to walk both. do not print errors if files exist in one
3387 # but not other. in both cases, filesets should be evaluated against
3423 # but not other. in both cases, filesets should be evaluated against
3388 # workingctx to get consistent result (issue4497). this means 'set:**'
3424 # workingctx to get consistent result (issue4497). this means 'set:**'
3389 # cannot be used to select missing files from target rev.
3425 # cannot be used to select missing files from target rev.
3390
3426
3391 # `names` is a mapping for all elements in working copy and target revision
3427 # `names` is a mapping for all elements in working copy and target revision
3392 # The mapping is in the form:
3428 # The mapping is in the form:
3393 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3429 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3394 names = {}
3430 names = {}
3395 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3431 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3396
3432
3397 with repo.wlock():
3433 with repo.wlock():
3398 ## filling of the `names` mapping
3434 ## filling of the `names` mapping
3399 # walk dirstate to fill `names`
3435 # walk dirstate to fill `names`
3400
3436
3401 interactive = opts.get(b'interactive', False)
3437 interactive = opts.get(b'interactive', False)
3402 wctx = repo[None]
3438 wctx = repo[None]
3403 m = scmutil.match(wctx, pats, opts)
3439 m = scmutil.match(wctx, pats, opts)
3404
3440
3405 # we'll need this later
3441 # we'll need this later
3406 targetsubs = sorted(s for s in wctx.substate if m(s))
3442 targetsubs = sorted(s for s in wctx.substate if m(s))
3407
3443
3408 if not m.always():
3444 if not m.always():
3409 matcher = matchmod.badmatch(m, lambda x, y: False)
3445 matcher = matchmod.badmatch(m, lambda x, y: False)
3410 for abs in wctx.walk(matcher):
3446 for abs in wctx.walk(matcher):
3411 names[abs] = m.exact(abs)
3447 names[abs] = m.exact(abs)
3412
3448
3413 # walk target manifest to fill `names`
3449 # walk target manifest to fill `names`
3414
3450
3415 def badfn(path, msg):
3451 def badfn(path, msg):
3416 if path in names:
3452 if path in names:
3417 return
3453 return
3418 if path in ctx.substate:
3454 if path in ctx.substate:
3419 return
3455 return
3420 path_ = path + b'/'
3456 path_ = path + b'/'
3421 for f in names:
3457 for f in names:
3422 if f.startswith(path_):
3458 if f.startswith(path_):
3423 return
3459 return
3424 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3460 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3425
3461
3426 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3462 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3427 if abs not in names:
3463 if abs not in names:
3428 names[abs] = m.exact(abs)
3464 names[abs] = m.exact(abs)
3429
3465
3430 # Find status of all file in `names`.
3466 # Find status of all file in `names`.
3431 m = scmutil.matchfiles(repo, names)
3467 m = scmutil.matchfiles(repo, names)
3432
3468
3433 changes = repo.status(
3469 changes = repo.status(
3434 node1=node, match=m, unknown=True, ignored=True, clean=True
3470 node1=node, match=m, unknown=True, ignored=True, clean=True
3435 )
3471 )
3436 else:
3472 else:
3437 changes = repo.status(node1=node, match=m)
3473 changes = repo.status(node1=node, match=m)
3438 for kind in changes:
3474 for kind in changes:
3439 for abs in kind:
3475 for abs in kind:
3440 names[abs] = m.exact(abs)
3476 names[abs] = m.exact(abs)
3441
3477
3442 m = scmutil.matchfiles(repo, names)
3478 m = scmutil.matchfiles(repo, names)
3443
3479
3444 modified = set(changes.modified)
3480 modified = set(changes.modified)
3445 added = set(changes.added)
3481 added = set(changes.added)
3446 removed = set(changes.removed)
3482 removed = set(changes.removed)
3447 _deleted = set(changes.deleted)
3483 _deleted = set(changes.deleted)
3448 unknown = set(changes.unknown)
3484 unknown = set(changes.unknown)
3449 unknown.update(changes.ignored)
3485 unknown.update(changes.ignored)
3450 clean = set(changes.clean)
3486 clean = set(changes.clean)
3451 modadded = set()
3487 modadded = set()
3452
3488
3453 # We need to account for the state of the file in the dirstate,
3489 # We need to account for the state of the file in the dirstate,
3454 # even when we revert against something else than parent. This will
3490 # even when we revert against something else than parent. This will
3455 # slightly alter the behavior of revert (doing back up or not, delete
3491 # slightly alter the behavior of revert (doing back up or not, delete
3456 # or just forget etc).
3492 # or just forget etc).
3457 if parent == node:
3493 if parent == node:
3458 dsmodified = modified
3494 dsmodified = modified
3459 dsadded = added
3495 dsadded = added
3460 dsremoved = removed
3496 dsremoved = removed
3461 # store all local modifications, useful later for rename detection
3497 # store all local modifications, useful later for rename detection
3462 localchanges = dsmodified | dsadded
3498 localchanges = dsmodified | dsadded
3463 modified, added, removed = set(), set(), set()
3499 modified, added, removed = set(), set(), set()
3464 else:
3500 else:
3465 changes = repo.status(node1=parent, match=m)
3501 changes = repo.status(node1=parent, match=m)
3466 dsmodified = set(changes.modified)
3502 dsmodified = set(changes.modified)
3467 dsadded = set(changes.added)
3503 dsadded = set(changes.added)
3468 dsremoved = set(changes.removed)
3504 dsremoved = set(changes.removed)
3469 # store all local modifications, useful later for rename detection
3505 # store all local modifications, useful later for rename detection
3470 localchanges = dsmodified | dsadded
3506 localchanges = dsmodified | dsadded
3471
3507
3472 # only take into account for removes between wc and target
3508 # only take into account for removes between wc and target
3473 clean |= dsremoved - removed
3509 clean |= dsremoved - removed
3474 dsremoved &= removed
3510 dsremoved &= removed
3475 # distinct between dirstate remove and other
3511 # distinct between dirstate remove and other
3476 removed -= dsremoved
3512 removed -= dsremoved
3477
3513
3478 modadded = added & dsmodified
3514 modadded = added & dsmodified
3479 added -= modadded
3515 added -= modadded
3480
3516
3481 # tell newly modified apart.
3517 # tell newly modified apart.
3482 dsmodified &= modified
3518 dsmodified &= modified
3483 dsmodified |= modified & dsadded # dirstate added may need backup
3519 dsmodified |= modified & dsadded # dirstate added may need backup
3484 modified -= dsmodified
3520 modified -= dsmodified
3485
3521
3486 # We need to wait for some post-processing to update this set
3522 # We need to wait for some post-processing to update this set
3487 # before making the distinction. The dirstate will be used for
3523 # before making the distinction. The dirstate will be used for
3488 # that purpose.
3524 # that purpose.
3489 dsadded = added
3525 dsadded = added
3490
3526
3491 # in case of merge, files that are actually added can be reported as
3527 # in case of merge, files that are actually added can be reported as
3492 # modified, we need to post process the result
3528 # modified, we need to post process the result
3493 if p2 != nullid:
3529 if p2 != nullid:
3494 mergeadd = set(dsmodified)
3530 mergeadd = set(dsmodified)
3495 for path in dsmodified:
3531 for path in dsmodified:
3496 if path in mf:
3532 if path in mf:
3497 mergeadd.remove(path)
3533 mergeadd.remove(path)
3498 dsadded |= mergeadd
3534 dsadded |= mergeadd
3499 dsmodified -= mergeadd
3535 dsmodified -= mergeadd
3500
3536
3501 # if f is a rename, update `names` to also revert the source
3537 # if f is a rename, update `names` to also revert the source
3502 for f in localchanges:
3538 for f in localchanges:
3503 src = repo.dirstate.copied(f)
3539 src = repo.dirstate.copied(f)
3504 # XXX should we check for rename down to target node?
3540 # XXX should we check for rename down to target node?
3505 if src and src not in names and repo.dirstate[src] == b'r':
3541 if src and src not in names and repo.dirstate[src] == b'r':
3506 dsremoved.add(src)
3542 dsremoved.add(src)
3507 names[src] = True
3543 names[src] = True
3508
3544
3509 # determine the exact nature of the deleted changesets
3545 # determine the exact nature of the deleted changesets
3510 deladded = set(_deleted)
3546 deladded = set(_deleted)
3511 for path in _deleted:
3547 for path in _deleted:
3512 if path in mf:
3548 if path in mf:
3513 deladded.remove(path)
3549 deladded.remove(path)
3514 deleted = _deleted - deladded
3550 deleted = _deleted - deladded
3515
3551
3516 # distinguish between file to forget and the other
3552 # distinguish between file to forget and the other
3517 added = set()
3553 added = set()
3518 for abs in dsadded:
3554 for abs in dsadded:
3519 if repo.dirstate[abs] != b'a':
3555 if repo.dirstate[abs] != b'a':
3520 added.add(abs)
3556 added.add(abs)
3521 dsadded -= added
3557 dsadded -= added
3522
3558
3523 for abs in deladded:
3559 for abs in deladded:
3524 if repo.dirstate[abs] == b'a':
3560 if repo.dirstate[abs] == b'a':
3525 dsadded.add(abs)
3561 dsadded.add(abs)
3526 deladded -= dsadded
3562 deladded -= dsadded
3527
3563
3528 # For files marked as removed, we check if an unknown file is present at
3564 # For files marked as removed, we check if an unknown file is present at
3529 # the same path. If a such file exists it may need to be backed up.
3565 # the same path. If a such file exists it may need to be backed up.
3530 # Making the distinction at this stage helps have simpler backup
3566 # Making the distinction at this stage helps have simpler backup
3531 # logic.
3567 # logic.
3532 removunk = set()
3568 removunk = set()
3533 for abs in removed:
3569 for abs in removed:
3534 target = repo.wjoin(abs)
3570 target = repo.wjoin(abs)
3535 if os.path.lexists(target):
3571 if os.path.lexists(target):
3536 removunk.add(abs)
3572 removunk.add(abs)
3537 removed -= removunk
3573 removed -= removunk
3538
3574
3539 dsremovunk = set()
3575 dsremovunk = set()
3540 for abs in dsremoved:
3576 for abs in dsremoved:
3541 target = repo.wjoin(abs)
3577 target = repo.wjoin(abs)
3542 if os.path.lexists(target):
3578 if os.path.lexists(target):
3543 dsremovunk.add(abs)
3579 dsremovunk.add(abs)
3544 dsremoved -= dsremovunk
3580 dsremoved -= dsremovunk
3545
3581
3546 # action to be actually performed by revert
3582 # action to be actually performed by revert
3547 # (<list of file>, message>) tuple
3583 # (<list of file>, message>) tuple
3548 actions = {
3584 actions = {
3549 b'revert': ([], _(b'reverting %s\n')),
3585 b'revert': ([], _(b'reverting %s\n')),
3550 b'add': ([], _(b'adding %s\n')),
3586 b'add': ([], _(b'adding %s\n')),
3551 b'remove': ([], _(b'removing %s\n')),
3587 b'remove': ([], _(b'removing %s\n')),
3552 b'drop': ([], _(b'removing %s\n')),
3588 b'drop': ([], _(b'removing %s\n')),
3553 b'forget': ([], _(b'forgetting %s\n')),
3589 b'forget': ([], _(b'forgetting %s\n')),
3554 b'undelete': ([], _(b'undeleting %s\n')),
3590 b'undelete': ([], _(b'undeleting %s\n')),
3555 b'noop': (None, _(b'no changes needed to %s\n')),
3591 b'noop': (None, _(b'no changes needed to %s\n')),
3556 b'unknown': (None, _(b'file not managed: %s\n')),
3592 b'unknown': (None, _(b'file not managed: %s\n')),
3557 }
3593 }
3558
3594
3559 # "constant" that convey the backup strategy.
3595 # "constant" that convey the backup strategy.
3560 # All set to `discard` if `no-backup` is set do avoid checking
3596 # All set to `discard` if `no-backup` is set do avoid checking
3561 # no_backup lower in the code.
3597 # no_backup lower in the code.
3562 # These values are ordered for comparison purposes
3598 # These values are ordered for comparison purposes
3563 backupinteractive = 3 # do backup if interactively modified
3599 backupinteractive = 3 # do backup if interactively modified
3564 backup = 2 # unconditionally do backup
3600 backup = 2 # unconditionally do backup
3565 check = 1 # check if the existing file differs from target
3601 check = 1 # check if the existing file differs from target
3566 discard = 0 # never do backup
3602 discard = 0 # never do backup
3567 if opts.get(b'no_backup'):
3603 if opts.get(b'no_backup'):
3568 backupinteractive = backup = check = discard
3604 backupinteractive = backup = check = discard
3569 if interactive:
3605 if interactive:
3570 dsmodifiedbackup = backupinteractive
3606 dsmodifiedbackup = backupinteractive
3571 else:
3607 else:
3572 dsmodifiedbackup = backup
3608 dsmodifiedbackup = backup
3573 tobackup = set()
3609 tobackup = set()
3574
3610
3575 backupanddel = actions[b'remove']
3611 backupanddel = actions[b'remove']
3576 if not opts.get(b'no_backup'):
3612 if not opts.get(b'no_backup'):
3577 backupanddel = actions[b'drop']
3613 backupanddel = actions[b'drop']
3578
3614
3579 disptable = (
3615 disptable = (
3580 # dispatch table:
3616 # dispatch table:
3581 # file state
3617 # file state
3582 # action
3618 # action
3583 # make backup
3619 # make backup
3584 ## Sets that results that will change file on disk
3620 ## Sets that results that will change file on disk
3585 # Modified compared to target, no local change
3621 # Modified compared to target, no local change
3586 (modified, actions[b'revert'], discard),
3622 (modified, actions[b'revert'], discard),
3587 # Modified compared to target, but local file is deleted
3623 # Modified compared to target, but local file is deleted
3588 (deleted, actions[b'revert'], discard),
3624 (deleted, actions[b'revert'], discard),
3589 # Modified compared to target, local change
3625 # Modified compared to target, local change
3590 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3626 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3591 # Added since target
3627 # Added since target
3592 (added, actions[b'remove'], discard),
3628 (added, actions[b'remove'], discard),
3593 # Added in working directory
3629 # Added in working directory
3594 (dsadded, actions[b'forget'], discard),
3630 (dsadded, actions[b'forget'], discard),
3595 # Added since target, have local modification
3631 # Added since target, have local modification
3596 (modadded, backupanddel, backup),
3632 (modadded, backupanddel, backup),
3597 # Added since target but file is missing in working directory
3633 # Added since target but file is missing in working directory
3598 (deladded, actions[b'drop'], discard),
3634 (deladded, actions[b'drop'], discard),
3599 # Removed since target, before working copy parent
3635 # Removed since target, before working copy parent
3600 (removed, actions[b'add'], discard),
3636 (removed, actions[b'add'], discard),
3601 # Same as `removed` but an unknown file exists at the same path
3637 # Same as `removed` but an unknown file exists at the same path
3602 (removunk, actions[b'add'], check),
3638 (removunk, actions[b'add'], check),
3603 # Removed since targe, marked as such in working copy parent
3639 # Removed since targe, marked as such in working copy parent
3604 (dsremoved, actions[b'undelete'], discard),
3640 (dsremoved, actions[b'undelete'], discard),
3605 # Same as `dsremoved` but an unknown file exists at the same path
3641 # Same as `dsremoved` but an unknown file exists at the same path
3606 (dsremovunk, actions[b'undelete'], check),
3642 (dsremovunk, actions[b'undelete'], check),
3607 ## the following sets does not result in any file changes
3643 ## the following sets does not result in any file changes
3608 # File with no modification
3644 # File with no modification
3609 (clean, actions[b'noop'], discard),
3645 (clean, actions[b'noop'], discard),
3610 # Existing file, not tracked anywhere
3646 # Existing file, not tracked anywhere
3611 (unknown, actions[b'unknown'], discard),
3647 (unknown, actions[b'unknown'], discard),
3612 )
3648 )
3613
3649
3614 for abs, exact in sorted(names.items()):
3650 for abs, exact in sorted(names.items()):
3615 # target file to be touch on disk (relative to cwd)
3651 # target file to be touch on disk (relative to cwd)
3616 target = repo.wjoin(abs)
3652 target = repo.wjoin(abs)
3617 # search the entry in the dispatch table.
3653 # search the entry in the dispatch table.
3618 # if the file is in any of these sets, it was touched in the working
3654 # if the file is in any of these sets, it was touched in the working
3619 # directory parent and we are sure it needs to be reverted.
3655 # directory parent and we are sure it needs to be reverted.
3620 for table, (xlist, msg), dobackup in disptable:
3656 for table, (xlist, msg), dobackup in disptable:
3621 if abs not in table:
3657 if abs not in table:
3622 continue
3658 continue
3623 if xlist is not None:
3659 if xlist is not None:
3624 xlist.append(abs)
3660 xlist.append(abs)
3625 if dobackup:
3661 if dobackup:
3626 # If in interactive mode, don't automatically create
3662 # If in interactive mode, don't automatically create
3627 # .orig files (issue4793)
3663 # .orig files (issue4793)
3628 if dobackup == backupinteractive:
3664 if dobackup == backupinteractive:
3629 tobackup.add(abs)
3665 tobackup.add(abs)
3630 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3666 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3631 absbakname = scmutil.backuppath(ui, repo, abs)
3667 absbakname = scmutil.backuppath(ui, repo, abs)
3632 bakname = os.path.relpath(
3668 bakname = os.path.relpath(
3633 absbakname, start=repo.root
3669 absbakname, start=repo.root
3634 )
3670 )
3635 ui.note(
3671 ui.note(
3636 _(b'saving current version of %s as %s\n')
3672 _(b'saving current version of %s as %s\n')
3637 % (uipathfn(abs), uipathfn(bakname))
3673 % (uipathfn(abs), uipathfn(bakname))
3638 )
3674 )
3639 if not opts.get(b'dry_run'):
3675 if not opts.get(b'dry_run'):
3640 if interactive:
3676 if interactive:
3641 util.copyfile(target, absbakname)
3677 util.copyfile(target, absbakname)
3642 else:
3678 else:
3643 util.rename(target, absbakname)
3679 util.rename(target, absbakname)
3644 if opts.get(b'dry_run'):
3680 if opts.get(b'dry_run'):
3645 if ui.verbose or not exact:
3681 if ui.verbose or not exact:
3646 ui.status(msg % uipathfn(abs))
3682 ui.status(msg % uipathfn(abs))
3647 elif exact:
3683 elif exact:
3648 ui.warn(msg % uipathfn(abs))
3684 ui.warn(msg % uipathfn(abs))
3649 break
3685 break
3650
3686
3651 if not opts.get(b'dry_run'):
3687 if not opts.get(b'dry_run'):
3652 needdata = (b'revert', b'add', b'undelete')
3688 needdata = (b'revert', b'add', b'undelete')
3653 oplist = [actions[name][0] for name in needdata]
3689 oplist = [actions[name][0] for name in needdata]
3654 prefetch = scmutil.prefetchfiles
3690 prefetch = scmutil.prefetchfiles
3655 matchfiles = scmutil.matchfiles
3691 matchfiles = scmutil.matchfiles
3656 prefetch(
3692 prefetch(
3657 repo,
3693 repo,
3658 [ctx.rev()],
3694 [ctx.rev()],
3659 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3695 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3660 )
3696 )
3661 match = scmutil.match(repo[None], pats)
3697 match = scmutil.match(repo[None], pats)
3662 _performrevert(
3698 _performrevert(
3663 repo,
3699 repo,
3664 parents,
3700 parents,
3665 ctx,
3701 ctx,
3666 names,
3702 names,
3667 uipathfn,
3703 uipathfn,
3668 actions,
3704 actions,
3669 match,
3705 match,
3670 interactive,
3706 interactive,
3671 tobackup,
3707 tobackup,
3672 )
3708 )
3673
3709
3674 if targetsubs:
3710 if targetsubs:
3675 # Revert the subrepos on the revert list
3711 # Revert the subrepos on the revert list
3676 for sub in targetsubs:
3712 for sub in targetsubs:
3677 try:
3713 try:
3678 wctx.sub(sub).revert(
3714 wctx.sub(sub).revert(
3679 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3715 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3680 )
3716 )
3681 except KeyError:
3717 except KeyError:
3682 raise error.Abort(
3718 raise error.Abort(
3683 b"subrepository '%s' does not exist in %s!"
3719 b"subrepository '%s' does not exist in %s!"
3684 % (sub, short(ctx.node()))
3720 % (sub, short(ctx.node()))
3685 )
3721 )
3686
3722
3687
3723
3688 def _performrevert(
3724 def _performrevert(
3689 repo,
3725 repo,
3690 parents,
3726 parents,
3691 ctx,
3727 ctx,
3692 names,
3728 names,
3693 uipathfn,
3729 uipathfn,
3694 actions,
3730 actions,
3695 match,
3731 match,
3696 interactive=False,
3732 interactive=False,
3697 tobackup=None,
3733 tobackup=None,
3698 ):
3734 ):
3699 """function that actually perform all the actions computed for revert
3735 """function that actually perform all the actions computed for revert
3700
3736
3701 This is an independent function to let extension to plug in and react to
3737 This is an independent function to let extension to plug in and react to
3702 the imminent revert.
3738 the imminent revert.
3703
3739
3704 Make sure you have the working directory locked when calling this function.
3740 Make sure you have the working directory locked when calling this function.
3705 """
3741 """
3706 parent, p2 = parents
3742 parent, p2 = parents
3707 node = ctx.node()
3743 node = ctx.node()
3708 excluded_files = []
3744 excluded_files = []
3709
3745
3710 def checkout(f):
3746 def checkout(f):
3711 fc = ctx[f]
3747 fc = ctx[f]
3712 repo.wwrite(f, fc.data(), fc.flags())
3748 repo.wwrite(f, fc.data(), fc.flags())
3713
3749
3714 def doremove(f):
3750 def doremove(f):
3715 try:
3751 try:
3716 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3752 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3717 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3753 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3718 except OSError:
3754 except OSError:
3719 pass
3755 pass
3720 repo.dirstate.remove(f)
3756 repo.dirstate.remove(f)
3721
3757
3722 def prntstatusmsg(action, f):
3758 def prntstatusmsg(action, f):
3723 exact = names[f]
3759 exact = names[f]
3724 if repo.ui.verbose or not exact:
3760 if repo.ui.verbose or not exact:
3725 repo.ui.status(actions[action][1] % uipathfn(f))
3761 repo.ui.status(actions[action][1] % uipathfn(f))
3726
3762
3727 audit_path = pathutil.pathauditor(repo.root, cached=True)
3763 audit_path = pathutil.pathauditor(repo.root, cached=True)
3728 for f in actions[b'forget'][0]:
3764 for f in actions[b'forget'][0]:
3729 if interactive:
3765 if interactive:
3730 choice = repo.ui.promptchoice(
3766 choice = repo.ui.promptchoice(
3731 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3767 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3732 )
3768 )
3733 if choice == 0:
3769 if choice == 0:
3734 prntstatusmsg(b'forget', f)
3770 prntstatusmsg(b'forget', f)
3735 repo.dirstate.drop(f)
3771 repo.dirstate.drop(f)
3736 else:
3772 else:
3737 excluded_files.append(f)
3773 excluded_files.append(f)
3738 else:
3774 else:
3739 prntstatusmsg(b'forget', f)
3775 prntstatusmsg(b'forget', f)
3740 repo.dirstate.drop(f)
3776 repo.dirstate.drop(f)
3741 for f in actions[b'remove'][0]:
3777 for f in actions[b'remove'][0]:
3742 audit_path(f)
3778 audit_path(f)
3743 if interactive:
3779 if interactive:
3744 choice = repo.ui.promptchoice(
3780 choice = repo.ui.promptchoice(
3745 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3781 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3746 )
3782 )
3747 if choice == 0:
3783 if choice == 0:
3748 prntstatusmsg(b'remove', f)
3784 prntstatusmsg(b'remove', f)
3749 doremove(f)
3785 doremove(f)
3750 else:
3786 else:
3751 excluded_files.append(f)
3787 excluded_files.append(f)
3752 else:
3788 else:
3753 prntstatusmsg(b'remove', f)
3789 prntstatusmsg(b'remove', f)
3754 doremove(f)
3790 doremove(f)
3755 for f in actions[b'drop'][0]:
3791 for f in actions[b'drop'][0]:
3756 audit_path(f)
3792 audit_path(f)
3757 prntstatusmsg(b'drop', f)
3793 prntstatusmsg(b'drop', f)
3758 repo.dirstate.remove(f)
3794 repo.dirstate.remove(f)
3759
3795
3760 normal = None
3796 normal = None
3761 if node == parent:
3797 if node == parent:
3762 # We're reverting to our parent. If possible, we'd like status
3798 # We're reverting to our parent. If possible, we'd like status
3763 # to report the file as clean. We have to use normallookup for
3799 # to report the file as clean. We have to use normallookup for
3764 # merges to avoid losing information about merged/dirty files.
3800 # merges to avoid losing information about merged/dirty files.
3765 if p2 != nullid:
3801 if p2 != nullid:
3766 normal = repo.dirstate.normallookup
3802 normal = repo.dirstate.normallookup
3767 else:
3803 else:
3768 normal = repo.dirstate.normal
3804 normal = repo.dirstate.normal
3769
3805
3770 newlyaddedandmodifiedfiles = set()
3806 newlyaddedandmodifiedfiles = set()
3771 if interactive:
3807 if interactive:
3772 # Prompt the user for changes to revert
3808 # Prompt the user for changes to revert
3773 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3809 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3774 m = scmutil.matchfiles(repo, torevert)
3810 m = scmutil.matchfiles(repo, torevert)
3775 diffopts = patch.difffeatureopts(
3811 diffopts = patch.difffeatureopts(
3776 repo.ui,
3812 repo.ui,
3777 whitespace=True,
3813 whitespace=True,
3778 section=b'commands',
3814 section=b'commands',
3779 configprefix=b'revert.interactive.',
3815 configprefix=b'revert.interactive.',
3780 )
3816 )
3781 diffopts.nodates = True
3817 diffopts.nodates = True
3782 diffopts.git = True
3818 diffopts.git = True
3783 operation = b'apply'
3819 operation = b'apply'
3784 if node == parent:
3820 if node == parent:
3785 if repo.ui.configbool(
3821 if repo.ui.configbool(
3786 b'experimental', b'revert.interactive.select-to-keep'
3822 b'experimental', b'revert.interactive.select-to-keep'
3787 ):
3823 ):
3788 operation = b'keep'
3824 operation = b'keep'
3789 else:
3825 else:
3790 operation = b'discard'
3826 operation = b'discard'
3791
3827
3792 if operation == b'apply':
3828 if operation == b'apply':
3793 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3829 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3794 else:
3830 else:
3795 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3831 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3796 originalchunks = patch.parsepatch(diff)
3832 originalchunks = patch.parsepatch(diff)
3797
3833
3798 try:
3834 try:
3799
3835
3800 chunks, opts = recordfilter(
3836 chunks, opts = recordfilter(
3801 repo.ui, originalchunks, match, operation=operation
3837 repo.ui, originalchunks, match, operation=operation
3802 )
3838 )
3803 if operation == b'discard':
3839 if operation == b'discard':
3804 chunks = patch.reversehunks(chunks)
3840 chunks = patch.reversehunks(chunks)
3805
3841
3806 except error.PatchError as err:
3842 except error.PatchError as err:
3807 raise error.Abort(_(b'error parsing patch: %s') % err)
3843 raise error.Abort(_(b'error parsing patch: %s') % err)
3808
3844
3809 # FIXME: when doing an interactive revert of a copy, there's no way of
3845 # FIXME: when doing an interactive revert of a copy, there's no way of
3810 # performing a partial revert of the added file, the only option is
3846 # performing a partial revert of the added file, the only option is
3811 # "remove added file <name> (Yn)?", so we don't need to worry about the
3847 # "remove added file <name> (Yn)?", so we don't need to worry about the
3812 # alsorestore value. Ideally we'd be able to partially revert
3848 # alsorestore value. Ideally we'd be able to partially revert
3813 # copied/renamed files.
3849 # copied/renamed files.
3814 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3850 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3815 chunks, originalchunks
3851 chunks, originalchunks
3816 )
3852 )
3817 if tobackup is None:
3853 if tobackup is None:
3818 tobackup = set()
3854 tobackup = set()
3819 # Apply changes
3855 # Apply changes
3820 fp = stringio()
3856 fp = stringio()
3821 # chunks are serialized per file, but files aren't sorted
3857 # chunks are serialized per file, but files aren't sorted
3822 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3858 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3823 prntstatusmsg(b'revert', f)
3859 prntstatusmsg(b'revert', f)
3824 files = set()
3860 files = set()
3825 for c in chunks:
3861 for c in chunks:
3826 if ishunk(c):
3862 if ishunk(c):
3827 abs = c.header.filename()
3863 abs = c.header.filename()
3828 # Create a backup file only if this hunk should be backed up
3864 # Create a backup file only if this hunk should be backed up
3829 if c.header.filename() in tobackup:
3865 if c.header.filename() in tobackup:
3830 target = repo.wjoin(abs)
3866 target = repo.wjoin(abs)
3831 bakname = scmutil.backuppath(repo.ui, repo, abs)
3867 bakname = scmutil.backuppath(repo.ui, repo, abs)
3832 util.copyfile(target, bakname)
3868 util.copyfile(target, bakname)
3833 tobackup.remove(abs)
3869 tobackup.remove(abs)
3834 if abs not in files:
3870 if abs not in files:
3835 files.add(abs)
3871 files.add(abs)
3836 if operation == b'keep':
3872 if operation == b'keep':
3837 checkout(abs)
3873 checkout(abs)
3838 c.write(fp)
3874 c.write(fp)
3839 dopatch = fp.tell()
3875 dopatch = fp.tell()
3840 fp.seek(0)
3876 fp.seek(0)
3841 if dopatch:
3877 if dopatch:
3842 try:
3878 try:
3843 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3879 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3844 except error.PatchError as err:
3880 except error.PatchError as err:
3845 raise error.Abort(pycompat.bytestr(err))
3881 raise error.Abort(pycompat.bytestr(err))
3846 del fp
3882 del fp
3847 else:
3883 else:
3848 for f in actions[b'revert'][0]:
3884 for f in actions[b'revert'][0]:
3849 prntstatusmsg(b'revert', f)
3885 prntstatusmsg(b'revert', f)
3850 checkout(f)
3886 checkout(f)
3851 if normal:
3887 if normal:
3852 normal(f)
3888 normal(f)
3853
3889
3854 for f in actions[b'add'][0]:
3890 for f in actions[b'add'][0]:
3855 # Don't checkout modified files, they are already created by the diff
3891 # Don't checkout modified files, they are already created by the diff
3856 if f not in newlyaddedandmodifiedfiles:
3892 if f not in newlyaddedandmodifiedfiles:
3857 prntstatusmsg(b'add', f)
3893 prntstatusmsg(b'add', f)
3858 checkout(f)
3894 checkout(f)
3859 repo.dirstate.add(f)
3895 repo.dirstate.add(f)
3860
3896
3861 normal = repo.dirstate.normallookup
3897 normal = repo.dirstate.normallookup
3862 if node == parent and p2 == nullid:
3898 if node == parent and p2 == nullid:
3863 normal = repo.dirstate.normal
3899 normal = repo.dirstate.normal
3864 for f in actions[b'undelete'][0]:
3900 for f in actions[b'undelete'][0]:
3865 if interactive:
3901 if interactive:
3866 choice = repo.ui.promptchoice(
3902 choice = repo.ui.promptchoice(
3867 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3903 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3868 )
3904 )
3869 if choice == 0:
3905 if choice == 0:
3870 prntstatusmsg(b'undelete', f)
3906 prntstatusmsg(b'undelete', f)
3871 checkout(f)
3907 checkout(f)
3872 normal(f)
3908 normal(f)
3873 else:
3909 else:
3874 excluded_files.append(f)
3910 excluded_files.append(f)
3875 else:
3911 else:
3876 prntstatusmsg(b'undelete', f)
3912 prntstatusmsg(b'undelete', f)
3877 checkout(f)
3913 checkout(f)
3878 normal(f)
3914 normal(f)
3879
3915
3880 copied = copies.pathcopies(repo[parent], ctx)
3916 copied = copies.pathcopies(repo[parent], ctx)
3881
3917
3882 for f in (
3918 for f in (
3883 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3919 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3884 ):
3920 ):
3885 if f in copied:
3921 if f in copied:
3886 repo.dirstate.copy(copied[f], f)
3922 repo.dirstate.copy(copied[f], f)
3887
3923
3888
3924
3889 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3925 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3890 # commands.outgoing. "missing" is "missing" of the result of
3926 # commands.outgoing. "missing" is "missing" of the result of
3891 # "findcommonoutgoing()"
3927 # "findcommonoutgoing()"
3892 outgoinghooks = util.hooks()
3928 outgoinghooks = util.hooks()
3893
3929
3894 # a list of (ui, repo) functions called by commands.summary
3930 # a list of (ui, repo) functions called by commands.summary
3895 summaryhooks = util.hooks()
3931 summaryhooks = util.hooks()
3896
3932
3897 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3933 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3898 #
3934 #
3899 # functions should return tuple of booleans below, if 'changes' is None:
3935 # functions should return tuple of booleans below, if 'changes' is None:
3900 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3936 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3901 #
3937 #
3902 # otherwise, 'changes' is a tuple of tuples below:
3938 # otherwise, 'changes' is a tuple of tuples below:
3903 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3939 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3904 # - (desturl, destbranch, destpeer, outgoing)
3940 # - (desturl, destbranch, destpeer, outgoing)
3905 summaryremotehooks = util.hooks()
3941 summaryremotehooks = util.hooks()
3906
3942
3907
3943
3908 def checkunfinished(repo, commit=False, skipmerge=False):
3944 def checkunfinished(repo, commit=False, skipmerge=False):
3909 '''Look for an unfinished multistep operation, like graft, and abort
3945 '''Look for an unfinished multistep operation, like graft, and abort
3910 if found. It's probably good to check this right before
3946 if found. It's probably good to check this right before
3911 bailifchanged().
3947 bailifchanged().
3912 '''
3948 '''
3913 # Check for non-clearable states first, so things like rebase will take
3949 # Check for non-clearable states first, so things like rebase will take
3914 # precedence over update.
3950 # precedence over update.
3915 for state in statemod._unfinishedstates:
3951 for state in statemod._unfinishedstates:
3916 if (
3952 if (
3917 state._clearable
3953 state._clearable
3918 or (commit and state._allowcommit)
3954 or (commit and state._allowcommit)
3919 or state._reportonly
3955 or state._reportonly
3920 ):
3956 ):
3921 continue
3957 continue
3922 if state.isunfinished(repo):
3958 if state.isunfinished(repo):
3923 raise error.Abort(state.msg(), hint=state.hint())
3959 raise error.Abort(state.msg(), hint=state.hint())
3924
3960
3925 for s in statemod._unfinishedstates:
3961 for s in statemod._unfinishedstates:
3926 if (
3962 if (
3927 not s._clearable
3963 not s._clearable
3928 or (commit and s._allowcommit)
3964 or (commit and s._allowcommit)
3929 or (s._opname == b'merge' and skipmerge)
3965 or (s._opname == b'merge' and skipmerge)
3930 or s._reportonly
3966 or s._reportonly
3931 ):
3967 ):
3932 continue
3968 continue
3933 if s.isunfinished(repo):
3969 if s.isunfinished(repo):
3934 raise error.Abort(s.msg(), hint=s.hint())
3970 raise error.Abort(s.msg(), hint=s.hint())
3935
3971
3936
3972
3937 def clearunfinished(repo):
3973 def clearunfinished(repo):
3938 '''Check for unfinished operations (as above), and clear the ones
3974 '''Check for unfinished operations (as above), and clear the ones
3939 that are clearable.
3975 that are clearable.
3940 '''
3976 '''
3941 for state in statemod._unfinishedstates:
3977 for state in statemod._unfinishedstates:
3942 if state._reportonly:
3978 if state._reportonly:
3943 continue
3979 continue
3944 if not state._clearable and state.isunfinished(repo):
3980 if not state._clearable and state.isunfinished(repo):
3945 raise error.Abort(state.msg(), hint=state.hint())
3981 raise error.Abort(state.msg(), hint=state.hint())
3946
3982
3947 for s in statemod._unfinishedstates:
3983 for s in statemod._unfinishedstates:
3948 if s._opname == b'merge' or state._reportonly:
3984 if s._opname == b'merge' or state._reportonly:
3949 continue
3985 continue
3950 if s._clearable and s.isunfinished(repo):
3986 if s._clearable and s.isunfinished(repo):
3951 util.unlink(repo.vfs.join(s._fname))
3987 util.unlink(repo.vfs.join(s._fname))
3952
3988
3953
3989
3954 def getunfinishedstate(repo):
3990 def getunfinishedstate(repo):
3955 ''' Checks for unfinished operations and returns statecheck object
3991 ''' Checks for unfinished operations and returns statecheck object
3956 for it'''
3992 for it'''
3957 for state in statemod._unfinishedstates:
3993 for state in statemod._unfinishedstates:
3958 if state.isunfinished(repo):
3994 if state.isunfinished(repo):
3959 return state
3995 return state
3960 return None
3996 return None
3961
3997
3962
3998
3963 def howtocontinue(repo):
3999 def howtocontinue(repo):
3964 '''Check for an unfinished operation and return the command to finish
4000 '''Check for an unfinished operation and return the command to finish
3965 it.
4001 it.
3966
4002
3967 statemod._unfinishedstates list is checked for an unfinished operation
4003 statemod._unfinishedstates list is checked for an unfinished operation
3968 and the corresponding message to finish it is generated if a method to
4004 and the corresponding message to finish it is generated if a method to
3969 continue is supported by the operation.
4005 continue is supported by the operation.
3970
4006
3971 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
4007 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3972 a boolean.
4008 a boolean.
3973 '''
4009 '''
3974 contmsg = _(b"continue: %s")
4010 contmsg = _(b"continue: %s")
3975 for state in statemod._unfinishedstates:
4011 for state in statemod._unfinishedstates:
3976 if not state._continueflag:
4012 if not state._continueflag:
3977 continue
4013 continue
3978 if state.isunfinished(repo):
4014 if state.isunfinished(repo):
3979 return contmsg % state.continuemsg(), True
4015 return contmsg % state.continuemsg(), True
3980 if repo[None].dirty(missing=True, merge=False, branch=False):
4016 if repo[None].dirty(missing=True, merge=False, branch=False):
3981 return contmsg % _(b"hg commit"), False
4017 return contmsg % _(b"hg commit"), False
3982 return None, None
4018 return None, None
3983
4019
3984
4020
3985 def checkafterresolved(repo):
4021 def checkafterresolved(repo):
3986 '''Inform the user about the next action after completing hg resolve
4022 '''Inform the user about the next action after completing hg resolve
3987
4023
3988 If there's a an unfinished operation that supports continue flag,
4024 If there's a an unfinished operation that supports continue flag,
3989 howtocontinue will yield repo.ui.warn as the reporter.
4025 howtocontinue will yield repo.ui.warn as the reporter.
3990
4026
3991 Otherwise, it will yield repo.ui.note.
4027 Otherwise, it will yield repo.ui.note.
3992 '''
4028 '''
3993 msg, warning = howtocontinue(repo)
4029 msg, warning = howtocontinue(repo)
3994 if msg is not None:
4030 if msg is not None:
3995 if warning:
4031 if warning:
3996 repo.ui.warn(b"%s\n" % msg)
4032 repo.ui.warn(b"%s\n" % msg)
3997 else:
4033 else:
3998 repo.ui.note(b"%s\n" % msg)
4034 repo.ui.note(b"%s\n" % msg)
3999
4035
4000
4036
4001 def wrongtooltocontinue(repo, task):
4037 def wrongtooltocontinue(repo, task):
4002 '''Raise an abort suggesting how to properly continue if there is an
4038 '''Raise an abort suggesting how to properly continue if there is an
4003 active task.
4039 active task.
4004
4040
4005 Uses howtocontinue() to find the active task.
4041 Uses howtocontinue() to find the active task.
4006
4042
4007 If there's no task (repo.ui.note for 'hg commit'), it does not offer
4043 If there's no task (repo.ui.note for 'hg commit'), it does not offer
4008 a hint.
4044 a hint.
4009 '''
4045 '''
4010 after = howtocontinue(repo)
4046 after = howtocontinue(repo)
4011 hint = None
4047 hint = None
4012 if after[1]:
4048 if after[1]:
4013 hint = after[0]
4049 hint = after[0]
4014 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
4050 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
4015
4051
4016
4052
4017 def abortgraft(ui, repo, graftstate):
4053 def abortgraft(ui, repo, graftstate):
4018 """abort the interrupted graft and rollbacks to the state before interrupted
4054 """abort the interrupted graft and rollbacks to the state before interrupted
4019 graft"""
4055 graft"""
4020 if not graftstate.exists():
4056 if not graftstate.exists():
4021 raise error.Abort(_(b"no interrupted graft to abort"))
4057 raise error.Abort(_(b"no interrupted graft to abort"))
4022 statedata = readgraftstate(repo, graftstate)
4058 statedata = readgraftstate(repo, graftstate)
4023 newnodes = statedata.get(b'newnodes')
4059 newnodes = statedata.get(b'newnodes')
4024 if newnodes is None:
4060 if newnodes is None:
4025 # and old graft state which does not have all the data required to abort
4061 # and old graft state which does not have all the data required to abort
4026 # the graft
4062 # the graft
4027 raise error.Abort(_(b"cannot abort using an old graftstate"))
4063 raise error.Abort(_(b"cannot abort using an old graftstate"))
4028
4064
4029 # changeset from which graft operation was started
4065 # changeset from which graft operation was started
4030 if len(newnodes) > 0:
4066 if len(newnodes) > 0:
4031 startctx = repo[newnodes[0]].p1()
4067 startctx = repo[newnodes[0]].p1()
4032 else:
4068 else:
4033 startctx = repo[b'.']
4069 startctx = repo[b'.']
4034 # whether to strip or not
4070 # whether to strip or not
4035 cleanup = False
4071 cleanup = False
4036 from . import hg
4072 from . import hg
4037
4073
4038 if newnodes:
4074 if newnodes:
4039 newnodes = [repo[r].rev() for r in newnodes]
4075 newnodes = [repo[r].rev() for r in newnodes]
4040 cleanup = True
4076 cleanup = True
4041 # checking that none of the newnodes turned public or is public
4077 # checking that none of the newnodes turned public or is public
4042 immutable = [c for c in newnodes if not repo[c].mutable()]
4078 immutable = [c for c in newnodes if not repo[c].mutable()]
4043 if immutable:
4079 if immutable:
4044 repo.ui.warn(
4080 repo.ui.warn(
4045 _(b"cannot clean up public changesets %s\n")
4081 _(b"cannot clean up public changesets %s\n")
4046 % b', '.join(bytes(repo[r]) for r in immutable),
4082 % b', '.join(bytes(repo[r]) for r in immutable),
4047 hint=_(b"see 'hg help phases' for details"),
4083 hint=_(b"see 'hg help phases' for details"),
4048 )
4084 )
4049 cleanup = False
4085 cleanup = False
4050
4086
4051 # checking that no new nodes are created on top of grafted revs
4087 # checking that no new nodes are created on top of grafted revs
4052 desc = set(repo.changelog.descendants(newnodes))
4088 desc = set(repo.changelog.descendants(newnodes))
4053 if desc - set(newnodes):
4089 if desc - set(newnodes):
4054 repo.ui.warn(
4090 repo.ui.warn(
4055 _(
4091 _(
4056 b"new changesets detected on destination "
4092 b"new changesets detected on destination "
4057 b"branch, can't strip\n"
4093 b"branch, can't strip\n"
4058 )
4094 )
4059 )
4095 )
4060 cleanup = False
4096 cleanup = False
4061
4097
4062 if cleanup:
4098 if cleanup:
4063 with repo.wlock(), repo.lock():
4099 with repo.wlock(), repo.lock():
4064 hg.updaterepo(repo, startctx.node(), overwrite=True)
4100 hg.updaterepo(repo, startctx.node(), overwrite=True)
4065 # stripping the new nodes created
4101 # stripping the new nodes created
4066 strippoints = [
4102 strippoints = [
4067 c.node() for c in repo.set(b"roots(%ld)", newnodes)
4103 c.node() for c in repo.set(b"roots(%ld)", newnodes)
4068 ]
4104 ]
4069 repair.strip(repo.ui, repo, strippoints, backup=False)
4105 repair.strip(repo.ui, repo, strippoints, backup=False)
4070
4106
4071 if not cleanup:
4107 if not cleanup:
4072 # we don't update to the startnode if we can't strip
4108 # we don't update to the startnode if we can't strip
4073 startctx = repo[b'.']
4109 startctx = repo[b'.']
4074 hg.updaterepo(repo, startctx.node(), overwrite=True)
4110 hg.updaterepo(repo, startctx.node(), overwrite=True)
4075
4111
4076 ui.status(_(b"graft aborted\n"))
4112 ui.status(_(b"graft aborted\n"))
4077 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4113 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4078 graftstate.delete()
4114 graftstate.delete()
4079 return 0
4115 return 0
4080
4116
4081
4117
4082 def readgraftstate(repo, graftstate):
4118 def readgraftstate(repo, graftstate):
4083 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4119 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4084 """read the graft state file and return a dict of the data stored in it"""
4120 """read the graft state file and return a dict of the data stored in it"""
4085 try:
4121 try:
4086 return graftstate.read()
4122 return graftstate.read()
4087 except error.CorruptedState:
4123 except error.CorruptedState:
4088 nodes = repo.vfs.read(b'graftstate').splitlines()
4124 nodes = repo.vfs.read(b'graftstate').splitlines()
4089 return {b'nodes': nodes}
4125 return {b'nodes': nodes}
4090
4126
4091
4127
4092 def hgabortgraft(ui, repo):
4128 def hgabortgraft(ui, repo):
4093 """ abort logic for aborting graft using 'hg abort'"""
4129 """ abort logic for aborting graft using 'hg abort'"""
4094 with repo.wlock():
4130 with repo.wlock():
4095 graftstate = statemod.cmdstate(repo, b'graftstate')
4131 graftstate = statemod.cmdstate(repo, b'graftstate')
4096 return abortgraft(ui, repo, graftstate)
4132 return abortgraft(ui, repo, graftstate)
@@ -1,7833 +1,7840 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from .pycompat import open
25 from .pycompat import open
26 from . import (
26 from . import (
27 archival,
27 archival,
28 bookmarks,
28 bookmarks,
29 bundle2,
29 bundle2,
30 changegroup,
30 changegroup,
31 cmdutil,
31 cmdutil,
32 copies,
32 copies,
33 debugcommands as debugcommandsmod,
33 debugcommands as debugcommandsmod,
34 destutil,
34 destutil,
35 dirstateguard,
35 dirstateguard,
36 discovery,
36 discovery,
37 encoding,
37 encoding,
38 error,
38 error,
39 exchange,
39 exchange,
40 extensions,
40 extensions,
41 filemerge,
41 filemerge,
42 formatter,
42 formatter,
43 graphmod,
43 graphmod,
44 hbisect,
44 hbisect,
45 help,
45 help,
46 hg,
46 hg,
47 logcmdutil,
47 logcmdutil,
48 merge as mergemod,
48 merge as mergemod,
49 narrowspec,
49 narrowspec,
50 obsolete,
50 obsolete,
51 obsutil,
51 obsutil,
52 patch,
52 patch,
53 phases,
53 phases,
54 pycompat,
54 pycompat,
55 rcutil,
55 rcutil,
56 registrar,
56 registrar,
57 revsetlang,
57 revsetlang,
58 rewriteutil,
58 rewriteutil,
59 scmutil,
59 scmutil,
60 server,
60 server,
61 shelve as shelvemod,
61 shelve as shelvemod,
62 state as statemod,
62 state as statemod,
63 streamclone,
63 streamclone,
64 tags as tagsmod,
64 tags as tagsmod,
65 ui as uimod,
65 ui as uimod,
66 util,
66 util,
67 verify as verifymod,
67 verify as verifymod,
68 wireprotoserver,
68 wireprotoserver,
69 )
69 )
70 from .utils import (
70 from .utils import (
71 dateutil,
71 dateutil,
72 stringutil,
72 stringutil,
73 )
73 )
74
74
75 table = {}
75 table = {}
76 table.update(debugcommandsmod.command._table)
76 table.update(debugcommandsmod.command._table)
77
77
78 command = registrar.command(table)
78 command = registrar.command(table)
79 INTENT_READONLY = registrar.INTENT_READONLY
79 INTENT_READONLY = registrar.INTENT_READONLY
80
80
81 # common command options
81 # common command options
82
82
83 globalopts = [
83 globalopts = [
84 (
84 (
85 b'R',
85 b'R',
86 b'repository',
86 b'repository',
87 b'',
87 b'',
88 _(b'repository root directory or name of overlay bundle file'),
88 _(b'repository root directory or name of overlay bundle file'),
89 _(b'REPO'),
89 _(b'REPO'),
90 ),
90 ),
91 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
91 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
92 (
92 (
93 b'y',
93 b'y',
94 b'noninteractive',
94 b'noninteractive',
95 None,
95 None,
96 _(
96 _(
97 b'do not prompt, automatically pick the first choice for all prompts'
97 b'do not prompt, automatically pick the first choice for all prompts'
98 ),
98 ),
99 ),
99 ),
100 (b'q', b'quiet', None, _(b'suppress output')),
100 (b'q', b'quiet', None, _(b'suppress output')),
101 (b'v', b'verbose', None, _(b'enable additional output')),
101 (b'v', b'verbose', None, _(b'enable additional output')),
102 (
102 (
103 b'',
103 b'',
104 b'color',
104 b'color',
105 b'',
105 b'',
106 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
106 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
107 # and should not be translated
107 # and should not be translated
108 _(b"when to colorize (boolean, always, auto, never, or debug)"),
108 _(b"when to colorize (boolean, always, auto, never, or debug)"),
109 _(b'TYPE'),
109 _(b'TYPE'),
110 ),
110 ),
111 (
111 (
112 b'',
112 b'',
113 b'config',
113 b'config',
114 [],
114 [],
115 _(b'set/override config option (use \'section.name=value\')'),
115 _(b'set/override config option (use \'section.name=value\')'),
116 _(b'CONFIG'),
116 _(b'CONFIG'),
117 ),
117 ),
118 (b'', b'debug', None, _(b'enable debugging output')),
118 (b'', b'debug', None, _(b'enable debugging output')),
119 (b'', b'debugger', None, _(b'start debugger')),
119 (b'', b'debugger', None, _(b'start debugger')),
120 (
120 (
121 b'',
121 b'',
122 b'encoding',
122 b'encoding',
123 encoding.encoding,
123 encoding.encoding,
124 _(b'set the charset encoding'),
124 _(b'set the charset encoding'),
125 _(b'ENCODE'),
125 _(b'ENCODE'),
126 ),
126 ),
127 (
127 (
128 b'',
128 b'',
129 b'encodingmode',
129 b'encodingmode',
130 encoding.encodingmode,
130 encoding.encodingmode,
131 _(b'set the charset encoding mode'),
131 _(b'set the charset encoding mode'),
132 _(b'MODE'),
132 _(b'MODE'),
133 ),
133 ),
134 (b'', b'traceback', None, _(b'always print a traceback on exception')),
134 (b'', b'traceback', None, _(b'always print a traceback on exception')),
135 (b'', b'time', None, _(b'time how long the command takes')),
135 (b'', b'time', None, _(b'time how long the command takes')),
136 (b'', b'profile', None, _(b'print command execution profile')),
136 (b'', b'profile', None, _(b'print command execution profile')),
137 (b'', b'version', None, _(b'output version information and exit')),
137 (b'', b'version', None, _(b'output version information and exit')),
138 (b'h', b'help', None, _(b'display help and exit')),
138 (b'h', b'help', None, _(b'display help and exit')),
139 (b'', b'hidden', False, _(b'consider hidden changesets')),
139 (b'', b'hidden', False, _(b'consider hidden changesets')),
140 (
140 (
141 b'',
141 b'',
142 b'pager',
142 b'pager',
143 b'auto',
143 b'auto',
144 _(b"when to paginate (boolean, always, auto, or never)"),
144 _(b"when to paginate (boolean, always, auto, or never)"),
145 _(b'TYPE'),
145 _(b'TYPE'),
146 ),
146 ),
147 ]
147 ]
148
148
149 dryrunopts = cmdutil.dryrunopts
149 dryrunopts = cmdutil.dryrunopts
150 remoteopts = cmdutil.remoteopts
150 remoteopts = cmdutil.remoteopts
151 walkopts = cmdutil.walkopts
151 walkopts = cmdutil.walkopts
152 commitopts = cmdutil.commitopts
152 commitopts = cmdutil.commitopts
153 commitopts2 = cmdutil.commitopts2
153 commitopts2 = cmdutil.commitopts2
154 commitopts3 = cmdutil.commitopts3
154 commitopts3 = cmdutil.commitopts3
155 formatteropts = cmdutil.formatteropts
155 formatteropts = cmdutil.formatteropts
156 templateopts = cmdutil.templateopts
156 templateopts = cmdutil.templateopts
157 logopts = cmdutil.logopts
157 logopts = cmdutil.logopts
158 diffopts = cmdutil.diffopts
158 diffopts = cmdutil.diffopts
159 diffwsopts = cmdutil.diffwsopts
159 diffwsopts = cmdutil.diffwsopts
160 diffopts2 = cmdutil.diffopts2
160 diffopts2 = cmdutil.diffopts2
161 mergetoolopts = cmdutil.mergetoolopts
161 mergetoolopts = cmdutil.mergetoolopts
162 similarityopts = cmdutil.similarityopts
162 similarityopts = cmdutil.similarityopts
163 subrepoopts = cmdutil.subrepoopts
163 subrepoopts = cmdutil.subrepoopts
164 debugrevlogopts = cmdutil.debugrevlogopts
164 debugrevlogopts = cmdutil.debugrevlogopts
165
165
166 # Commands start here, listed alphabetically
166 # Commands start here, listed alphabetically
167
167
168
168
169 @command(
169 @command(
170 b'abort',
170 b'abort',
171 dryrunopts,
171 dryrunopts,
172 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
172 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
173 helpbasic=True,
173 helpbasic=True,
174 )
174 )
175 def abort(ui, repo, **opts):
175 def abort(ui, repo, **opts):
176 """abort an unfinished operation (EXPERIMENTAL)
176 """abort an unfinished operation (EXPERIMENTAL)
177
177
178 Aborts a multistep operation like graft, histedit, rebase, merge,
178 Aborts a multistep operation like graft, histedit, rebase, merge,
179 and unshelve if they are in an unfinished state.
179 and unshelve if they are in an unfinished state.
180
180
181 use --dry-run/-n to dry run the command.
181 use --dry-run/-n to dry run the command.
182 """
182 """
183 dryrun = opts.get('dry_run')
183 dryrun = opts.get('dry_run')
184 abortstate = cmdutil.getunfinishedstate(repo)
184 abortstate = cmdutil.getunfinishedstate(repo)
185 if not abortstate:
185 if not abortstate:
186 raise error.Abort(_(b'no operation in progress'))
186 raise error.Abort(_(b'no operation in progress'))
187 if not abortstate.abortfunc:
187 if not abortstate.abortfunc:
188 raise error.Abort(
188 raise error.Abort(
189 (
189 (
190 _(b"%s in progress but does not support 'hg abort'")
190 _(b"%s in progress but does not support 'hg abort'")
191 % (abortstate._opname)
191 % (abortstate._opname)
192 ),
192 ),
193 hint=abortstate.hint(),
193 hint=abortstate.hint(),
194 )
194 )
195 if dryrun:
195 if dryrun:
196 ui.status(
196 ui.status(
197 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
197 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
198 )
198 )
199 return
199 return
200 return abortstate.abortfunc(ui, repo)
200 return abortstate.abortfunc(ui, repo)
201
201
202
202
203 @command(
203 @command(
204 b'add',
204 b'add',
205 walkopts + subrepoopts + dryrunopts,
205 walkopts + subrepoopts + dryrunopts,
206 _(b'[OPTION]... [FILE]...'),
206 _(b'[OPTION]... [FILE]...'),
207 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
207 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
208 helpbasic=True,
208 helpbasic=True,
209 inferrepo=True,
209 inferrepo=True,
210 )
210 )
211 def add(ui, repo, *pats, **opts):
211 def add(ui, repo, *pats, **opts):
212 """add the specified files on the next commit
212 """add the specified files on the next commit
213
213
214 Schedule files to be version controlled and added to the
214 Schedule files to be version controlled and added to the
215 repository.
215 repository.
216
216
217 The files will be added to the repository at the next commit. To
217 The files will be added to the repository at the next commit. To
218 undo an add before that, see :hg:`forget`.
218 undo an add before that, see :hg:`forget`.
219
219
220 If no names are given, add all files to the repository (except
220 If no names are given, add all files to the repository (except
221 files matching ``.hgignore``).
221 files matching ``.hgignore``).
222
222
223 .. container:: verbose
223 .. container:: verbose
224
224
225 Examples:
225 Examples:
226
226
227 - New (unknown) files are added
227 - New (unknown) files are added
228 automatically by :hg:`add`::
228 automatically by :hg:`add`::
229
229
230 $ ls
230 $ ls
231 foo.c
231 foo.c
232 $ hg status
232 $ hg status
233 ? foo.c
233 ? foo.c
234 $ hg add
234 $ hg add
235 adding foo.c
235 adding foo.c
236 $ hg status
236 $ hg status
237 A foo.c
237 A foo.c
238
238
239 - Specific files to be added can be specified::
239 - Specific files to be added can be specified::
240
240
241 $ ls
241 $ ls
242 bar.c foo.c
242 bar.c foo.c
243 $ hg status
243 $ hg status
244 ? bar.c
244 ? bar.c
245 ? foo.c
245 ? foo.c
246 $ hg add bar.c
246 $ hg add bar.c
247 $ hg status
247 $ hg status
248 A bar.c
248 A bar.c
249 ? foo.c
249 ? foo.c
250
250
251 Returns 0 if all files are successfully added.
251 Returns 0 if all files are successfully added.
252 """
252 """
253
253
254 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
254 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
255 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
255 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
256 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
256 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
257 return rejected and 1 or 0
257 return rejected and 1 or 0
258
258
259
259
260 @command(
260 @command(
261 b'addremove',
261 b'addremove',
262 similarityopts + subrepoopts + walkopts + dryrunopts,
262 similarityopts + subrepoopts + walkopts + dryrunopts,
263 _(b'[OPTION]... [FILE]...'),
263 _(b'[OPTION]... [FILE]...'),
264 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
264 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
265 inferrepo=True,
265 inferrepo=True,
266 )
266 )
267 def addremove(ui, repo, *pats, **opts):
267 def addremove(ui, repo, *pats, **opts):
268 """add all new files, delete all missing files
268 """add all new files, delete all missing files
269
269
270 Add all new files and remove all missing files from the
270 Add all new files and remove all missing files from the
271 repository.
271 repository.
272
272
273 Unless names are given, new files are ignored if they match any of
273 Unless names are given, new files are ignored if they match any of
274 the patterns in ``.hgignore``. As with add, these changes take
274 the patterns in ``.hgignore``. As with add, these changes take
275 effect at the next commit.
275 effect at the next commit.
276
276
277 Use the -s/--similarity option to detect renamed files. This
277 Use the -s/--similarity option to detect renamed files. This
278 option takes a percentage between 0 (disabled) and 100 (files must
278 option takes a percentage between 0 (disabled) and 100 (files must
279 be identical) as its parameter. With a parameter greater than 0,
279 be identical) as its parameter. With a parameter greater than 0,
280 this compares every removed file with every added file and records
280 this compares every removed file with every added file and records
281 those similar enough as renames. Detecting renamed files this way
281 those similar enough as renames. Detecting renamed files this way
282 can be expensive. After using this option, :hg:`status -C` can be
282 can be expensive. After using this option, :hg:`status -C` can be
283 used to check which files were identified as moved or renamed. If
283 used to check which files were identified as moved or renamed. If
284 not specified, -s/--similarity defaults to 100 and only renames of
284 not specified, -s/--similarity defaults to 100 and only renames of
285 identical files are detected.
285 identical files are detected.
286
286
287 .. container:: verbose
287 .. container:: verbose
288
288
289 Examples:
289 Examples:
290
290
291 - A number of files (bar.c and foo.c) are new,
291 - A number of files (bar.c and foo.c) are new,
292 while foobar.c has been removed (without using :hg:`remove`)
292 while foobar.c has been removed (without using :hg:`remove`)
293 from the repository::
293 from the repository::
294
294
295 $ ls
295 $ ls
296 bar.c foo.c
296 bar.c foo.c
297 $ hg status
297 $ hg status
298 ! foobar.c
298 ! foobar.c
299 ? bar.c
299 ? bar.c
300 ? foo.c
300 ? foo.c
301 $ hg addremove
301 $ hg addremove
302 adding bar.c
302 adding bar.c
303 adding foo.c
303 adding foo.c
304 removing foobar.c
304 removing foobar.c
305 $ hg status
305 $ hg status
306 A bar.c
306 A bar.c
307 A foo.c
307 A foo.c
308 R foobar.c
308 R foobar.c
309
309
310 - A file foobar.c was moved to foo.c without using :hg:`rename`.
310 - A file foobar.c was moved to foo.c without using :hg:`rename`.
311 Afterwards, it was edited slightly::
311 Afterwards, it was edited slightly::
312
312
313 $ ls
313 $ ls
314 foo.c
314 foo.c
315 $ hg status
315 $ hg status
316 ! foobar.c
316 ! foobar.c
317 ? foo.c
317 ? foo.c
318 $ hg addremove --similarity 90
318 $ hg addremove --similarity 90
319 removing foobar.c
319 removing foobar.c
320 adding foo.c
320 adding foo.c
321 recording removal of foobar.c as rename to foo.c (94% similar)
321 recording removal of foobar.c as rename to foo.c (94% similar)
322 $ hg status -C
322 $ hg status -C
323 A foo.c
323 A foo.c
324 foobar.c
324 foobar.c
325 R foobar.c
325 R foobar.c
326
326
327 Returns 0 if all files are successfully added.
327 Returns 0 if all files are successfully added.
328 """
328 """
329 opts = pycompat.byteskwargs(opts)
329 opts = pycompat.byteskwargs(opts)
330 if not opts.get(b'similarity'):
330 if not opts.get(b'similarity'):
331 opts[b'similarity'] = b'100'
331 opts[b'similarity'] = b'100'
332 matcher = scmutil.match(repo[None], pats, opts)
332 matcher = scmutil.match(repo[None], pats, opts)
333 relative = scmutil.anypats(pats, opts)
333 relative = scmutil.anypats(pats, opts)
334 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
334 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
335 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
335 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
336
336
337
337
338 @command(
338 @command(
339 b'annotate|blame',
339 b'annotate|blame',
340 [
340 [
341 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
341 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
342 (
342 (
343 b'',
343 b'',
344 b'follow',
344 b'follow',
345 None,
345 None,
346 _(b'follow copies/renames and list the filename (DEPRECATED)'),
346 _(b'follow copies/renames and list the filename (DEPRECATED)'),
347 ),
347 ),
348 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
348 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
349 (b'a', b'text', None, _(b'treat all files as text')),
349 (b'a', b'text', None, _(b'treat all files as text')),
350 (b'u', b'user', None, _(b'list the author (long with -v)')),
350 (b'u', b'user', None, _(b'list the author (long with -v)')),
351 (b'f', b'file', None, _(b'list the filename')),
351 (b'f', b'file', None, _(b'list the filename')),
352 (b'd', b'date', None, _(b'list the date (short with -q)')),
352 (b'd', b'date', None, _(b'list the date (short with -q)')),
353 (b'n', b'number', None, _(b'list the revision number (default)')),
353 (b'n', b'number', None, _(b'list the revision number (default)')),
354 (b'c', b'changeset', None, _(b'list the changeset')),
354 (b'c', b'changeset', None, _(b'list the changeset')),
355 (
355 (
356 b'l',
356 b'l',
357 b'line-number',
357 b'line-number',
358 None,
358 None,
359 _(b'show line number at the first appearance'),
359 _(b'show line number at the first appearance'),
360 ),
360 ),
361 (
361 (
362 b'',
362 b'',
363 b'skip',
363 b'skip',
364 [],
364 [],
365 _(b'revset to not display (EXPERIMENTAL)'),
365 _(b'revset to not display (EXPERIMENTAL)'),
366 _(b'REV'),
366 _(b'REV'),
367 ),
367 ),
368 ]
368 ]
369 + diffwsopts
369 + diffwsopts
370 + walkopts
370 + walkopts
371 + formatteropts,
371 + formatteropts,
372 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
372 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
373 helpcategory=command.CATEGORY_FILE_CONTENTS,
373 helpcategory=command.CATEGORY_FILE_CONTENTS,
374 helpbasic=True,
374 helpbasic=True,
375 inferrepo=True,
375 inferrepo=True,
376 )
376 )
377 def annotate(ui, repo, *pats, **opts):
377 def annotate(ui, repo, *pats, **opts):
378 """show changeset information by line for each file
378 """show changeset information by line for each file
379
379
380 List changes in files, showing the revision id responsible for
380 List changes in files, showing the revision id responsible for
381 each line.
381 each line.
382
382
383 This command is useful for discovering when a change was made and
383 This command is useful for discovering when a change was made and
384 by whom.
384 by whom.
385
385
386 If you include --file, --user, or --date, the revision number is
386 If you include --file, --user, or --date, the revision number is
387 suppressed unless you also include --number.
387 suppressed unless you also include --number.
388
388
389 Without the -a/--text option, annotate will avoid processing files
389 Without the -a/--text option, annotate will avoid processing files
390 it detects as binary. With -a, annotate will annotate the file
390 it detects as binary. With -a, annotate will annotate the file
391 anyway, although the results will probably be neither useful
391 anyway, although the results will probably be neither useful
392 nor desirable.
392 nor desirable.
393
393
394 .. container:: verbose
394 .. container:: verbose
395
395
396 Template:
396 Template:
397
397
398 The following keywords are supported in addition to the common template
398 The following keywords are supported in addition to the common template
399 keywords and functions. See also :hg:`help templates`.
399 keywords and functions. See also :hg:`help templates`.
400
400
401 :lines: List of lines with annotation data.
401 :lines: List of lines with annotation data.
402 :path: String. Repository-absolute path of the specified file.
402 :path: String. Repository-absolute path of the specified file.
403
403
404 And each entry of ``{lines}`` provides the following sub-keywords in
404 And each entry of ``{lines}`` provides the following sub-keywords in
405 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
405 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
406
406
407 :line: String. Line content.
407 :line: String. Line content.
408 :lineno: Integer. Line number at that revision.
408 :lineno: Integer. Line number at that revision.
409 :path: String. Repository-absolute path of the file at that revision.
409 :path: String. Repository-absolute path of the file at that revision.
410
410
411 See :hg:`help templates.operators` for the list expansion syntax.
411 See :hg:`help templates.operators` for the list expansion syntax.
412
412
413 Returns 0 on success.
413 Returns 0 on success.
414 """
414 """
415 opts = pycompat.byteskwargs(opts)
415 opts = pycompat.byteskwargs(opts)
416 if not pats:
416 if not pats:
417 raise error.Abort(_(b'at least one filename or pattern is required'))
417 raise error.Abort(_(b'at least one filename or pattern is required'))
418
418
419 if opts.get(b'follow'):
419 if opts.get(b'follow'):
420 # --follow is deprecated and now just an alias for -f/--file
420 # --follow is deprecated and now just an alias for -f/--file
421 # to mimic the behavior of Mercurial before version 1.5
421 # to mimic the behavior of Mercurial before version 1.5
422 opts[b'file'] = True
422 opts[b'file'] = True
423
423
424 if (
424 if (
425 not opts.get(b'user')
425 not opts.get(b'user')
426 and not opts.get(b'changeset')
426 and not opts.get(b'changeset')
427 and not opts.get(b'date')
427 and not opts.get(b'date')
428 and not opts.get(b'file')
428 and not opts.get(b'file')
429 ):
429 ):
430 opts[b'number'] = True
430 opts[b'number'] = True
431
431
432 linenumber = opts.get(b'line_number') is not None
432 linenumber = opts.get(b'line_number') is not None
433 if (
433 if (
434 linenumber
434 linenumber
435 and (not opts.get(b'changeset'))
435 and (not opts.get(b'changeset'))
436 and (not opts.get(b'number'))
436 and (not opts.get(b'number'))
437 ):
437 ):
438 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
438 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
439
439
440 rev = opts.get(b'rev')
440 rev = opts.get(b'rev')
441 if rev:
441 if rev:
442 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
442 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
443 ctx = scmutil.revsingle(repo, rev)
443 ctx = scmutil.revsingle(repo, rev)
444
444
445 ui.pager(b'annotate')
445 ui.pager(b'annotate')
446 rootfm = ui.formatter(b'annotate', opts)
446 rootfm = ui.formatter(b'annotate', opts)
447 if ui.debugflag:
447 if ui.debugflag:
448 shorthex = pycompat.identity
448 shorthex = pycompat.identity
449 else:
449 else:
450
450
451 def shorthex(h):
451 def shorthex(h):
452 return h[:12]
452 return h[:12]
453
453
454 if ui.quiet:
454 if ui.quiet:
455 datefunc = dateutil.shortdate
455 datefunc = dateutil.shortdate
456 else:
456 else:
457 datefunc = dateutil.datestr
457 datefunc = dateutil.datestr
458 if ctx.rev() is None:
458 if ctx.rev() is None:
459 if opts.get(b'changeset'):
459 if opts.get(b'changeset'):
460 # omit "+" suffix which is appended to node hex
460 # omit "+" suffix which is appended to node hex
461 def formatrev(rev):
461 def formatrev(rev):
462 if rev == wdirrev:
462 if rev == wdirrev:
463 return b'%d' % ctx.p1().rev()
463 return b'%d' % ctx.p1().rev()
464 else:
464 else:
465 return b'%d' % rev
465 return b'%d' % rev
466
466
467 else:
467 else:
468
468
469 def formatrev(rev):
469 def formatrev(rev):
470 if rev == wdirrev:
470 if rev == wdirrev:
471 return b'%d+' % ctx.p1().rev()
471 return b'%d+' % ctx.p1().rev()
472 else:
472 else:
473 return b'%d ' % rev
473 return b'%d ' % rev
474
474
475 def formathex(h):
475 def formathex(h):
476 if h == wdirhex:
476 if h == wdirhex:
477 return b'%s+' % shorthex(hex(ctx.p1().node()))
477 return b'%s+' % shorthex(hex(ctx.p1().node()))
478 else:
478 else:
479 return b'%s ' % shorthex(h)
479 return b'%s ' % shorthex(h)
480
480
481 else:
481 else:
482 formatrev = b'%d'.__mod__
482 formatrev = b'%d'.__mod__
483 formathex = shorthex
483 formathex = shorthex
484
484
485 opmap = [
485 opmap = [
486 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
486 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
487 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
487 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
488 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
488 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
489 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
489 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
490 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
490 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
491 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
491 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
492 ]
492 ]
493 opnamemap = {
493 opnamemap = {
494 b'rev': b'number',
494 b'rev': b'number',
495 b'node': b'changeset',
495 b'node': b'changeset',
496 b'path': b'file',
496 b'path': b'file',
497 b'lineno': b'line_number',
497 b'lineno': b'line_number',
498 }
498 }
499
499
500 if rootfm.isplain():
500 if rootfm.isplain():
501
501
502 def makefunc(get, fmt):
502 def makefunc(get, fmt):
503 return lambda x: fmt(get(x))
503 return lambda x: fmt(get(x))
504
504
505 else:
505 else:
506
506
507 def makefunc(get, fmt):
507 def makefunc(get, fmt):
508 return get
508 return get
509
509
510 datahint = rootfm.datahint()
510 datahint = rootfm.datahint()
511 funcmap = [
511 funcmap = [
512 (makefunc(get, fmt), sep)
512 (makefunc(get, fmt), sep)
513 for fn, sep, get, fmt in opmap
513 for fn, sep, get, fmt in opmap
514 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
514 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
515 ]
515 ]
516 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
516 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
517 fields = b' '.join(
517 fields = b' '.join(
518 fn
518 fn
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
522
523 def bad(x, y):
523 def bad(x, y):
524 raise error.Abort(b"%s: %s" % (x, y))
524 raise error.Abort(b"%s: %s" % (x, y))
525
525
526 m = scmutil.match(ctx, pats, opts, badfn=bad)
526 m = scmutil.match(ctx, pats, opts, badfn=bad)
527
527
528 follow = not opts.get(b'no_follow')
528 follow = not opts.get(b'no_follow')
529 diffopts = patch.difffeatureopts(
529 diffopts = patch.difffeatureopts(
530 ui, opts, section=b'annotate', whitespace=True
530 ui, opts, section=b'annotate', whitespace=True
531 )
531 )
532 skiprevs = opts.get(b'skip')
532 skiprevs = opts.get(b'skip')
533 if skiprevs:
533 if skiprevs:
534 skiprevs = scmutil.revrange(repo, skiprevs)
534 skiprevs = scmutil.revrange(repo, skiprevs)
535
535
536 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
536 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
537 for abs in ctx.walk(m):
537 for abs in ctx.walk(m):
538 fctx = ctx[abs]
538 fctx = ctx[abs]
539 rootfm.startitem()
539 rootfm.startitem()
540 rootfm.data(path=abs)
540 rootfm.data(path=abs)
541 if not opts.get(b'text') and fctx.isbinary():
541 if not opts.get(b'text') and fctx.isbinary():
542 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
542 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
543 continue
543 continue
544
544
545 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
545 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
546 lines = fctx.annotate(
546 lines = fctx.annotate(
547 follow=follow, skiprevs=skiprevs, diffopts=diffopts
547 follow=follow, skiprevs=skiprevs, diffopts=diffopts
548 )
548 )
549 if not lines:
549 if not lines:
550 fm.end()
550 fm.end()
551 continue
551 continue
552 formats = []
552 formats = []
553 pieces = []
553 pieces = []
554
554
555 for f, sep in funcmap:
555 for f, sep in funcmap:
556 l = [f(n) for n in lines]
556 l = [f(n) for n in lines]
557 if fm.isplain():
557 if fm.isplain():
558 sizes = [encoding.colwidth(x) for x in l]
558 sizes = [encoding.colwidth(x) for x in l]
559 ml = max(sizes)
559 ml = max(sizes)
560 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
560 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
561 else:
561 else:
562 formats.append([b'%s'] * len(l))
562 formats.append([b'%s'] * len(l))
563 pieces.append(l)
563 pieces.append(l)
564
564
565 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
565 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
566 fm.startitem()
566 fm.startitem()
567 fm.context(fctx=n.fctx)
567 fm.context(fctx=n.fctx)
568 fm.write(fields, b"".join(f), *p)
568 fm.write(fields, b"".join(f), *p)
569 if n.skip:
569 if n.skip:
570 fmt = b"* %s"
570 fmt = b"* %s"
571 else:
571 else:
572 fmt = b": %s"
572 fmt = b": %s"
573 fm.write(b'line', fmt, n.text)
573 fm.write(b'line', fmt, n.text)
574
574
575 if not lines[-1].text.endswith(b'\n'):
575 if not lines[-1].text.endswith(b'\n'):
576 fm.plain(b'\n')
576 fm.plain(b'\n')
577 fm.end()
577 fm.end()
578
578
579 rootfm.end()
579 rootfm.end()
580
580
581
581
582 @command(
582 @command(
583 b'archive',
583 b'archive',
584 [
584 [
585 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
585 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
586 (
586 (
587 b'p',
587 b'p',
588 b'prefix',
588 b'prefix',
589 b'',
589 b'',
590 _(b'directory prefix for files in archive'),
590 _(b'directory prefix for files in archive'),
591 _(b'PREFIX'),
591 _(b'PREFIX'),
592 ),
592 ),
593 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
593 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
594 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
594 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
595 ]
595 ]
596 + subrepoopts
596 + subrepoopts
597 + walkopts,
597 + walkopts,
598 _(b'[OPTION]... DEST'),
598 _(b'[OPTION]... DEST'),
599 helpcategory=command.CATEGORY_IMPORT_EXPORT,
599 helpcategory=command.CATEGORY_IMPORT_EXPORT,
600 )
600 )
601 def archive(ui, repo, dest, **opts):
601 def archive(ui, repo, dest, **opts):
602 '''create an unversioned archive of a repository revision
602 '''create an unversioned archive of a repository revision
603
603
604 By default, the revision used is the parent of the working
604 By default, the revision used is the parent of the working
605 directory; use -r/--rev to specify a different revision.
605 directory; use -r/--rev to specify a different revision.
606
606
607 The archive type is automatically detected based on file
607 The archive type is automatically detected based on file
608 extension (to override, use -t/--type).
608 extension (to override, use -t/--type).
609
609
610 .. container:: verbose
610 .. container:: verbose
611
611
612 Examples:
612 Examples:
613
613
614 - create a zip file containing the 1.0 release::
614 - create a zip file containing the 1.0 release::
615
615
616 hg archive -r 1.0 project-1.0.zip
616 hg archive -r 1.0 project-1.0.zip
617
617
618 - create a tarball excluding .hg files::
618 - create a tarball excluding .hg files::
619
619
620 hg archive project.tar.gz -X ".hg*"
620 hg archive project.tar.gz -X ".hg*"
621
621
622 Valid types are:
622 Valid types are:
623
623
624 :``files``: a directory full of files (default)
624 :``files``: a directory full of files (default)
625 :``tar``: tar archive, uncompressed
625 :``tar``: tar archive, uncompressed
626 :``tbz2``: tar archive, compressed using bzip2
626 :``tbz2``: tar archive, compressed using bzip2
627 :``tgz``: tar archive, compressed using gzip
627 :``tgz``: tar archive, compressed using gzip
628 :``txz``: tar archive, compressed using lzma (only in Python 3)
628 :``txz``: tar archive, compressed using lzma (only in Python 3)
629 :``uzip``: zip archive, uncompressed
629 :``uzip``: zip archive, uncompressed
630 :``zip``: zip archive, compressed using deflate
630 :``zip``: zip archive, compressed using deflate
631
631
632 The exact name of the destination archive or directory is given
632 The exact name of the destination archive or directory is given
633 using a format string; see :hg:`help export` for details.
633 using a format string; see :hg:`help export` for details.
634
634
635 Each member added to an archive file has a directory prefix
635 Each member added to an archive file has a directory prefix
636 prepended. Use -p/--prefix to specify a format string for the
636 prepended. Use -p/--prefix to specify a format string for the
637 prefix. The default is the basename of the archive, with suffixes
637 prefix. The default is the basename of the archive, with suffixes
638 removed.
638 removed.
639
639
640 Returns 0 on success.
640 Returns 0 on success.
641 '''
641 '''
642
642
643 opts = pycompat.byteskwargs(opts)
643 opts = pycompat.byteskwargs(opts)
644 rev = opts.get(b'rev')
644 rev = opts.get(b'rev')
645 if rev:
645 if rev:
646 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
646 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
647 ctx = scmutil.revsingle(repo, rev)
647 ctx = scmutil.revsingle(repo, rev)
648 if not ctx:
648 if not ctx:
649 raise error.Abort(_(b'no working directory: please specify a revision'))
649 raise error.Abort(_(b'no working directory: please specify a revision'))
650 node = ctx.node()
650 node = ctx.node()
651 dest = cmdutil.makefilename(ctx, dest)
651 dest = cmdutil.makefilename(ctx, dest)
652 if os.path.realpath(dest) == repo.root:
652 if os.path.realpath(dest) == repo.root:
653 raise error.Abort(_(b'repository root cannot be destination'))
653 raise error.Abort(_(b'repository root cannot be destination'))
654
654
655 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
655 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
656 prefix = opts.get(b'prefix')
656 prefix = opts.get(b'prefix')
657
657
658 if dest == b'-':
658 if dest == b'-':
659 if kind == b'files':
659 if kind == b'files':
660 raise error.Abort(_(b'cannot archive plain files to stdout'))
660 raise error.Abort(_(b'cannot archive plain files to stdout'))
661 dest = cmdutil.makefileobj(ctx, dest)
661 dest = cmdutil.makefileobj(ctx, dest)
662 if not prefix:
662 if not prefix:
663 prefix = os.path.basename(repo.root) + b'-%h'
663 prefix = os.path.basename(repo.root) + b'-%h'
664
664
665 prefix = cmdutil.makefilename(ctx, prefix)
665 prefix = cmdutil.makefilename(ctx, prefix)
666 match = scmutil.match(ctx, [], opts)
666 match = scmutil.match(ctx, [], opts)
667 archival.archive(
667 archival.archive(
668 repo,
668 repo,
669 dest,
669 dest,
670 node,
670 node,
671 kind,
671 kind,
672 not opts.get(b'no_decode'),
672 not opts.get(b'no_decode'),
673 match,
673 match,
674 prefix,
674 prefix,
675 subrepos=opts.get(b'subrepos'),
675 subrepos=opts.get(b'subrepos'),
676 )
676 )
677
677
678
678
679 @command(
679 @command(
680 b'backout',
680 b'backout',
681 [
681 [
682 (
682 (
683 b'',
683 b'',
684 b'merge',
684 b'merge',
685 None,
685 None,
686 _(b'merge with old dirstate parent after backout'),
686 _(b'merge with old dirstate parent after backout'),
687 ),
687 ),
688 (
688 (
689 b'',
689 b'',
690 b'commit',
690 b'commit',
691 None,
691 None,
692 _(b'commit if no conflicts were encountered (DEPRECATED)'),
692 _(b'commit if no conflicts were encountered (DEPRECATED)'),
693 ),
693 ),
694 (b'', b'no-commit', None, _(b'do not commit')),
694 (b'', b'no-commit', None, _(b'do not commit')),
695 (
695 (
696 b'',
696 b'',
697 b'parent',
697 b'parent',
698 b'',
698 b'',
699 _(b'parent to choose when backing out merge (DEPRECATED)'),
699 _(b'parent to choose when backing out merge (DEPRECATED)'),
700 _(b'REV'),
700 _(b'REV'),
701 ),
701 ),
702 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
702 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
703 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
703 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
704 ]
704 ]
705 + mergetoolopts
705 + mergetoolopts
706 + walkopts
706 + walkopts
707 + commitopts
707 + commitopts
708 + commitopts2,
708 + commitopts2,
709 _(b'[OPTION]... [-r] REV'),
709 _(b'[OPTION]... [-r] REV'),
710 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
710 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
711 )
711 )
712 def backout(ui, repo, node=None, rev=None, **opts):
712 def backout(ui, repo, node=None, rev=None, **opts):
713 '''reverse effect of earlier changeset
713 '''reverse effect of earlier changeset
714
714
715 Prepare a new changeset with the effect of REV undone in the
715 Prepare a new changeset with the effect of REV undone in the
716 current working directory. If no conflicts were encountered,
716 current working directory. If no conflicts were encountered,
717 it will be committed immediately.
717 it will be committed immediately.
718
718
719 If REV is the parent of the working directory, then this new changeset
719 If REV is the parent of the working directory, then this new changeset
720 is committed automatically (unless --no-commit is specified).
720 is committed automatically (unless --no-commit is specified).
721
721
722 .. note::
722 .. note::
723
723
724 :hg:`backout` cannot be used to fix either an unwanted or
724 :hg:`backout` cannot be used to fix either an unwanted or
725 incorrect merge.
725 incorrect merge.
726
726
727 .. container:: verbose
727 .. container:: verbose
728
728
729 Examples:
729 Examples:
730
730
731 - Reverse the effect of the parent of the working directory.
731 - Reverse the effect of the parent of the working directory.
732 This backout will be committed immediately::
732 This backout will be committed immediately::
733
733
734 hg backout -r .
734 hg backout -r .
735
735
736 - Reverse the effect of previous bad revision 23::
736 - Reverse the effect of previous bad revision 23::
737
737
738 hg backout -r 23
738 hg backout -r 23
739
739
740 - Reverse the effect of previous bad revision 23 and
740 - Reverse the effect of previous bad revision 23 and
741 leave changes uncommitted::
741 leave changes uncommitted::
742
742
743 hg backout -r 23 --no-commit
743 hg backout -r 23 --no-commit
744 hg commit -m "Backout revision 23"
744 hg commit -m "Backout revision 23"
745
745
746 By default, the pending changeset will have one parent,
746 By default, the pending changeset will have one parent,
747 maintaining a linear history. With --merge, the pending
747 maintaining a linear history. With --merge, the pending
748 changeset will instead have two parents: the old parent of the
748 changeset will instead have two parents: the old parent of the
749 working directory and a new child of REV that simply undoes REV.
749 working directory and a new child of REV that simply undoes REV.
750
750
751 Before version 1.7, the behavior without --merge was equivalent
751 Before version 1.7, the behavior without --merge was equivalent
752 to specifying --merge followed by :hg:`update --clean .` to
752 to specifying --merge followed by :hg:`update --clean .` to
753 cancel the merge and leave the child of REV as a head to be
753 cancel the merge and leave the child of REV as a head to be
754 merged separately.
754 merged separately.
755
755
756 See :hg:`help dates` for a list of formats valid for -d/--date.
756 See :hg:`help dates` for a list of formats valid for -d/--date.
757
757
758 See :hg:`help revert` for a way to restore files to the state
758 See :hg:`help revert` for a way to restore files to the state
759 of another revision.
759 of another revision.
760
760
761 Returns 0 on success, 1 if nothing to backout or there are unresolved
761 Returns 0 on success, 1 if nothing to backout or there are unresolved
762 files.
762 files.
763 '''
763 '''
764 with repo.wlock(), repo.lock():
764 with repo.wlock(), repo.lock():
765 return _dobackout(ui, repo, node, rev, **opts)
765 return _dobackout(ui, repo, node, rev, **opts)
766
766
767
767
768 def _dobackout(ui, repo, node=None, rev=None, **opts):
768 def _dobackout(ui, repo, node=None, rev=None, **opts):
769 opts = pycompat.byteskwargs(opts)
769 opts = pycompat.byteskwargs(opts)
770 if opts.get(b'commit') and opts.get(b'no_commit'):
770 if opts.get(b'commit') and opts.get(b'no_commit'):
771 raise error.Abort(_(b"cannot use --commit with --no-commit"))
771 raise error.Abort(_(b"cannot use --commit with --no-commit"))
772 if opts.get(b'merge') and opts.get(b'no_commit'):
772 if opts.get(b'merge') and opts.get(b'no_commit'):
773 raise error.Abort(_(b"cannot use --merge with --no-commit"))
773 raise error.Abort(_(b"cannot use --merge with --no-commit"))
774
774
775 if rev and node:
775 if rev and node:
776 raise error.Abort(_(b"please specify just one revision"))
776 raise error.Abort(_(b"please specify just one revision"))
777
777
778 if not rev:
778 if not rev:
779 rev = node
779 rev = node
780
780
781 if not rev:
781 if not rev:
782 raise error.Abort(_(b"please specify a revision to backout"))
782 raise error.Abort(_(b"please specify a revision to backout"))
783
783
784 date = opts.get(b'date')
784 date = opts.get(b'date')
785 if date:
785 if date:
786 opts[b'date'] = dateutil.parsedate(date)
786 opts[b'date'] = dateutil.parsedate(date)
787
787
788 cmdutil.checkunfinished(repo)
788 cmdutil.checkunfinished(repo)
789 cmdutil.bailifchanged(repo)
789 cmdutil.bailifchanged(repo)
790 node = scmutil.revsingle(repo, rev).node()
790 node = scmutil.revsingle(repo, rev).node()
791
791
792 op1, op2 = repo.dirstate.parents()
792 op1, op2 = repo.dirstate.parents()
793 if not repo.changelog.isancestor(node, op1):
793 if not repo.changelog.isancestor(node, op1):
794 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
794 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
795
795
796 p1, p2 = repo.changelog.parents(node)
796 p1, p2 = repo.changelog.parents(node)
797 if p1 == nullid:
797 if p1 == nullid:
798 raise error.Abort(_(b'cannot backout a change with no parents'))
798 raise error.Abort(_(b'cannot backout a change with no parents'))
799 if p2 != nullid:
799 if p2 != nullid:
800 if not opts.get(b'parent'):
800 if not opts.get(b'parent'):
801 raise error.Abort(_(b'cannot backout a merge changeset'))
801 raise error.Abort(_(b'cannot backout a merge changeset'))
802 p = repo.lookup(opts[b'parent'])
802 p = repo.lookup(opts[b'parent'])
803 if p not in (p1, p2):
803 if p not in (p1, p2):
804 raise error.Abort(
804 raise error.Abort(
805 _(b'%s is not a parent of %s') % (short(p), short(node))
805 _(b'%s is not a parent of %s') % (short(p), short(node))
806 )
806 )
807 parent = p
807 parent = p
808 else:
808 else:
809 if opts.get(b'parent'):
809 if opts.get(b'parent'):
810 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
810 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
811 parent = p1
811 parent = p1
812
812
813 # the backout should appear on the same branch
813 # the backout should appear on the same branch
814 branch = repo.dirstate.branch()
814 branch = repo.dirstate.branch()
815 bheads = repo.branchheads(branch)
815 bheads = repo.branchheads(branch)
816 rctx = scmutil.revsingle(repo, hex(parent))
816 rctx = scmutil.revsingle(repo, hex(parent))
817 if not opts.get(b'merge') and op1 != node:
817 if not opts.get(b'merge') and op1 != node:
818 with dirstateguard.dirstateguard(repo, b'backout'):
818 with dirstateguard.dirstateguard(repo, b'backout'):
819 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
819 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
820 with ui.configoverride(overrides, b'backout'):
820 with ui.configoverride(overrides, b'backout'):
821 stats = mergemod.update(
821 stats = mergemod.update(
822 repo,
822 repo,
823 parent,
823 parent,
824 branchmerge=True,
824 branchmerge=True,
825 force=True,
825 force=True,
826 ancestor=node,
826 ancestor=node,
827 mergeancestor=False,
827 mergeancestor=False,
828 )
828 )
829 repo.setparents(op1, op2)
829 repo.setparents(op1, op2)
830 hg._showstats(repo, stats)
830 hg._showstats(repo, stats)
831 if stats.unresolvedcount:
831 if stats.unresolvedcount:
832 repo.ui.status(
832 repo.ui.status(
833 _(b"use 'hg resolve' to retry unresolved file merges\n")
833 _(b"use 'hg resolve' to retry unresolved file merges\n")
834 )
834 )
835 return 1
835 return 1
836 else:
836 else:
837 hg.clean(repo, node, show_stats=False)
837 hg.clean(repo, node, show_stats=False)
838 repo.dirstate.setbranch(branch)
838 repo.dirstate.setbranch(branch)
839 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
839 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
840
840
841 if opts.get(b'no_commit'):
841 if opts.get(b'no_commit'):
842 msg = _(b"changeset %s backed out, don't forget to commit.\n")
842 msg = _(b"changeset %s backed out, don't forget to commit.\n")
843 ui.status(msg % short(node))
843 ui.status(msg % short(node))
844 return 0
844 return 0
845
845
846 def commitfunc(ui, repo, message, match, opts):
846 def commitfunc(ui, repo, message, match, opts):
847 editform = b'backout'
847 editform = b'backout'
848 e = cmdutil.getcommiteditor(
848 e = cmdutil.getcommiteditor(
849 editform=editform, **pycompat.strkwargs(opts)
849 editform=editform, **pycompat.strkwargs(opts)
850 )
850 )
851 if not message:
851 if not message:
852 # we don't translate commit messages
852 # we don't translate commit messages
853 message = b"Backed out changeset %s" % short(node)
853 message = b"Backed out changeset %s" % short(node)
854 e = cmdutil.getcommiteditor(edit=True, editform=editform)
854 e = cmdutil.getcommiteditor(edit=True, editform=editform)
855 return repo.commit(
855 return repo.commit(
856 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
856 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
857 )
857 )
858
858
859 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
859 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
860 if not newnode:
860 if not newnode:
861 ui.status(_(b"nothing changed\n"))
861 ui.status(_(b"nothing changed\n"))
862 return 1
862 return 1
863 cmdutil.commitstatus(repo, newnode, branch, bheads)
863 cmdutil.commitstatus(repo, newnode, branch, bheads)
864
864
865 def nice(node):
865 def nice(node):
866 return b'%d:%s' % (repo.changelog.rev(node), short(node))
866 return b'%d:%s' % (repo.changelog.rev(node), short(node))
867
867
868 ui.status(
868 ui.status(
869 _(b'changeset %s backs out changeset %s\n')
869 _(b'changeset %s backs out changeset %s\n')
870 % (nice(repo.changelog.tip()), nice(node))
870 % (nice(repo.changelog.tip()), nice(node))
871 )
871 )
872 if opts.get(b'merge') and op1 != node:
872 if opts.get(b'merge') and op1 != node:
873 hg.clean(repo, op1, show_stats=False)
873 hg.clean(repo, op1, show_stats=False)
874 ui.status(
874 ui.status(
875 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
875 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
876 )
876 )
877 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
877 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
878 with ui.configoverride(overrides, b'backout'):
878 with ui.configoverride(overrides, b'backout'):
879 return hg.merge(repo, hex(repo.changelog.tip()))
879 return hg.merge(repo, hex(repo.changelog.tip()))
880 return 0
880 return 0
881
881
882
882
883 @command(
883 @command(
884 b'bisect',
884 b'bisect',
885 [
885 [
886 (b'r', b'reset', False, _(b'reset bisect state')),
886 (b'r', b'reset', False, _(b'reset bisect state')),
887 (b'g', b'good', False, _(b'mark changeset good')),
887 (b'g', b'good', False, _(b'mark changeset good')),
888 (b'b', b'bad', False, _(b'mark changeset bad')),
888 (b'b', b'bad', False, _(b'mark changeset bad')),
889 (b's', b'skip', False, _(b'skip testing changeset')),
889 (b's', b'skip', False, _(b'skip testing changeset')),
890 (b'e', b'extend', False, _(b'extend the bisect range')),
890 (b'e', b'extend', False, _(b'extend the bisect range')),
891 (
891 (
892 b'c',
892 b'c',
893 b'command',
893 b'command',
894 b'',
894 b'',
895 _(b'use command to check changeset state'),
895 _(b'use command to check changeset state'),
896 _(b'CMD'),
896 _(b'CMD'),
897 ),
897 ),
898 (b'U', b'noupdate', False, _(b'do not update to target')),
898 (b'U', b'noupdate', False, _(b'do not update to target')),
899 ],
899 ],
900 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
900 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
901 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
901 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
902 )
902 )
903 def bisect(
903 def bisect(
904 ui,
904 ui,
905 repo,
905 repo,
906 rev=None,
906 rev=None,
907 extra=None,
907 extra=None,
908 command=None,
908 command=None,
909 reset=None,
909 reset=None,
910 good=None,
910 good=None,
911 bad=None,
911 bad=None,
912 skip=None,
912 skip=None,
913 extend=None,
913 extend=None,
914 noupdate=None,
914 noupdate=None,
915 ):
915 ):
916 """subdivision search of changesets
916 """subdivision search of changesets
917
917
918 This command helps to find changesets which introduce problems. To
918 This command helps to find changesets which introduce problems. To
919 use, mark the earliest changeset you know exhibits the problem as
919 use, mark the earliest changeset you know exhibits the problem as
920 bad, then mark the latest changeset which is free from the problem
920 bad, then mark the latest changeset which is free from the problem
921 as good. Bisect will update your working directory to a revision
921 as good. Bisect will update your working directory to a revision
922 for testing (unless the -U/--noupdate option is specified). Once
922 for testing (unless the -U/--noupdate option is specified). Once
923 you have performed tests, mark the working directory as good or
923 you have performed tests, mark the working directory as good or
924 bad, and bisect will either update to another candidate changeset
924 bad, and bisect will either update to another candidate changeset
925 or announce that it has found the bad revision.
925 or announce that it has found the bad revision.
926
926
927 As a shortcut, you can also use the revision argument to mark a
927 As a shortcut, you can also use the revision argument to mark a
928 revision as good or bad without checking it out first.
928 revision as good or bad without checking it out first.
929
929
930 If you supply a command, it will be used for automatic bisection.
930 If you supply a command, it will be used for automatic bisection.
931 The environment variable HG_NODE will contain the ID of the
931 The environment variable HG_NODE will contain the ID of the
932 changeset being tested. The exit status of the command will be
932 changeset being tested. The exit status of the command will be
933 used to mark revisions as good or bad: status 0 means good, 125
933 used to mark revisions as good or bad: status 0 means good, 125
934 means to skip the revision, 127 (command not found) will abort the
934 means to skip the revision, 127 (command not found) will abort the
935 bisection, and any other non-zero exit status means the revision
935 bisection, and any other non-zero exit status means the revision
936 is bad.
936 is bad.
937
937
938 .. container:: verbose
938 .. container:: verbose
939
939
940 Some examples:
940 Some examples:
941
941
942 - start a bisection with known bad revision 34, and good revision 12::
942 - start a bisection with known bad revision 34, and good revision 12::
943
943
944 hg bisect --bad 34
944 hg bisect --bad 34
945 hg bisect --good 12
945 hg bisect --good 12
946
946
947 - advance the current bisection by marking current revision as good or
947 - advance the current bisection by marking current revision as good or
948 bad::
948 bad::
949
949
950 hg bisect --good
950 hg bisect --good
951 hg bisect --bad
951 hg bisect --bad
952
952
953 - mark the current revision, or a known revision, to be skipped (e.g. if
953 - mark the current revision, or a known revision, to be skipped (e.g. if
954 that revision is not usable because of another issue)::
954 that revision is not usable because of another issue)::
955
955
956 hg bisect --skip
956 hg bisect --skip
957 hg bisect --skip 23
957 hg bisect --skip 23
958
958
959 - skip all revisions that do not touch directories ``foo`` or ``bar``::
959 - skip all revisions that do not touch directories ``foo`` or ``bar``::
960
960
961 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
961 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
962
962
963 - forget the current bisection::
963 - forget the current bisection::
964
964
965 hg bisect --reset
965 hg bisect --reset
966
966
967 - use 'make && make tests' to automatically find the first broken
967 - use 'make && make tests' to automatically find the first broken
968 revision::
968 revision::
969
969
970 hg bisect --reset
970 hg bisect --reset
971 hg bisect --bad 34
971 hg bisect --bad 34
972 hg bisect --good 12
972 hg bisect --good 12
973 hg bisect --command "make && make tests"
973 hg bisect --command "make && make tests"
974
974
975 - see all changesets whose states are already known in the current
975 - see all changesets whose states are already known in the current
976 bisection::
976 bisection::
977
977
978 hg log -r "bisect(pruned)"
978 hg log -r "bisect(pruned)"
979
979
980 - see the changeset currently being bisected (especially useful
980 - see the changeset currently being bisected (especially useful
981 if running with -U/--noupdate)::
981 if running with -U/--noupdate)::
982
982
983 hg log -r "bisect(current)"
983 hg log -r "bisect(current)"
984
984
985 - see all changesets that took part in the current bisection::
985 - see all changesets that took part in the current bisection::
986
986
987 hg log -r "bisect(range)"
987 hg log -r "bisect(range)"
988
988
989 - you can even get a nice graph::
989 - you can even get a nice graph::
990
990
991 hg log --graph -r "bisect(range)"
991 hg log --graph -r "bisect(range)"
992
992
993 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
993 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
994
994
995 Returns 0 on success.
995 Returns 0 on success.
996 """
996 """
997 # backward compatibility
997 # backward compatibility
998 if rev in b"good bad reset init".split():
998 if rev in b"good bad reset init".split():
999 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
999 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1000 cmd, rev, extra = rev, extra, None
1000 cmd, rev, extra = rev, extra, None
1001 if cmd == b"good":
1001 if cmd == b"good":
1002 good = True
1002 good = True
1003 elif cmd == b"bad":
1003 elif cmd == b"bad":
1004 bad = True
1004 bad = True
1005 else:
1005 else:
1006 reset = True
1006 reset = True
1007 elif extra:
1007 elif extra:
1008 raise error.Abort(_(b'incompatible arguments'))
1008 raise error.Abort(_(b'incompatible arguments'))
1009
1009
1010 incompatibles = {
1010 incompatibles = {
1011 b'--bad': bad,
1011 b'--bad': bad,
1012 b'--command': bool(command),
1012 b'--command': bool(command),
1013 b'--extend': extend,
1013 b'--extend': extend,
1014 b'--good': good,
1014 b'--good': good,
1015 b'--reset': reset,
1015 b'--reset': reset,
1016 b'--skip': skip,
1016 b'--skip': skip,
1017 }
1017 }
1018
1018
1019 enabled = [x for x in incompatibles if incompatibles[x]]
1019 enabled = [x for x in incompatibles if incompatibles[x]]
1020
1020
1021 if len(enabled) > 1:
1021 if len(enabled) > 1:
1022 raise error.Abort(
1022 raise error.Abort(
1023 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1023 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1024 )
1024 )
1025
1025
1026 if reset:
1026 if reset:
1027 hbisect.resetstate(repo)
1027 hbisect.resetstate(repo)
1028 return
1028 return
1029
1029
1030 state = hbisect.load_state(repo)
1030 state = hbisect.load_state(repo)
1031
1031
1032 # update state
1032 # update state
1033 if good or bad or skip:
1033 if good or bad or skip:
1034 if rev:
1034 if rev:
1035 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1035 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1036 else:
1036 else:
1037 nodes = [repo.lookup(b'.')]
1037 nodes = [repo.lookup(b'.')]
1038 if good:
1038 if good:
1039 state[b'good'] += nodes
1039 state[b'good'] += nodes
1040 elif bad:
1040 elif bad:
1041 state[b'bad'] += nodes
1041 state[b'bad'] += nodes
1042 elif skip:
1042 elif skip:
1043 state[b'skip'] += nodes
1043 state[b'skip'] += nodes
1044 hbisect.save_state(repo, state)
1044 hbisect.save_state(repo, state)
1045 if not (state[b'good'] and state[b'bad']):
1045 if not (state[b'good'] and state[b'bad']):
1046 return
1046 return
1047
1047
1048 def mayupdate(repo, node, show_stats=True):
1048 def mayupdate(repo, node, show_stats=True):
1049 """common used update sequence"""
1049 """common used update sequence"""
1050 if noupdate:
1050 if noupdate:
1051 return
1051 return
1052 cmdutil.checkunfinished(repo)
1052 cmdutil.checkunfinished(repo)
1053 cmdutil.bailifchanged(repo)
1053 cmdutil.bailifchanged(repo)
1054 return hg.clean(repo, node, show_stats=show_stats)
1054 return hg.clean(repo, node, show_stats=show_stats)
1055
1055
1056 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1056 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1057
1057
1058 if command:
1058 if command:
1059 changesets = 1
1059 changesets = 1
1060 if noupdate:
1060 if noupdate:
1061 try:
1061 try:
1062 node = state[b'current'][0]
1062 node = state[b'current'][0]
1063 except LookupError:
1063 except LookupError:
1064 raise error.Abort(
1064 raise error.Abort(
1065 _(
1065 _(
1066 b'current bisect revision is unknown - '
1066 b'current bisect revision is unknown - '
1067 b'start a new bisect to fix'
1067 b'start a new bisect to fix'
1068 )
1068 )
1069 )
1069 )
1070 else:
1070 else:
1071 node, p2 = repo.dirstate.parents()
1071 node, p2 = repo.dirstate.parents()
1072 if p2 != nullid:
1072 if p2 != nullid:
1073 raise error.Abort(_(b'current bisect revision is a merge'))
1073 raise error.Abort(_(b'current bisect revision is a merge'))
1074 if rev:
1074 if rev:
1075 node = repo[scmutil.revsingle(repo, rev, node)].node()
1075 node = repo[scmutil.revsingle(repo, rev, node)].node()
1076 with hbisect.restore_state(repo, state, node):
1076 with hbisect.restore_state(repo, state, node):
1077 while changesets:
1077 while changesets:
1078 # update state
1078 # update state
1079 state[b'current'] = [node]
1079 state[b'current'] = [node]
1080 hbisect.save_state(repo, state)
1080 hbisect.save_state(repo, state)
1081 status = ui.system(
1081 status = ui.system(
1082 command,
1082 command,
1083 environ={b'HG_NODE': hex(node)},
1083 environ={b'HG_NODE': hex(node)},
1084 blockedtag=b'bisect_check',
1084 blockedtag=b'bisect_check',
1085 )
1085 )
1086 if status == 125:
1086 if status == 125:
1087 transition = b"skip"
1087 transition = b"skip"
1088 elif status == 0:
1088 elif status == 0:
1089 transition = b"good"
1089 transition = b"good"
1090 # status < 0 means process was killed
1090 # status < 0 means process was killed
1091 elif status == 127:
1091 elif status == 127:
1092 raise error.Abort(_(b"failed to execute %s") % command)
1092 raise error.Abort(_(b"failed to execute %s") % command)
1093 elif status < 0:
1093 elif status < 0:
1094 raise error.Abort(_(b"%s killed") % command)
1094 raise error.Abort(_(b"%s killed") % command)
1095 else:
1095 else:
1096 transition = b"bad"
1096 transition = b"bad"
1097 state[transition].append(node)
1097 state[transition].append(node)
1098 ctx = repo[node]
1098 ctx = repo[node]
1099 ui.status(
1099 ui.status(
1100 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1100 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1101 )
1101 )
1102 hbisect.checkstate(state)
1102 hbisect.checkstate(state)
1103 # bisect
1103 # bisect
1104 nodes, changesets, bgood = hbisect.bisect(repo, state)
1104 nodes, changesets, bgood = hbisect.bisect(repo, state)
1105 # update to next check
1105 # update to next check
1106 node = nodes[0]
1106 node = nodes[0]
1107 mayupdate(repo, node, show_stats=False)
1107 mayupdate(repo, node, show_stats=False)
1108 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1108 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1109 return
1109 return
1110
1110
1111 hbisect.checkstate(state)
1111 hbisect.checkstate(state)
1112
1112
1113 # actually bisect
1113 # actually bisect
1114 nodes, changesets, good = hbisect.bisect(repo, state)
1114 nodes, changesets, good = hbisect.bisect(repo, state)
1115 if extend:
1115 if extend:
1116 if not changesets:
1116 if not changesets:
1117 extendnode = hbisect.extendrange(repo, state, nodes, good)
1117 extendnode = hbisect.extendrange(repo, state, nodes, good)
1118 if extendnode is not None:
1118 if extendnode is not None:
1119 ui.write(
1119 ui.write(
1120 _(b"Extending search to changeset %d:%s\n")
1120 _(b"Extending search to changeset %d:%s\n")
1121 % (extendnode.rev(), extendnode)
1121 % (extendnode.rev(), extendnode)
1122 )
1122 )
1123 state[b'current'] = [extendnode.node()]
1123 state[b'current'] = [extendnode.node()]
1124 hbisect.save_state(repo, state)
1124 hbisect.save_state(repo, state)
1125 return mayupdate(repo, extendnode.node())
1125 return mayupdate(repo, extendnode.node())
1126 raise error.Abort(_(b"nothing to extend"))
1126 raise error.Abort(_(b"nothing to extend"))
1127
1127
1128 if changesets == 0:
1128 if changesets == 0:
1129 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1129 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1130 else:
1130 else:
1131 assert len(nodes) == 1 # only a single node can be tested next
1131 assert len(nodes) == 1 # only a single node can be tested next
1132 node = nodes[0]
1132 node = nodes[0]
1133 # compute the approximate number of remaining tests
1133 # compute the approximate number of remaining tests
1134 tests, size = 0, 2
1134 tests, size = 0, 2
1135 while size <= changesets:
1135 while size <= changesets:
1136 tests, size = tests + 1, size * 2
1136 tests, size = tests + 1, size * 2
1137 rev = repo.changelog.rev(node)
1137 rev = repo.changelog.rev(node)
1138 ui.write(
1138 ui.write(
1139 _(
1139 _(
1140 b"Testing changeset %d:%s "
1140 b"Testing changeset %d:%s "
1141 b"(%d changesets remaining, ~%d tests)\n"
1141 b"(%d changesets remaining, ~%d tests)\n"
1142 )
1142 )
1143 % (rev, short(node), changesets, tests)
1143 % (rev, short(node), changesets, tests)
1144 )
1144 )
1145 state[b'current'] = [node]
1145 state[b'current'] = [node]
1146 hbisect.save_state(repo, state)
1146 hbisect.save_state(repo, state)
1147 return mayupdate(repo, node)
1147 return mayupdate(repo, node)
1148
1148
1149
1149
1150 @command(
1150 @command(
1151 b'bookmarks|bookmark',
1151 b'bookmarks|bookmark',
1152 [
1152 [
1153 (b'f', b'force', False, _(b'force')),
1153 (b'f', b'force', False, _(b'force')),
1154 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1154 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1155 (b'd', b'delete', False, _(b'delete a given bookmark')),
1155 (b'd', b'delete', False, _(b'delete a given bookmark')),
1156 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1156 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1157 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1157 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1158 (b'l', b'list', False, _(b'list existing bookmarks')),
1158 (b'l', b'list', False, _(b'list existing bookmarks')),
1159 ]
1159 ]
1160 + formatteropts,
1160 + formatteropts,
1161 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1161 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1162 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1162 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1163 )
1163 )
1164 def bookmark(ui, repo, *names, **opts):
1164 def bookmark(ui, repo, *names, **opts):
1165 '''create a new bookmark or list existing bookmarks
1165 '''create a new bookmark or list existing bookmarks
1166
1166
1167 Bookmarks are labels on changesets to help track lines of development.
1167 Bookmarks are labels on changesets to help track lines of development.
1168 Bookmarks are unversioned and can be moved, renamed and deleted.
1168 Bookmarks are unversioned and can be moved, renamed and deleted.
1169 Deleting or moving a bookmark has no effect on the associated changesets.
1169 Deleting or moving a bookmark has no effect on the associated changesets.
1170
1170
1171 Creating or updating to a bookmark causes it to be marked as 'active'.
1171 Creating or updating to a bookmark causes it to be marked as 'active'.
1172 The active bookmark is indicated with a '*'.
1172 The active bookmark is indicated with a '*'.
1173 When a commit is made, the active bookmark will advance to the new commit.
1173 When a commit is made, the active bookmark will advance to the new commit.
1174 A plain :hg:`update` will also advance an active bookmark, if possible.
1174 A plain :hg:`update` will also advance an active bookmark, if possible.
1175 Updating away from a bookmark will cause it to be deactivated.
1175 Updating away from a bookmark will cause it to be deactivated.
1176
1176
1177 Bookmarks can be pushed and pulled between repositories (see
1177 Bookmarks can be pushed and pulled between repositories (see
1178 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1178 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1179 diverged, a new 'divergent bookmark' of the form 'name@path' will
1179 diverged, a new 'divergent bookmark' of the form 'name@path' will
1180 be created. Using :hg:`merge` will resolve the divergence.
1180 be created. Using :hg:`merge` will resolve the divergence.
1181
1181
1182 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1182 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1183 the active bookmark's name.
1183 the active bookmark's name.
1184
1184
1185 A bookmark named '@' has the special property that :hg:`clone` will
1185 A bookmark named '@' has the special property that :hg:`clone` will
1186 check it out by default if it exists.
1186 check it out by default if it exists.
1187
1187
1188 .. container:: verbose
1188 .. container:: verbose
1189
1189
1190 Template:
1190 Template:
1191
1191
1192 The following keywords are supported in addition to the common template
1192 The following keywords are supported in addition to the common template
1193 keywords and functions such as ``{bookmark}``. See also
1193 keywords and functions such as ``{bookmark}``. See also
1194 :hg:`help templates`.
1194 :hg:`help templates`.
1195
1195
1196 :active: Boolean. True if the bookmark is active.
1196 :active: Boolean. True if the bookmark is active.
1197
1197
1198 Examples:
1198 Examples:
1199
1199
1200 - create an active bookmark for a new line of development::
1200 - create an active bookmark for a new line of development::
1201
1201
1202 hg book new-feature
1202 hg book new-feature
1203
1203
1204 - create an inactive bookmark as a place marker::
1204 - create an inactive bookmark as a place marker::
1205
1205
1206 hg book -i reviewed
1206 hg book -i reviewed
1207
1207
1208 - create an inactive bookmark on another changeset::
1208 - create an inactive bookmark on another changeset::
1209
1209
1210 hg book -r .^ tested
1210 hg book -r .^ tested
1211
1211
1212 - rename bookmark turkey to dinner::
1212 - rename bookmark turkey to dinner::
1213
1213
1214 hg book -m turkey dinner
1214 hg book -m turkey dinner
1215
1215
1216 - move the '@' bookmark from another branch::
1216 - move the '@' bookmark from another branch::
1217
1217
1218 hg book -f @
1218 hg book -f @
1219
1219
1220 - print only the active bookmark name::
1220 - print only the active bookmark name::
1221
1221
1222 hg book -ql .
1222 hg book -ql .
1223 '''
1223 '''
1224 opts = pycompat.byteskwargs(opts)
1224 opts = pycompat.byteskwargs(opts)
1225 force = opts.get(b'force')
1225 force = opts.get(b'force')
1226 rev = opts.get(b'rev')
1226 rev = opts.get(b'rev')
1227 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1227 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1228
1228
1229 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1229 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1230 if action:
1230 if action:
1231 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1231 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1232 elif names or rev:
1232 elif names or rev:
1233 action = b'add'
1233 action = b'add'
1234 elif inactive:
1234 elif inactive:
1235 action = b'inactive' # meaning deactivate
1235 action = b'inactive' # meaning deactivate
1236 else:
1236 else:
1237 action = b'list'
1237 action = b'list'
1238
1238
1239 cmdutil.check_incompatible_arguments(
1239 cmdutil.check_incompatible_arguments(
1240 opts, b'inactive', [b'delete', b'list']
1240 opts, b'inactive', [b'delete', b'list']
1241 )
1241 )
1242 if not names and action in {b'add', b'delete'}:
1242 if not names and action in {b'add', b'delete'}:
1243 raise error.Abort(_(b"bookmark name required"))
1243 raise error.Abort(_(b"bookmark name required"))
1244
1244
1245 if action in {b'add', b'delete', b'rename', b'inactive'}:
1245 if action in {b'add', b'delete', b'rename', b'inactive'}:
1246 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1246 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1247 if action == b'delete':
1247 if action == b'delete':
1248 names = pycompat.maplist(repo._bookmarks.expandname, names)
1248 names = pycompat.maplist(repo._bookmarks.expandname, names)
1249 bookmarks.delete(repo, tr, names)
1249 bookmarks.delete(repo, tr, names)
1250 elif action == b'rename':
1250 elif action == b'rename':
1251 if not names:
1251 if not names:
1252 raise error.Abort(_(b"new bookmark name required"))
1252 raise error.Abort(_(b"new bookmark name required"))
1253 elif len(names) > 1:
1253 elif len(names) > 1:
1254 raise error.Abort(_(b"only one new bookmark name allowed"))
1254 raise error.Abort(_(b"only one new bookmark name allowed"))
1255 oldname = repo._bookmarks.expandname(opts[b'rename'])
1255 oldname = repo._bookmarks.expandname(opts[b'rename'])
1256 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1256 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1257 elif action == b'add':
1257 elif action == b'add':
1258 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1258 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1259 elif action == b'inactive':
1259 elif action == b'inactive':
1260 if len(repo._bookmarks) == 0:
1260 if len(repo._bookmarks) == 0:
1261 ui.status(_(b"no bookmarks set\n"))
1261 ui.status(_(b"no bookmarks set\n"))
1262 elif not repo._activebookmark:
1262 elif not repo._activebookmark:
1263 ui.status(_(b"no active bookmark\n"))
1263 ui.status(_(b"no active bookmark\n"))
1264 else:
1264 else:
1265 bookmarks.deactivate(repo)
1265 bookmarks.deactivate(repo)
1266 elif action == b'list':
1266 elif action == b'list':
1267 names = pycompat.maplist(repo._bookmarks.expandname, names)
1267 names = pycompat.maplist(repo._bookmarks.expandname, names)
1268 with ui.formatter(b'bookmarks', opts) as fm:
1268 with ui.formatter(b'bookmarks', opts) as fm:
1269 bookmarks.printbookmarks(ui, repo, fm, names)
1269 bookmarks.printbookmarks(ui, repo, fm, names)
1270 else:
1270 else:
1271 raise error.ProgrammingError(b'invalid action: %s' % action)
1271 raise error.ProgrammingError(b'invalid action: %s' % action)
1272
1272
1273
1273
1274 @command(
1274 @command(
1275 b'branch',
1275 b'branch',
1276 [
1276 [
1277 (
1277 (
1278 b'f',
1278 b'f',
1279 b'force',
1279 b'force',
1280 None,
1280 None,
1281 _(b'set branch name even if it shadows an existing branch'),
1281 _(b'set branch name even if it shadows an existing branch'),
1282 ),
1282 ),
1283 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1283 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1284 (
1284 (
1285 b'r',
1285 b'r',
1286 b'rev',
1286 b'rev',
1287 [],
1287 [],
1288 _(b'change branches of the given revs (EXPERIMENTAL)'),
1288 _(b'change branches of the given revs (EXPERIMENTAL)'),
1289 ),
1289 ),
1290 ],
1290 ],
1291 _(b'[-fC] [NAME]'),
1291 _(b'[-fC] [NAME]'),
1292 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1292 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1293 )
1293 )
1294 def branch(ui, repo, label=None, **opts):
1294 def branch(ui, repo, label=None, **opts):
1295 """set or show the current branch name
1295 """set or show the current branch name
1296
1296
1297 .. note::
1297 .. note::
1298
1298
1299 Branch names are permanent and global. Use :hg:`bookmark` to create a
1299 Branch names are permanent and global. Use :hg:`bookmark` to create a
1300 light-weight bookmark instead. See :hg:`help glossary` for more
1300 light-weight bookmark instead. See :hg:`help glossary` for more
1301 information about named branches and bookmarks.
1301 information about named branches and bookmarks.
1302
1302
1303 With no argument, show the current branch name. With one argument,
1303 With no argument, show the current branch name. With one argument,
1304 set the working directory branch name (the branch will not exist
1304 set the working directory branch name (the branch will not exist
1305 in the repository until the next commit). Standard practice
1305 in the repository until the next commit). Standard practice
1306 recommends that primary development take place on the 'default'
1306 recommends that primary development take place on the 'default'
1307 branch.
1307 branch.
1308
1308
1309 Unless -f/--force is specified, branch will not let you set a
1309 Unless -f/--force is specified, branch will not let you set a
1310 branch name that already exists.
1310 branch name that already exists.
1311
1311
1312 Use -C/--clean to reset the working directory branch to that of
1312 Use -C/--clean to reset the working directory branch to that of
1313 the parent of the working directory, negating a previous branch
1313 the parent of the working directory, negating a previous branch
1314 change.
1314 change.
1315
1315
1316 Use the command :hg:`update` to switch to an existing branch. Use
1316 Use the command :hg:`update` to switch to an existing branch. Use
1317 :hg:`commit --close-branch` to mark this branch head as closed.
1317 :hg:`commit --close-branch` to mark this branch head as closed.
1318 When all heads of a branch are closed, the branch will be
1318 When all heads of a branch are closed, the branch will be
1319 considered closed.
1319 considered closed.
1320
1320
1321 Returns 0 on success.
1321 Returns 0 on success.
1322 """
1322 """
1323 opts = pycompat.byteskwargs(opts)
1323 opts = pycompat.byteskwargs(opts)
1324 revs = opts.get(b'rev')
1324 revs = opts.get(b'rev')
1325 if label:
1325 if label:
1326 label = label.strip()
1326 label = label.strip()
1327
1327
1328 if not opts.get(b'clean') and not label:
1328 if not opts.get(b'clean') and not label:
1329 if revs:
1329 if revs:
1330 raise error.Abort(_(b"no branch name specified for the revisions"))
1330 raise error.Abort(_(b"no branch name specified for the revisions"))
1331 ui.write(b"%s\n" % repo.dirstate.branch())
1331 ui.write(b"%s\n" % repo.dirstate.branch())
1332 return
1332 return
1333
1333
1334 with repo.wlock():
1334 with repo.wlock():
1335 if opts.get(b'clean'):
1335 if opts.get(b'clean'):
1336 label = repo[b'.'].branch()
1336 label = repo[b'.'].branch()
1337 repo.dirstate.setbranch(label)
1337 repo.dirstate.setbranch(label)
1338 ui.status(_(b'reset working directory to branch %s\n') % label)
1338 ui.status(_(b'reset working directory to branch %s\n') % label)
1339 elif label:
1339 elif label:
1340
1340
1341 scmutil.checknewlabel(repo, label, b'branch')
1341 scmutil.checknewlabel(repo, label, b'branch')
1342 if revs:
1342 if revs:
1343 return cmdutil.changebranch(ui, repo, revs, label)
1343 return cmdutil.changebranch(ui, repo, revs, label)
1344
1344
1345 if not opts.get(b'force') and label in repo.branchmap():
1345 if not opts.get(b'force') and label in repo.branchmap():
1346 if label not in [p.branch() for p in repo[None].parents()]:
1346 if label not in [p.branch() for p in repo[None].parents()]:
1347 raise error.Abort(
1347 raise error.Abort(
1348 _(b'a branch of the same name already exists'),
1348 _(b'a branch of the same name already exists'),
1349 # i18n: "it" refers to an existing branch
1349 # i18n: "it" refers to an existing branch
1350 hint=_(b"use 'hg update' to switch to it"),
1350 hint=_(b"use 'hg update' to switch to it"),
1351 )
1351 )
1352
1352
1353 repo.dirstate.setbranch(label)
1353 repo.dirstate.setbranch(label)
1354 ui.status(_(b'marked working directory as branch %s\n') % label)
1354 ui.status(_(b'marked working directory as branch %s\n') % label)
1355
1355
1356 # find any open named branches aside from default
1356 # find any open named branches aside from default
1357 for n, h, t, c in repo.branchmap().iterbranches():
1357 for n, h, t, c in repo.branchmap().iterbranches():
1358 if n != b"default" and not c:
1358 if n != b"default" and not c:
1359 return 0
1359 return 0
1360 ui.status(
1360 ui.status(
1361 _(
1361 _(
1362 b'(branches are permanent and global, '
1362 b'(branches are permanent and global, '
1363 b'did you want a bookmark?)\n'
1363 b'did you want a bookmark?)\n'
1364 )
1364 )
1365 )
1365 )
1366
1366
1367
1367
1368 @command(
1368 @command(
1369 b'branches',
1369 b'branches',
1370 [
1370 [
1371 (
1371 (
1372 b'a',
1372 b'a',
1373 b'active',
1373 b'active',
1374 False,
1374 False,
1375 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1375 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1376 ),
1376 ),
1377 (b'c', b'closed', False, _(b'show normal and closed branches')),
1377 (b'c', b'closed', False, _(b'show normal and closed branches')),
1378 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1378 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1379 ]
1379 ]
1380 + formatteropts,
1380 + formatteropts,
1381 _(b'[-c]'),
1381 _(b'[-c]'),
1382 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1382 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1383 intents={INTENT_READONLY},
1383 intents={INTENT_READONLY},
1384 )
1384 )
1385 def branches(ui, repo, active=False, closed=False, **opts):
1385 def branches(ui, repo, active=False, closed=False, **opts):
1386 """list repository named branches
1386 """list repository named branches
1387
1387
1388 List the repository's named branches, indicating which ones are
1388 List the repository's named branches, indicating which ones are
1389 inactive. If -c/--closed is specified, also list branches which have
1389 inactive. If -c/--closed is specified, also list branches which have
1390 been marked closed (see :hg:`commit --close-branch`).
1390 been marked closed (see :hg:`commit --close-branch`).
1391
1391
1392 Use the command :hg:`update` to switch to an existing branch.
1392 Use the command :hg:`update` to switch to an existing branch.
1393
1393
1394 .. container:: verbose
1394 .. container:: verbose
1395
1395
1396 Template:
1396 Template:
1397
1397
1398 The following keywords are supported in addition to the common template
1398 The following keywords are supported in addition to the common template
1399 keywords and functions such as ``{branch}``. See also
1399 keywords and functions such as ``{branch}``. See also
1400 :hg:`help templates`.
1400 :hg:`help templates`.
1401
1401
1402 :active: Boolean. True if the branch is active.
1402 :active: Boolean. True if the branch is active.
1403 :closed: Boolean. True if the branch is closed.
1403 :closed: Boolean. True if the branch is closed.
1404 :current: Boolean. True if it is the current branch.
1404 :current: Boolean. True if it is the current branch.
1405
1405
1406 Returns 0.
1406 Returns 0.
1407 """
1407 """
1408
1408
1409 opts = pycompat.byteskwargs(opts)
1409 opts = pycompat.byteskwargs(opts)
1410 revs = opts.get(b'rev')
1410 revs = opts.get(b'rev')
1411 selectedbranches = None
1411 selectedbranches = None
1412 if revs:
1412 if revs:
1413 revs = scmutil.revrange(repo, revs)
1413 revs = scmutil.revrange(repo, revs)
1414 getbi = repo.revbranchcache().branchinfo
1414 getbi = repo.revbranchcache().branchinfo
1415 selectedbranches = {getbi(r)[0] for r in revs}
1415 selectedbranches = {getbi(r)[0] for r in revs}
1416
1416
1417 ui.pager(b'branches')
1417 ui.pager(b'branches')
1418 fm = ui.formatter(b'branches', opts)
1418 fm = ui.formatter(b'branches', opts)
1419 hexfunc = fm.hexfunc
1419 hexfunc = fm.hexfunc
1420
1420
1421 allheads = set(repo.heads())
1421 allheads = set(repo.heads())
1422 branches = []
1422 branches = []
1423 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1423 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1424 if selectedbranches is not None and tag not in selectedbranches:
1424 if selectedbranches is not None and tag not in selectedbranches:
1425 continue
1425 continue
1426 isactive = False
1426 isactive = False
1427 if not isclosed:
1427 if not isclosed:
1428 openheads = set(repo.branchmap().iteropen(heads))
1428 openheads = set(repo.branchmap().iteropen(heads))
1429 isactive = bool(openheads & allheads)
1429 isactive = bool(openheads & allheads)
1430 branches.append((tag, repo[tip], isactive, not isclosed))
1430 branches.append((tag, repo[tip], isactive, not isclosed))
1431 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1431 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1432
1432
1433 for tag, ctx, isactive, isopen in branches:
1433 for tag, ctx, isactive, isopen in branches:
1434 if active and not isactive:
1434 if active and not isactive:
1435 continue
1435 continue
1436 if isactive:
1436 if isactive:
1437 label = b'branches.active'
1437 label = b'branches.active'
1438 notice = b''
1438 notice = b''
1439 elif not isopen:
1439 elif not isopen:
1440 if not closed:
1440 if not closed:
1441 continue
1441 continue
1442 label = b'branches.closed'
1442 label = b'branches.closed'
1443 notice = _(b' (closed)')
1443 notice = _(b' (closed)')
1444 else:
1444 else:
1445 label = b'branches.inactive'
1445 label = b'branches.inactive'
1446 notice = _(b' (inactive)')
1446 notice = _(b' (inactive)')
1447 current = tag == repo.dirstate.branch()
1447 current = tag == repo.dirstate.branch()
1448 if current:
1448 if current:
1449 label = b'branches.current'
1449 label = b'branches.current'
1450
1450
1451 fm.startitem()
1451 fm.startitem()
1452 fm.write(b'branch', b'%s', tag, label=label)
1452 fm.write(b'branch', b'%s', tag, label=label)
1453 rev = ctx.rev()
1453 rev = ctx.rev()
1454 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1454 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1455 fmt = b' ' * padsize + b' %d:%s'
1455 fmt = b' ' * padsize + b' %d:%s'
1456 fm.condwrite(
1456 fm.condwrite(
1457 not ui.quiet,
1457 not ui.quiet,
1458 b'rev node',
1458 b'rev node',
1459 fmt,
1459 fmt,
1460 rev,
1460 rev,
1461 hexfunc(ctx.node()),
1461 hexfunc(ctx.node()),
1462 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1462 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1463 )
1463 )
1464 fm.context(ctx=ctx)
1464 fm.context(ctx=ctx)
1465 fm.data(active=isactive, closed=not isopen, current=current)
1465 fm.data(active=isactive, closed=not isopen, current=current)
1466 if not ui.quiet:
1466 if not ui.quiet:
1467 fm.plain(notice)
1467 fm.plain(notice)
1468 fm.plain(b'\n')
1468 fm.plain(b'\n')
1469 fm.end()
1469 fm.end()
1470
1470
1471
1471
1472 @command(
1472 @command(
1473 b'bundle',
1473 b'bundle',
1474 [
1474 [
1475 (
1475 (
1476 b'f',
1476 b'f',
1477 b'force',
1477 b'force',
1478 None,
1478 None,
1479 _(b'run even when the destination is unrelated'),
1479 _(b'run even when the destination is unrelated'),
1480 ),
1480 ),
1481 (
1481 (
1482 b'r',
1482 b'r',
1483 b'rev',
1483 b'rev',
1484 [],
1484 [],
1485 _(b'a changeset intended to be added to the destination'),
1485 _(b'a changeset intended to be added to the destination'),
1486 _(b'REV'),
1486 _(b'REV'),
1487 ),
1487 ),
1488 (
1488 (
1489 b'b',
1489 b'b',
1490 b'branch',
1490 b'branch',
1491 [],
1491 [],
1492 _(b'a specific branch you would like to bundle'),
1492 _(b'a specific branch you would like to bundle'),
1493 _(b'BRANCH'),
1493 _(b'BRANCH'),
1494 ),
1494 ),
1495 (
1495 (
1496 b'',
1496 b'',
1497 b'base',
1497 b'base',
1498 [],
1498 [],
1499 _(b'a base changeset assumed to be available at the destination'),
1499 _(b'a base changeset assumed to be available at the destination'),
1500 _(b'REV'),
1500 _(b'REV'),
1501 ),
1501 ),
1502 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1502 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1503 (
1503 (
1504 b't',
1504 b't',
1505 b'type',
1505 b'type',
1506 b'bzip2',
1506 b'bzip2',
1507 _(b'bundle compression type to use'),
1507 _(b'bundle compression type to use'),
1508 _(b'TYPE'),
1508 _(b'TYPE'),
1509 ),
1509 ),
1510 ]
1510 ]
1511 + remoteopts,
1511 + remoteopts,
1512 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1512 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1513 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1513 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1514 )
1514 )
1515 def bundle(ui, repo, fname, dest=None, **opts):
1515 def bundle(ui, repo, fname, dest=None, **opts):
1516 """create a bundle file
1516 """create a bundle file
1517
1517
1518 Generate a bundle file containing data to be transferred to another
1518 Generate a bundle file containing data to be transferred to another
1519 repository.
1519 repository.
1520
1520
1521 To create a bundle containing all changesets, use -a/--all
1521 To create a bundle containing all changesets, use -a/--all
1522 (or --base null). Otherwise, hg assumes the destination will have
1522 (or --base null). Otherwise, hg assumes the destination will have
1523 all the nodes you specify with --base parameters. Otherwise, hg
1523 all the nodes you specify with --base parameters. Otherwise, hg
1524 will assume the repository has all the nodes in destination, or
1524 will assume the repository has all the nodes in destination, or
1525 default-push/default if no destination is specified, where destination
1525 default-push/default if no destination is specified, where destination
1526 is the repository you provide through DEST option.
1526 is the repository you provide through DEST option.
1527
1527
1528 You can change bundle format with the -t/--type option. See
1528 You can change bundle format with the -t/--type option. See
1529 :hg:`help bundlespec` for documentation on this format. By default,
1529 :hg:`help bundlespec` for documentation on this format. By default,
1530 the most appropriate format is used and compression defaults to
1530 the most appropriate format is used and compression defaults to
1531 bzip2.
1531 bzip2.
1532
1532
1533 The bundle file can then be transferred using conventional means
1533 The bundle file can then be transferred using conventional means
1534 and applied to another repository with the unbundle or pull
1534 and applied to another repository with the unbundle or pull
1535 command. This is useful when direct push and pull are not
1535 command. This is useful when direct push and pull are not
1536 available or when exporting an entire repository is undesirable.
1536 available or when exporting an entire repository is undesirable.
1537
1537
1538 Applying bundles preserves all changeset contents including
1538 Applying bundles preserves all changeset contents including
1539 permissions, copy/rename information, and revision history.
1539 permissions, copy/rename information, and revision history.
1540
1540
1541 Returns 0 on success, 1 if no changes found.
1541 Returns 0 on success, 1 if no changes found.
1542 """
1542 """
1543 opts = pycompat.byteskwargs(opts)
1543 opts = pycompat.byteskwargs(opts)
1544 revs = None
1544 revs = None
1545 if b'rev' in opts:
1545 if b'rev' in opts:
1546 revstrings = opts[b'rev']
1546 revstrings = opts[b'rev']
1547 revs = scmutil.revrange(repo, revstrings)
1547 revs = scmutil.revrange(repo, revstrings)
1548 if revstrings and not revs:
1548 if revstrings and not revs:
1549 raise error.Abort(_(b'no commits to bundle'))
1549 raise error.Abort(_(b'no commits to bundle'))
1550
1550
1551 bundletype = opts.get(b'type', b'bzip2').lower()
1551 bundletype = opts.get(b'type', b'bzip2').lower()
1552 try:
1552 try:
1553 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1553 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1554 except error.UnsupportedBundleSpecification as e:
1554 except error.UnsupportedBundleSpecification as e:
1555 raise error.Abort(
1555 raise error.Abort(
1556 pycompat.bytestr(e),
1556 pycompat.bytestr(e),
1557 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1557 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1558 )
1558 )
1559 cgversion = bundlespec.contentopts[b"cg.version"]
1559 cgversion = bundlespec.contentopts[b"cg.version"]
1560
1560
1561 # Packed bundles are a pseudo bundle format for now.
1561 # Packed bundles are a pseudo bundle format for now.
1562 if cgversion == b's1':
1562 if cgversion == b's1':
1563 raise error.Abort(
1563 raise error.Abort(
1564 _(b'packed bundles cannot be produced by "hg bundle"'),
1564 _(b'packed bundles cannot be produced by "hg bundle"'),
1565 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1565 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1566 )
1566 )
1567
1567
1568 if opts.get(b'all'):
1568 if opts.get(b'all'):
1569 if dest:
1569 if dest:
1570 raise error.Abort(
1570 raise error.Abort(
1571 _(b"--all is incompatible with specifying a destination")
1571 _(b"--all is incompatible with specifying a destination")
1572 )
1572 )
1573 if opts.get(b'base'):
1573 if opts.get(b'base'):
1574 ui.warn(_(b"ignoring --base because --all was specified\n"))
1574 ui.warn(_(b"ignoring --base because --all was specified\n"))
1575 base = [nullrev]
1575 base = [nullrev]
1576 else:
1576 else:
1577 base = scmutil.revrange(repo, opts.get(b'base'))
1577 base = scmutil.revrange(repo, opts.get(b'base'))
1578 if cgversion not in changegroup.supportedoutgoingversions(repo):
1578 if cgversion not in changegroup.supportedoutgoingversions(repo):
1579 raise error.Abort(
1579 raise error.Abort(
1580 _(b"repository does not support bundle version %s") % cgversion
1580 _(b"repository does not support bundle version %s") % cgversion
1581 )
1581 )
1582
1582
1583 if base:
1583 if base:
1584 if dest:
1584 if dest:
1585 raise error.Abort(
1585 raise error.Abort(
1586 _(b"--base is incompatible with specifying a destination")
1586 _(b"--base is incompatible with specifying a destination")
1587 )
1587 )
1588 common = [repo[rev].node() for rev in base]
1588 common = [repo[rev].node() for rev in base]
1589 heads = [repo[r].node() for r in revs] if revs else None
1589 heads = [repo[r].node() for r in revs] if revs else None
1590 outgoing = discovery.outgoing(repo, common, heads)
1590 outgoing = discovery.outgoing(repo, common, heads)
1591 else:
1591 else:
1592 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1592 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1593 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1593 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1594 other = hg.peer(repo, opts, dest)
1594 other = hg.peer(repo, opts, dest)
1595 revs = [repo[r].hex() for r in revs]
1595 revs = [repo[r].hex() for r in revs]
1596 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1596 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1597 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1597 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1598 outgoing = discovery.findcommonoutgoing(
1598 outgoing = discovery.findcommonoutgoing(
1599 repo,
1599 repo,
1600 other,
1600 other,
1601 onlyheads=heads,
1601 onlyheads=heads,
1602 force=opts.get(b'force'),
1602 force=opts.get(b'force'),
1603 portable=True,
1603 portable=True,
1604 )
1604 )
1605
1605
1606 if not outgoing.missing:
1606 if not outgoing.missing:
1607 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1607 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1608 return 1
1608 return 1
1609
1609
1610 if cgversion == b'01': # bundle1
1610 if cgversion == b'01': # bundle1
1611 bversion = b'HG10' + bundlespec.wirecompression
1611 bversion = b'HG10' + bundlespec.wirecompression
1612 bcompression = None
1612 bcompression = None
1613 elif cgversion in (b'02', b'03'):
1613 elif cgversion in (b'02', b'03'):
1614 bversion = b'HG20'
1614 bversion = b'HG20'
1615 bcompression = bundlespec.wirecompression
1615 bcompression = bundlespec.wirecompression
1616 else:
1616 else:
1617 raise error.ProgrammingError(
1617 raise error.ProgrammingError(
1618 b'bundle: unexpected changegroup version %s' % cgversion
1618 b'bundle: unexpected changegroup version %s' % cgversion
1619 )
1619 )
1620
1620
1621 # TODO compression options should be derived from bundlespec parsing.
1621 # TODO compression options should be derived from bundlespec parsing.
1622 # This is a temporary hack to allow adjusting bundle compression
1622 # This is a temporary hack to allow adjusting bundle compression
1623 # level without a) formalizing the bundlespec changes to declare it
1623 # level without a) formalizing the bundlespec changes to declare it
1624 # b) introducing a command flag.
1624 # b) introducing a command flag.
1625 compopts = {}
1625 compopts = {}
1626 complevel = ui.configint(
1626 complevel = ui.configint(
1627 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1627 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1628 )
1628 )
1629 if complevel is None:
1629 if complevel is None:
1630 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1630 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1631 if complevel is not None:
1631 if complevel is not None:
1632 compopts[b'level'] = complevel
1632 compopts[b'level'] = complevel
1633
1633
1634 # Allow overriding the bundling of obsmarker in phases through
1634 # Allow overriding the bundling of obsmarker in phases through
1635 # configuration while we don't have a bundle version that include them
1635 # configuration while we don't have a bundle version that include them
1636 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1636 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1637 bundlespec.contentopts[b'obsolescence'] = True
1637 bundlespec.contentopts[b'obsolescence'] = True
1638 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1638 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1639 bundlespec.contentopts[b'phases'] = True
1639 bundlespec.contentopts[b'phases'] = True
1640
1640
1641 bundle2.writenewbundle(
1641 bundle2.writenewbundle(
1642 ui,
1642 ui,
1643 repo,
1643 repo,
1644 b'bundle',
1644 b'bundle',
1645 fname,
1645 fname,
1646 bversion,
1646 bversion,
1647 outgoing,
1647 outgoing,
1648 bundlespec.contentopts,
1648 bundlespec.contentopts,
1649 compression=bcompression,
1649 compression=bcompression,
1650 compopts=compopts,
1650 compopts=compopts,
1651 )
1651 )
1652
1652
1653
1653
1654 @command(
1654 @command(
1655 b'cat',
1655 b'cat',
1656 [
1656 [
1657 (
1657 (
1658 b'o',
1658 b'o',
1659 b'output',
1659 b'output',
1660 b'',
1660 b'',
1661 _(b'print output to file with formatted name'),
1661 _(b'print output to file with formatted name'),
1662 _(b'FORMAT'),
1662 _(b'FORMAT'),
1663 ),
1663 ),
1664 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1664 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1665 (b'', b'decode', None, _(b'apply any matching decode filter')),
1665 (b'', b'decode', None, _(b'apply any matching decode filter')),
1666 ]
1666 ]
1667 + walkopts
1667 + walkopts
1668 + formatteropts,
1668 + formatteropts,
1669 _(b'[OPTION]... FILE...'),
1669 _(b'[OPTION]... FILE...'),
1670 helpcategory=command.CATEGORY_FILE_CONTENTS,
1670 helpcategory=command.CATEGORY_FILE_CONTENTS,
1671 inferrepo=True,
1671 inferrepo=True,
1672 intents={INTENT_READONLY},
1672 intents={INTENT_READONLY},
1673 )
1673 )
1674 def cat(ui, repo, file1, *pats, **opts):
1674 def cat(ui, repo, file1, *pats, **opts):
1675 """output the current or given revision of files
1675 """output the current or given revision of files
1676
1676
1677 Print the specified files as they were at the given revision. If
1677 Print the specified files as they were at the given revision. If
1678 no revision is given, the parent of the working directory is used.
1678 no revision is given, the parent of the working directory is used.
1679
1679
1680 Output may be to a file, in which case the name of the file is
1680 Output may be to a file, in which case the name of the file is
1681 given using a template string. See :hg:`help templates`. In addition
1681 given using a template string. See :hg:`help templates`. In addition
1682 to the common template keywords, the following formatting rules are
1682 to the common template keywords, the following formatting rules are
1683 supported:
1683 supported:
1684
1684
1685 :``%%``: literal "%" character
1685 :``%%``: literal "%" character
1686 :``%s``: basename of file being printed
1686 :``%s``: basename of file being printed
1687 :``%d``: dirname of file being printed, or '.' if in repository root
1687 :``%d``: dirname of file being printed, or '.' if in repository root
1688 :``%p``: root-relative path name of file being printed
1688 :``%p``: root-relative path name of file being printed
1689 :``%H``: changeset hash (40 hexadecimal digits)
1689 :``%H``: changeset hash (40 hexadecimal digits)
1690 :``%R``: changeset revision number
1690 :``%R``: changeset revision number
1691 :``%h``: short-form changeset hash (12 hexadecimal digits)
1691 :``%h``: short-form changeset hash (12 hexadecimal digits)
1692 :``%r``: zero-padded changeset revision number
1692 :``%r``: zero-padded changeset revision number
1693 :``%b``: basename of the exporting repository
1693 :``%b``: basename of the exporting repository
1694 :``\\``: literal "\\" character
1694 :``\\``: literal "\\" character
1695
1695
1696 .. container:: verbose
1696 .. container:: verbose
1697
1697
1698 Template:
1698 Template:
1699
1699
1700 The following keywords are supported in addition to the common template
1700 The following keywords are supported in addition to the common template
1701 keywords and functions. See also :hg:`help templates`.
1701 keywords and functions. See also :hg:`help templates`.
1702
1702
1703 :data: String. File content.
1703 :data: String. File content.
1704 :path: String. Repository-absolute path of the file.
1704 :path: String. Repository-absolute path of the file.
1705
1705
1706 Returns 0 on success.
1706 Returns 0 on success.
1707 """
1707 """
1708 opts = pycompat.byteskwargs(opts)
1708 opts = pycompat.byteskwargs(opts)
1709 rev = opts.get(b'rev')
1709 rev = opts.get(b'rev')
1710 if rev:
1710 if rev:
1711 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1711 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1712 ctx = scmutil.revsingle(repo, rev)
1712 ctx = scmutil.revsingle(repo, rev)
1713 m = scmutil.match(ctx, (file1,) + pats, opts)
1713 m = scmutil.match(ctx, (file1,) + pats, opts)
1714 fntemplate = opts.pop(b'output', b'')
1714 fntemplate = opts.pop(b'output', b'')
1715 if cmdutil.isstdiofilename(fntemplate):
1715 if cmdutil.isstdiofilename(fntemplate):
1716 fntemplate = b''
1716 fntemplate = b''
1717
1717
1718 if fntemplate:
1718 if fntemplate:
1719 fm = formatter.nullformatter(ui, b'cat', opts)
1719 fm = formatter.nullformatter(ui, b'cat', opts)
1720 else:
1720 else:
1721 ui.pager(b'cat')
1721 ui.pager(b'cat')
1722 fm = ui.formatter(b'cat', opts)
1722 fm = ui.formatter(b'cat', opts)
1723 with fm:
1723 with fm:
1724 return cmdutil.cat(
1724 return cmdutil.cat(
1725 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1725 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1726 )
1726 )
1727
1727
1728
1728
1729 @command(
1729 @command(
1730 b'clone',
1730 b'clone',
1731 [
1731 [
1732 (
1732 (
1733 b'U',
1733 b'U',
1734 b'noupdate',
1734 b'noupdate',
1735 None,
1735 None,
1736 _(
1736 _(
1737 b'the clone will include an empty working '
1737 b'the clone will include an empty working '
1738 b'directory (only a repository)'
1738 b'directory (only a repository)'
1739 ),
1739 ),
1740 ),
1740 ),
1741 (
1741 (
1742 b'u',
1742 b'u',
1743 b'updaterev',
1743 b'updaterev',
1744 b'',
1744 b'',
1745 _(b'revision, tag, or branch to check out'),
1745 _(b'revision, tag, or branch to check out'),
1746 _(b'REV'),
1746 _(b'REV'),
1747 ),
1747 ),
1748 (
1748 (
1749 b'r',
1749 b'r',
1750 b'rev',
1750 b'rev',
1751 [],
1751 [],
1752 _(
1752 _(
1753 b'do not clone everything, but include this changeset'
1753 b'do not clone everything, but include this changeset'
1754 b' and its ancestors'
1754 b' and its ancestors'
1755 ),
1755 ),
1756 _(b'REV'),
1756 _(b'REV'),
1757 ),
1757 ),
1758 (
1758 (
1759 b'b',
1759 b'b',
1760 b'branch',
1760 b'branch',
1761 [],
1761 [],
1762 _(
1762 _(
1763 b'do not clone everything, but include this branch\'s'
1763 b'do not clone everything, but include this branch\'s'
1764 b' changesets and their ancestors'
1764 b' changesets and their ancestors'
1765 ),
1765 ),
1766 _(b'BRANCH'),
1766 _(b'BRANCH'),
1767 ),
1767 ),
1768 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1768 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1769 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1769 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1770 (b'', b'stream', None, _(b'clone with minimal data processing')),
1770 (b'', b'stream', None, _(b'clone with minimal data processing')),
1771 ]
1771 ]
1772 + remoteopts,
1772 + remoteopts,
1773 _(b'[OPTION]... SOURCE [DEST]'),
1773 _(b'[OPTION]... SOURCE [DEST]'),
1774 helpcategory=command.CATEGORY_REPO_CREATION,
1774 helpcategory=command.CATEGORY_REPO_CREATION,
1775 helpbasic=True,
1775 helpbasic=True,
1776 norepo=True,
1776 norepo=True,
1777 )
1777 )
1778 def clone(ui, source, dest=None, **opts):
1778 def clone(ui, source, dest=None, **opts):
1779 """make a copy of an existing repository
1779 """make a copy of an existing repository
1780
1780
1781 Create a copy of an existing repository in a new directory.
1781 Create a copy of an existing repository in a new directory.
1782
1782
1783 If no destination directory name is specified, it defaults to the
1783 If no destination directory name is specified, it defaults to the
1784 basename of the source.
1784 basename of the source.
1785
1785
1786 The location of the source is added to the new repository's
1786 The location of the source is added to the new repository's
1787 ``.hg/hgrc`` file, as the default to be used for future pulls.
1787 ``.hg/hgrc`` file, as the default to be used for future pulls.
1788
1788
1789 Only local paths and ``ssh://`` URLs are supported as
1789 Only local paths and ``ssh://`` URLs are supported as
1790 destinations. For ``ssh://`` destinations, no working directory or
1790 destinations. For ``ssh://`` destinations, no working directory or
1791 ``.hg/hgrc`` will be created on the remote side.
1791 ``.hg/hgrc`` will be created on the remote side.
1792
1792
1793 If the source repository has a bookmark called '@' set, that
1793 If the source repository has a bookmark called '@' set, that
1794 revision will be checked out in the new repository by default.
1794 revision will be checked out in the new repository by default.
1795
1795
1796 To check out a particular version, use -u/--update, or
1796 To check out a particular version, use -u/--update, or
1797 -U/--noupdate to create a clone with no working directory.
1797 -U/--noupdate to create a clone with no working directory.
1798
1798
1799 To pull only a subset of changesets, specify one or more revisions
1799 To pull only a subset of changesets, specify one or more revisions
1800 identifiers with -r/--rev or branches with -b/--branch. The
1800 identifiers with -r/--rev or branches with -b/--branch. The
1801 resulting clone will contain only the specified changesets and
1801 resulting clone will contain only the specified changesets and
1802 their ancestors. These options (or 'clone src#rev dest') imply
1802 their ancestors. These options (or 'clone src#rev dest') imply
1803 --pull, even for local source repositories.
1803 --pull, even for local source repositories.
1804
1804
1805 In normal clone mode, the remote normalizes repository data into a common
1805 In normal clone mode, the remote normalizes repository data into a common
1806 exchange format and the receiving end translates this data into its local
1806 exchange format and the receiving end translates this data into its local
1807 storage format. --stream activates a different clone mode that essentially
1807 storage format. --stream activates a different clone mode that essentially
1808 copies repository files from the remote with minimal data processing. This
1808 copies repository files from the remote with minimal data processing. This
1809 significantly reduces the CPU cost of a clone both remotely and locally.
1809 significantly reduces the CPU cost of a clone both remotely and locally.
1810 However, it often increases the transferred data size by 30-40%. This can
1810 However, it often increases the transferred data size by 30-40%. This can
1811 result in substantially faster clones where I/O throughput is plentiful,
1811 result in substantially faster clones where I/O throughput is plentiful,
1812 especially for larger repositories. A side-effect of --stream clones is
1812 especially for larger repositories. A side-effect of --stream clones is
1813 that storage settings and requirements on the remote are applied locally:
1813 that storage settings and requirements on the remote are applied locally:
1814 a modern client may inherit legacy or inefficient storage used by the
1814 a modern client may inherit legacy or inefficient storage used by the
1815 remote or a legacy Mercurial client may not be able to clone from a
1815 remote or a legacy Mercurial client may not be able to clone from a
1816 modern Mercurial remote.
1816 modern Mercurial remote.
1817
1817
1818 .. note::
1818 .. note::
1819
1819
1820 Specifying a tag will include the tagged changeset but not the
1820 Specifying a tag will include the tagged changeset but not the
1821 changeset containing the tag.
1821 changeset containing the tag.
1822
1822
1823 .. container:: verbose
1823 .. container:: verbose
1824
1824
1825 For efficiency, hardlinks are used for cloning whenever the
1825 For efficiency, hardlinks are used for cloning whenever the
1826 source and destination are on the same filesystem (note this
1826 source and destination are on the same filesystem (note this
1827 applies only to the repository data, not to the working
1827 applies only to the repository data, not to the working
1828 directory). Some filesystems, such as AFS, implement hardlinking
1828 directory). Some filesystems, such as AFS, implement hardlinking
1829 incorrectly, but do not report errors. In these cases, use the
1829 incorrectly, but do not report errors. In these cases, use the
1830 --pull option to avoid hardlinking.
1830 --pull option to avoid hardlinking.
1831
1831
1832 Mercurial will update the working directory to the first applicable
1832 Mercurial will update the working directory to the first applicable
1833 revision from this list:
1833 revision from this list:
1834
1834
1835 a) null if -U or the source repository has no changesets
1835 a) null if -U or the source repository has no changesets
1836 b) if -u . and the source repository is local, the first parent of
1836 b) if -u . and the source repository is local, the first parent of
1837 the source repository's working directory
1837 the source repository's working directory
1838 c) the changeset specified with -u (if a branch name, this means the
1838 c) the changeset specified with -u (if a branch name, this means the
1839 latest head of that branch)
1839 latest head of that branch)
1840 d) the changeset specified with -r
1840 d) the changeset specified with -r
1841 e) the tipmost head specified with -b
1841 e) the tipmost head specified with -b
1842 f) the tipmost head specified with the url#branch source syntax
1842 f) the tipmost head specified with the url#branch source syntax
1843 g) the revision marked with the '@' bookmark, if present
1843 g) the revision marked with the '@' bookmark, if present
1844 h) the tipmost head of the default branch
1844 h) the tipmost head of the default branch
1845 i) tip
1845 i) tip
1846
1846
1847 When cloning from servers that support it, Mercurial may fetch
1847 When cloning from servers that support it, Mercurial may fetch
1848 pre-generated data from a server-advertised URL or inline from the
1848 pre-generated data from a server-advertised URL or inline from the
1849 same stream. When this is done, hooks operating on incoming changesets
1849 same stream. When this is done, hooks operating on incoming changesets
1850 and changegroups may fire more than once, once for each pre-generated
1850 and changegroups may fire more than once, once for each pre-generated
1851 bundle and as well as for any additional remaining data. In addition,
1851 bundle and as well as for any additional remaining data. In addition,
1852 if an error occurs, the repository may be rolled back to a partial
1852 if an error occurs, the repository may be rolled back to a partial
1853 clone. This behavior may change in future releases.
1853 clone. This behavior may change in future releases.
1854 See :hg:`help -e clonebundles` for more.
1854 See :hg:`help -e clonebundles` for more.
1855
1855
1856 Examples:
1856 Examples:
1857
1857
1858 - clone a remote repository to a new directory named hg/::
1858 - clone a remote repository to a new directory named hg/::
1859
1859
1860 hg clone https://www.mercurial-scm.org/repo/hg/
1860 hg clone https://www.mercurial-scm.org/repo/hg/
1861
1861
1862 - create a lightweight local clone::
1862 - create a lightweight local clone::
1863
1863
1864 hg clone project/ project-feature/
1864 hg clone project/ project-feature/
1865
1865
1866 - clone from an absolute path on an ssh server (note double-slash)::
1866 - clone from an absolute path on an ssh server (note double-slash)::
1867
1867
1868 hg clone ssh://user@server//home/projects/alpha/
1868 hg clone ssh://user@server//home/projects/alpha/
1869
1869
1870 - do a streaming clone while checking out a specified version::
1870 - do a streaming clone while checking out a specified version::
1871
1871
1872 hg clone --stream http://server/repo -u 1.5
1872 hg clone --stream http://server/repo -u 1.5
1873
1873
1874 - create a repository without changesets after a particular revision::
1874 - create a repository without changesets after a particular revision::
1875
1875
1876 hg clone -r 04e544 experimental/ good/
1876 hg clone -r 04e544 experimental/ good/
1877
1877
1878 - clone (and track) a particular named branch::
1878 - clone (and track) a particular named branch::
1879
1879
1880 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1880 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1881
1881
1882 See :hg:`help urls` for details on specifying URLs.
1882 See :hg:`help urls` for details on specifying URLs.
1883
1883
1884 Returns 0 on success.
1884 Returns 0 on success.
1885 """
1885 """
1886 opts = pycompat.byteskwargs(opts)
1886 opts = pycompat.byteskwargs(opts)
1887 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1887 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1888
1888
1889 # --include/--exclude can come from narrow or sparse.
1889 # --include/--exclude can come from narrow or sparse.
1890 includepats, excludepats = None, None
1890 includepats, excludepats = None, None
1891
1891
1892 # hg.clone() differentiates between None and an empty set. So make sure
1892 # hg.clone() differentiates between None and an empty set. So make sure
1893 # patterns are sets if narrow is requested without patterns.
1893 # patterns are sets if narrow is requested without patterns.
1894 if opts.get(b'narrow'):
1894 if opts.get(b'narrow'):
1895 includepats = set()
1895 includepats = set()
1896 excludepats = set()
1896 excludepats = set()
1897
1897
1898 if opts.get(b'include'):
1898 if opts.get(b'include'):
1899 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1899 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1900 if opts.get(b'exclude'):
1900 if opts.get(b'exclude'):
1901 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1901 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1902
1902
1903 r = hg.clone(
1903 r = hg.clone(
1904 ui,
1904 ui,
1905 opts,
1905 opts,
1906 source,
1906 source,
1907 dest,
1907 dest,
1908 pull=opts.get(b'pull'),
1908 pull=opts.get(b'pull'),
1909 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1909 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1910 revs=opts.get(b'rev'),
1910 revs=opts.get(b'rev'),
1911 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1911 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1912 branch=opts.get(b'branch'),
1912 branch=opts.get(b'branch'),
1913 shareopts=opts.get(b'shareopts'),
1913 shareopts=opts.get(b'shareopts'),
1914 storeincludepats=includepats,
1914 storeincludepats=includepats,
1915 storeexcludepats=excludepats,
1915 storeexcludepats=excludepats,
1916 depth=opts.get(b'depth') or None,
1916 depth=opts.get(b'depth') or None,
1917 )
1917 )
1918
1918
1919 return r is None
1919 return r is None
1920
1920
1921
1921
1922 @command(
1922 @command(
1923 b'commit|ci',
1923 b'commit|ci',
1924 [
1924 [
1925 (
1925 (
1926 b'A',
1926 b'A',
1927 b'addremove',
1927 b'addremove',
1928 None,
1928 None,
1929 _(b'mark new/missing files as added/removed before committing'),
1929 _(b'mark new/missing files as added/removed before committing'),
1930 ),
1930 ),
1931 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1931 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1932 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1932 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1933 (b's', b'secret', None, _(b'use the secret phase for committing')),
1933 (b's', b'secret', None, _(b'use the secret phase for committing')),
1934 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1934 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1935 (
1935 (
1936 b'',
1936 b'',
1937 b'force-close-branch',
1937 b'force-close-branch',
1938 None,
1938 None,
1939 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1939 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1940 ),
1940 ),
1941 (b'i', b'interactive', None, _(b'use interactive mode')),
1941 (b'i', b'interactive', None, _(b'use interactive mode')),
1942 ]
1942 ]
1943 + walkopts
1943 + walkopts
1944 + commitopts
1944 + commitopts
1945 + commitopts2
1945 + commitopts2
1946 + subrepoopts,
1946 + subrepoopts,
1947 _(b'[OPTION]... [FILE]...'),
1947 _(b'[OPTION]... [FILE]...'),
1948 helpcategory=command.CATEGORY_COMMITTING,
1948 helpcategory=command.CATEGORY_COMMITTING,
1949 helpbasic=True,
1949 helpbasic=True,
1950 inferrepo=True,
1950 inferrepo=True,
1951 )
1951 )
1952 def commit(ui, repo, *pats, **opts):
1952 def commit(ui, repo, *pats, **opts):
1953 """commit the specified files or all outstanding changes
1953 """commit the specified files or all outstanding changes
1954
1954
1955 Commit changes to the given files into the repository. Unlike a
1955 Commit changes to the given files into the repository. Unlike a
1956 centralized SCM, this operation is a local operation. See
1956 centralized SCM, this operation is a local operation. See
1957 :hg:`push` for a way to actively distribute your changes.
1957 :hg:`push` for a way to actively distribute your changes.
1958
1958
1959 If a list of files is omitted, all changes reported by :hg:`status`
1959 If a list of files is omitted, all changes reported by :hg:`status`
1960 will be committed.
1960 will be committed.
1961
1961
1962 If you are committing the result of a merge, do not provide any
1962 If you are committing the result of a merge, do not provide any
1963 filenames or -I/-X filters.
1963 filenames or -I/-X filters.
1964
1964
1965 If no commit message is specified, Mercurial starts your
1965 If no commit message is specified, Mercurial starts your
1966 configured editor where you can enter a message. In case your
1966 configured editor where you can enter a message. In case your
1967 commit fails, you will find a backup of your message in
1967 commit fails, you will find a backup of your message in
1968 ``.hg/last-message.txt``.
1968 ``.hg/last-message.txt``.
1969
1969
1970 The --close-branch flag can be used to mark the current branch
1970 The --close-branch flag can be used to mark the current branch
1971 head closed. When all heads of a branch are closed, the branch
1971 head closed. When all heads of a branch are closed, the branch
1972 will be considered closed and no longer listed.
1972 will be considered closed and no longer listed.
1973
1973
1974 The --amend flag can be used to amend the parent of the
1974 The --amend flag can be used to amend the parent of the
1975 working directory with a new commit that contains the changes
1975 working directory with a new commit that contains the changes
1976 in the parent in addition to those currently reported by :hg:`status`,
1976 in the parent in addition to those currently reported by :hg:`status`,
1977 if there are any. The old commit is stored in a backup bundle in
1977 if there are any. The old commit is stored in a backup bundle in
1978 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1978 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1979 on how to restore it).
1979 on how to restore it).
1980
1980
1981 Message, user and date are taken from the amended commit unless
1981 Message, user and date are taken from the amended commit unless
1982 specified. When a message isn't specified on the command line,
1982 specified. When a message isn't specified on the command line,
1983 the editor will open with the message of the amended commit.
1983 the editor will open with the message of the amended commit.
1984
1984
1985 It is not possible to amend public changesets (see :hg:`help phases`)
1985 It is not possible to amend public changesets (see :hg:`help phases`)
1986 or changesets that have children.
1986 or changesets that have children.
1987
1987
1988 See :hg:`help dates` for a list of formats valid for -d/--date.
1988 See :hg:`help dates` for a list of formats valid for -d/--date.
1989
1989
1990 Returns 0 on success, 1 if nothing changed.
1990 Returns 0 on success, 1 if nothing changed.
1991
1991
1992 .. container:: verbose
1992 .. container:: verbose
1993
1993
1994 Examples:
1994 Examples:
1995
1995
1996 - commit all files ending in .py::
1996 - commit all files ending in .py::
1997
1997
1998 hg commit --include "set:**.py"
1998 hg commit --include "set:**.py"
1999
1999
2000 - commit all non-binary files::
2000 - commit all non-binary files::
2001
2001
2002 hg commit --exclude "set:binary()"
2002 hg commit --exclude "set:binary()"
2003
2003
2004 - amend the current commit and set the date to now::
2004 - amend the current commit and set the date to now::
2005
2005
2006 hg commit --amend --date now
2006 hg commit --amend --date now
2007 """
2007 """
2008 with repo.wlock(), repo.lock():
2008 with repo.wlock(), repo.lock():
2009 return _docommit(ui, repo, *pats, **opts)
2009 return _docommit(ui, repo, *pats, **opts)
2010
2010
2011
2011
2012 def _docommit(ui, repo, *pats, **opts):
2012 def _docommit(ui, repo, *pats, **opts):
2013 if opts.get('interactive'):
2013 if opts.get('interactive'):
2014 opts.pop('interactive')
2014 opts.pop('interactive')
2015 ret = cmdutil.dorecord(
2015 ret = cmdutil.dorecord(
2016 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2016 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2017 )
2017 )
2018 # ret can be 0 (no changes to record) or the value returned by
2018 # ret can be 0 (no changes to record) or the value returned by
2019 # commit(), 1 if nothing changed or None on success.
2019 # commit(), 1 if nothing changed or None on success.
2020 return 1 if ret == 0 else ret
2020 return 1 if ret == 0 else ret
2021
2021
2022 opts = pycompat.byteskwargs(opts)
2022 opts = pycompat.byteskwargs(opts)
2023 if opts.get(b'subrepos'):
2023 if opts.get(b'subrepos'):
2024 if opts.get(b'amend'):
2024 if opts.get(b'amend'):
2025 raise error.Abort(_(b'cannot amend with --subrepos'))
2025 raise error.Abort(_(b'cannot amend with --subrepos'))
2026 # Let --subrepos on the command line override config setting.
2026 # Let --subrepos on the command line override config setting.
2027 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2027 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2028
2028
2029 cmdutil.checkunfinished(repo, commit=True)
2029 cmdutil.checkunfinished(repo, commit=True)
2030
2030
2031 branch = repo[None].branch()
2031 branch = repo[None].branch()
2032 bheads = repo.branchheads(branch)
2032 bheads = repo.branchheads(branch)
2033
2033
2034 extra = {}
2034 extra = {}
2035 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2035 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2036 extra[b'close'] = b'1'
2036 extra[b'close'] = b'1'
2037
2037
2038 if repo[b'.'].closesbranch():
2038 if repo[b'.'].closesbranch():
2039 raise error.Abort(
2039 raise error.Abort(
2040 _(b'current revision is already a branch closing head')
2040 _(b'current revision is already a branch closing head')
2041 )
2041 )
2042 elif not bheads:
2042 elif not bheads:
2043 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2043 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2044 elif (
2044 elif (
2045 branch == repo[b'.'].branch()
2045 branch == repo[b'.'].branch()
2046 and repo[b'.'].node() not in bheads
2046 and repo[b'.'].node() not in bheads
2047 and not opts.get(b'force_close_branch')
2047 and not opts.get(b'force_close_branch')
2048 ):
2048 ):
2049 hint = _(
2049 hint = _(
2050 b'use --force-close-branch to close branch from a non-head'
2050 b'use --force-close-branch to close branch from a non-head'
2051 b' changeset'
2051 b' changeset'
2052 )
2052 )
2053 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2053 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2054 elif opts.get(b'amend'):
2054 elif opts.get(b'amend'):
2055 if (
2055 if (
2056 repo[b'.'].p1().branch() != branch
2056 repo[b'.'].p1().branch() != branch
2057 and repo[b'.'].p2().branch() != branch
2057 and repo[b'.'].p2().branch() != branch
2058 ):
2058 ):
2059 raise error.Abort(_(b'can only close branch heads'))
2059 raise error.Abort(_(b'can only close branch heads'))
2060
2060
2061 if opts.get(b'amend'):
2061 if opts.get(b'amend'):
2062 if ui.configbool(b'ui', b'commitsubrepos'):
2062 if ui.configbool(b'ui', b'commitsubrepos'):
2063 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2063 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2064
2064
2065 old = repo[b'.']
2065 old = repo[b'.']
2066 rewriteutil.precheck(repo, [old.rev()], b'amend')
2066 rewriteutil.precheck(repo, [old.rev()], b'amend')
2067
2067
2068 # Currently histedit gets confused if an amend happens while histedit
2068 # Currently histedit gets confused if an amend happens while histedit
2069 # is in progress. Since we have a checkunfinished command, we are
2069 # is in progress. Since we have a checkunfinished command, we are
2070 # temporarily honoring it.
2070 # temporarily honoring it.
2071 #
2071 #
2072 # Note: eventually this guard will be removed. Please do not expect
2072 # Note: eventually this guard will be removed. Please do not expect
2073 # this behavior to remain.
2073 # this behavior to remain.
2074 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2074 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2075 cmdutil.checkunfinished(repo)
2075 cmdutil.checkunfinished(repo)
2076
2076
2077 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2077 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2078 if node == old.node():
2078 if node == old.node():
2079 ui.status(_(b"nothing changed\n"))
2079 ui.status(_(b"nothing changed\n"))
2080 return 1
2080 return 1
2081 else:
2081 else:
2082
2082
2083 def commitfunc(ui, repo, message, match, opts):
2083 def commitfunc(ui, repo, message, match, opts):
2084 overrides = {}
2084 overrides = {}
2085 if opts.get(b'secret'):
2085 if opts.get(b'secret'):
2086 overrides[(b'phases', b'new-commit')] = b'secret'
2086 overrides[(b'phases', b'new-commit')] = b'secret'
2087
2087
2088 baseui = repo.baseui
2088 baseui = repo.baseui
2089 with baseui.configoverride(overrides, b'commit'):
2089 with baseui.configoverride(overrides, b'commit'):
2090 with ui.configoverride(overrides, b'commit'):
2090 with ui.configoverride(overrides, b'commit'):
2091 editform = cmdutil.mergeeditform(
2091 editform = cmdutil.mergeeditform(
2092 repo[None], b'commit.normal'
2092 repo[None], b'commit.normal'
2093 )
2093 )
2094 editor = cmdutil.getcommiteditor(
2094 editor = cmdutil.getcommiteditor(
2095 editform=editform, **pycompat.strkwargs(opts)
2095 editform=editform, **pycompat.strkwargs(opts)
2096 )
2096 )
2097 return repo.commit(
2097 return repo.commit(
2098 message,
2098 message,
2099 opts.get(b'user'),
2099 opts.get(b'user'),
2100 opts.get(b'date'),
2100 opts.get(b'date'),
2101 match,
2101 match,
2102 editor=editor,
2102 editor=editor,
2103 extra=extra,
2103 extra=extra,
2104 )
2104 )
2105
2105
2106 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2106 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2107
2107
2108 if not node:
2108 if not node:
2109 stat = cmdutil.postcommitstatus(repo, pats, opts)
2109 stat = cmdutil.postcommitstatus(repo, pats, opts)
2110 if stat.deleted:
2110 if stat.deleted:
2111 ui.status(
2111 ui.status(
2112 _(
2112 _(
2113 b"nothing changed (%d missing files, see "
2113 b"nothing changed (%d missing files, see "
2114 b"'hg status')\n"
2114 b"'hg status')\n"
2115 )
2115 )
2116 % len(stat.deleted)
2116 % len(stat.deleted)
2117 )
2117 )
2118 else:
2118 else:
2119 ui.status(_(b"nothing changed\n"))
2119 ui.status(_(b"nothing changed\n"))
2120 return 1
2120 return 1
2121
2121
2122 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2122 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2123
2123
2124 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2124 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2125 status(
2125 status(
2126 ui,
2126 ui,
2127 repo,
2127 repo,
2128 modified=True,
2128 modified=True,
2129 added=True,
2129 added=True,
2130 removed=True,
2130 removed=True,
2131 deleted=True,
2131 deleted=True,
2132 unknown=True,
2132 unknown=True,
2133 subrepos=opts.get(b'subrepos'),
2133 subrepos=opts.get(b'subrepos'),
2134 )
2134 )
2135
2135
2136
2136
2137 @command(
2137 @command(
2138 b'config|showconfig|debugconfig',
2138 b'config|showconfig|debugconfig',
2139 [
2139 [
2140 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2140 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2141 (b'e', b'edit', None, _(b'edit user config')),
2141 (b'e', b'edit', None, _(b'edit user config')),
2142 (b'l', b'local', None, _(b'edit repository config')),
2142 (b'l', b'local', None, _(b'edit repository config')),
2143 (b'g', b'global', None, _(b'edit global config')),
2143 (b'g', b'global', None, _(b'edit global config')),
2144 ]
2144 ]
2145 + formatteropts,
2145 + formatteropts,
2146 _(b'[-u] [NAME]...'),
2146 _(b'[-u] [NAME]...'),
2147 helpcategory=command.CATEGORY_HELP,
2147 helpcategory=command.CATEGORY_HELP,
2148 optionalrepo=True,
2148 optionalrepo=True,
2149 intents={INTENT_READONLY},
2149 intents={INTENT_READONLY},
2150 )
2150 )
2151 def config(ui, repo, *values, **opts):
2151 def config(ui, repo, *values, **opts):
2152 """show combined config settings from all hgrc files
2152 """show combined config settings from all hgrc files
2153
2153
2154 With no arguments, print names and values of all config items.
2154 With no arguments, print names and values of all config items.
2155
2155
2156 With one argument of the form section.name, print just the value
2156 With one argument of the form section.name, print just the value
2157 of that config item.
2157 of that config item.
2158
2158
2159 With multiple arguments, print names and values of all config
2159 With multiple arguments, print names and values of all config
2160 items with matching section names or section.names.
2160 items with matching section names or section.names.
2161
2161
2162 With --edit, start an editor on the user-level config file. With
2162 With --edit, start an editor on the user-level config file. With
2163 --global, edit the system-wide config file. With --local, edit the
2163 --global, edit the system-wide config file. With --local, edit the
2164 repository-level config file.
2164 repository-level config file.
2165
2165
2166 With --debug, the source (filename and line number) is printed
2166 With --debug, the source (filename and line number) is printed
2167 for each config item.
2167 for each config item.
2168
2168
2169 See :hg:`help config` for more information about config files.
2169 See :hg:`help config` for more information about config files.
2170
2170
2171 .. container:: verbose
2171 .. container:: verbose
2172
2172
2173 Template:
2173 Template:
2174
2174
2175 The following keywords are supported. See also :hg:`help templates`.
2175 The following keywords are supported. See also :hg:`help templates`.
2176
2176
2177 :name: String. Config name.
2177 :name: String. Config name.
2178 :source: String. Filename and line number where the item is defined.
2178 :source: String. Filename and line number where the item is defined.
2179 :value: String. Config value.
2179 :value: String. Config value.
2180
2180
2181 Returns 0 on success, 1 if NAME does not exist.
2181 Returns 0 on success, 1 if NAME does not exist.
2182
2182
2183 """
2183 """
2184
2184
2185 opts = pycompat.byteskwargs(opts)
2185 opts = pycompat.byteskwargs(opts)
2186 if opts.get(b'edit') or opts.get(b'local') or opts.get(b'global'):
2186 if opts.get(b'edit') or opts.get(b'local') or opts.get(b'global'):
2187 if opts.get(b'local') and opts.get(b'global'):
2187 if opts.get(b'local') and opts.get(b'global'):
2188 raise error.Abort(_(b"can't use --local and --global together"))
2188 raise error.Abort(_(b"can't use --local and --global together"))
2189
2189
2190 if opts.get(b'local'):
2190 if opts.get(b'local'):
2191 if not repo:
2191 if not repo:
2192 raise error.Abort(_(b"can't use --local outside a repository"))
2192 raise error.Abort(_(b"can't use --local outside a repository"))
2193 paths = [repo.vfs.join(b'hgrc')]
2193 paths = [repo.vfs.join(b'hgrc')]
2194 elif opts.get(b'global'):
2194 elif opts.get(b'global'):
2195 paths = rcutil.systemrcpath()
2195 paths = rcutil.systemrcpath()
2196 else:
2196 else:
2197 paths = rcutil.userrcpath()
2197 paths = rcutil.userrcpath()
2198
2198
2199 for f in paths:
2199 for f in paths:
2200 if os.path.exists(f):
2200 if os.path.exists(f):
2201 break
2201 break
2202 else:
2202 else:
2203 if opts.get(b'global'):
2203 if opts.get(b'global'):
2204 samplehgrc = uimod.samplehgrcs[b'global']
2204 samplehgrc = uimod.samplehgrcs[b'global']
2205 elif opts.get(b'local'):
2205 elif opts.get(b'local'):
2206 samplehgrc = uimod.samplehgrcs[b'local']
2206 samplehgrc = uimod.samplehgrcs[b'local']
2207 else:
2207 else:
2208 samplehgrc = uimod.samplehgrcs[b'user']
2208 samplehgrc = uimod.samplehgrcs[b'user']
2209
2209
2210 f = paths[0]
2210 f = paths[0]
2211 fp = open(f, b"wb")
2211 fp = open(f, b"wb")
2212 fp.write(util.tonativeeol(samplehgrc))
2212 fp.write(util.tonativeeol(samplehgrc))
2213 fp.close()
2213 fp.close()
2214
2214
2215 editor = ui.geteditor()
2215 editor = ui.geteditor()
2216 ui.system(
2216 ui.system(
2217 b"%s \"%s\"" % (editor, f),
2217 b"%s \"%s\"" % (editor, f),
2218 onerr=error.Abort,
2218 onerr=error.Abort,
2219 errprefix=_(b"edit failed"),
2219 errprefix=_(b"edit failed"),
2220 blockedtag=b'config_edit',
2220 blockedtag=b'config_edit',
2221 )
2221 )
2222 return
2222 return
2223 ui.pager(b'config')
2223 ui.pager(b'config')
2224 fm = ui.formatter(b'config', opts)
2224 fm = ui.formatter(b'config', opts)
2225 for t, f in rcutil.rccomponents():
2225 for t, f in rcutil.rccomponents():
2226 if t == b'path':
2226 if t == b'path':
2227 ui.debug(b'read config from: %s\n' % f)
2227 ui.debug(b'read config from: %s\n' % f)
2228 elif t == b'resource':
2228 elif t == b'resource':
2229 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2229 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2230 elif t == b'items':
2230 elif t == b'items':
2231 # Don't print anything for 'items'.
2231 # Don't print anything for 'items'.
2232 pass
2232 pass
2233 else:
2233 else:
2234 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2234 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2235 untrusted = bool(opts.get(b'untrusted'))
2235 untrusted = bool(opts.get(b'untrusted'))
2236
2236
2237 selsections = selentries = []
2237 selsections = selentries = []
2238 if values:
2238 if values:
2239 selsections = [v for v in values if b'.' not in v]
2239 selsections = [v for v in values if b'.' not in v]
2240 selentries = [v for v in values if b'.' in v]
2240 selentries = [v for v in values if b'.' in v]
2241 uniquesel = len(selentries) == 1 and not selsections
2241 uniquesel = len(selentries) == 1 and not selsections
2242 selsections = set(selsections)
2242 selsections = set(selsections)
2243 selentries = set(selentries)
2243 selentries = set(selentries)
2244
2244
2245 matched = False
2245 matched = False
2246 for section, name, value in ui.walkconfig(untrusted=untrusted):
2246 for section, name, value in ui.walkconfig(untrusted=untrusted):
2247 source = ui.configsource(section, name, untrusted)
2247 source = ui.configsource(section, name, untrusted)
2248 value = pycompat.bytestr(value)
2248 value = pycompat.bytestr(value)
2249 defaultvalue = ui.configdefault(section, name)
2249 defaultvalue = ui.configdefault(section, name)
2250 if fm.isplain():
2250 if fm.isplain():
2251 source = source or b'none'
2251 source = source or b'none'
2252 value = value.replace(b'\n', b'\\n')
2252 value = value.replace(b'\n', b'\\n')
2253 entryname = section + b'.' + name
2253 entryname = section + b'.' + name
2254 if values and not (section in selsections or entryname in selentries):
2254 if values and not (section in selsections or entryname in selentries):
2255 continue
2255 continue
2256 fm.startitem()
2256 fm.startitem()
2257 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2257 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2258 if uniquesel:
2258 if uniquesel:
2259 fm.data(name=entryname)
2259 fm.data(name=entryname)
2260 fm.write(b'value', b'%s\n', value)
2260 fm.write(b'value', b'%s\n', value)
2261 else:
2261 else:
2262 fm.write(b'name value', b'%s=%s\n', entryname, value)
2262 fm.write(b'name value', b'%s=%s\n', entryname, value)
2263 if formatter.isprintable(defaultvalue):
2263 if formatter.isprintable(defaultvalue):
2264 fm.data(defaultvalue=defaultvalue)
2264 fm.data(defaultvalue=defaultvalue)
2265 elif isinstance(defaultvalue, list) and all(
2265 elif isinstance(defaultvalue, list) and all(
2266 formatter.isprintable(e) for e in defaultvalue
2266 formatter.isprintable(e) for e in defaultvalue
2267 ):
2267 ):
2268 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2268 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2269 # TODO: no idea how to process unsupported defaultvalue types
2269 # TODO: no idea how to process unsupported defaultvalue types
2270 matched = True
2270 matched = True
2271 fm.end()
2271 fm.end()
2272 if matched:
2272 if matched:
2273 return 0
2273 return 0
2274 return 1
2274 return 1
2275
2275
2276
2276
2277 @command(
2277 @command(
2278 b'continue',
2278 b'continue',
2279 dryrunopts,
2279 dryrunopts,
2280 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2280 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2281 helpbasic=True,
2281 helpbasic=True,
2282 )
2282 )
2283 def continuecmd(ui, repo, **opts):
2283 def continuecmd(ui, repo, **opts):
2284 """resumes an interrupted operation (EXPERIMENTAL)
2284 """resumes an interrupted operation (EXPERIMENTAL)
2285
2285
2286 Finishes a multistep operation like graft, histedit, rebase, merge,
2286 Finishes a multistep operation like graft, histedit, rebase, merge,
2287 and unshelve if they are in an interrupted state.
2287 and unshelve if they are in an interrupted state.
2288
2288
2289 use --dry-run/-n to dry run the command.
2289 use --dry-run/-n to dry run the command.
2290 """
2290 """
2291 dryrun = opts.get('dry_run')
2291 dryrun = opts.get('dry_run')
2292 contstate = cmdutil.getunfinishedstate(repo)
2292 contstate = cmdutil.getunfinishedstate(repo)
2293 if not contstate:
2293 if not contstate:
2294 raise error.Abort(_(b'no operation in progress'))
2294 raise error.Abort(_(b'no operation in progress'))
2295 if not contstate.continuefunc:
2295 if not contstate.continuefunc:
2296 raise error.Abort(
2296 raise error.Abort(
2297 (
2297 (
2298 _(b"%s in progress but does not support 'hg continue'")
2298 _(b"%s in progress but does not support 'hg continue'")
2299 % (contstate._opname)
2299 % (contstate._opname)
2300 ),
2300 ),
2301 hint=contstate.continuemsg(),
2301 hint=contstate.continuemsg(),
2302 )
2302 )
2303 if dryrun:
2303 if dryrun:
2304 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2304 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2305 return
2305 return
2306 return contstate.continuefunc(ui, repo)
2306 return contstate.continuefunc(ui, repo)
2307
2307
2308
2308
2309 @command(
2309 @command(
2310 b'copy|cp',
2310 b'copy|cp',
2311 [
2311 [
2312 (b'', b'forget', None, _(b'unmark a file as copied')),
2312 (b'', b'forget', None, _(b'unmark a file as copied')),
2313 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2313 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2314 (
2314 (
2315 b'',
2316 b'at-rev',
2317 b'',
2318 _(b'unmark copies in the given revision (EXPERIMENTAL)'),
2319 _(b'REV'),
2320 ),
2321 (
2315 b'f',
2322 b'f',
2316 b'force',
2323 b'force',
2317 None,
2324 None,
2318 _(b'forcibly copy over an existing managed file'),
2325 _(b'forcibly copy over an existing managed file'),
2319 ),
2326 ),
2320 ]
2327 ]
2321 + walkopts
2328 + walkopts
2322 + dryrunopts,
2329 + dryrunopts,
2323 _(b'[OPTION]... SOURCE... DEST'),
2330 _(b'[OPTION]... SOURCE... DEST'),
2324 helpcategory=command.CATEGORY_FILE_CONTENTS,
2331 helpcategory=command.CATEGORY_FILE_CONTENTS,
2325 )
2332 )
2326 def copy(ui, repo, *pats, **opts):
2333 def copy(ui, repo, *pats, **opts):
2327 """mark files as copied for the next commit
2334 """mark files as copied for the next commit
2328
2335
2329 Mark dest as having copies of source files. If dest is a
2336 Mark dest as having copies of source files. If dest is a
2330 directory, copies are put in that directory. If dest is a file,
2337 directory, copies are put in that directory. If dest is a file,
2331 the source must be a single file.
2338 the source must be a single file.
2332
2339
2333 By default, this command copies the contents of files as they
2340 By default, this command copies the contents of files as they
2334 exist in the working directory. If invoked with -A/--after, the
2341 exist in the working directory. If invoked with -A/--after, the
2335 operation is recorded, but no copying is performed.
2342 operation is recorded, but no copying is performed.
2336
2343
2337 To undo marking a file as copied, use --forget. With that option,
2344 To undo marking a file as copied, use --forget. With that option,
2338 all given (positional) arguments are unmarked as copies. The destination
2345 all given (positional) arguments are unmarked as copies. The destination
2339 file(s) will be left in place (still tracked).
2346 file(s) will be left in place (still tracked).
2340
2347
2341 This command takes effect with the next commit.
2348 This command takes effect with the next commit.
2342
2349
2343 Returns 0 on success, 1 if errors are encountered.
2350 Returns 0 on success, 1 if errors are encountered.
2344 """
2351 """
2345 opts = pycompat.byteskwargs(opts)
2352 opts = pycompat.byteskwargs(opts)
2346 with repo.wlock(False):
2353 with repo.wlock(False):
2347 return cmdutil.copy(ui, repo, pats, opts)
2354 return cmdutil.copy(ui, repo, pats, opts)
2348
2355
2349
2356
2350 @command(
2357 @command(
2351 b'debugcommands',
2358 b'debugcommands',
2352 [],
2359 [],
2353 _(b'[COMMAND]'),
2360 _(b'[COMMAND]'),
2354 helpcategory=command.CATEGORY_HELP,
2361 helpcategory=command.CATEGORY_HELP,
2355 norepo=True,
2362 norepo=True,
2356 )
2363 )
2357 def debugcommands(ui, cmd=b'', *args):
2364 def debugcommands(ui, cmd=b'', *args):
2358 """list all available commands and options"""
2365 """list all available commands and options"""
2359 for cmd, vals in sorted(pycompat.iteritems(table)):
2366 for cmd, vals in sorted(pycompat.iteritems(table)):
2360 cmd = cmd.split(b'|')[0]
2367 cmd = cmd.split(b'|')[0]
2361 opts = b', '.join([i[1] for i in vals[1]])
2368 opts = b', '.join([i[1] for i in vals[1]])
2362 ui.write(b'%s: %s\n' % (cmd, opts))
2369 ui.write(b'%s: %s\n' % (cmd, opts))
2363
2370
2364
2371
2365 @command(
2372 @command(
2366 b'debugcomplete',
2373 b'debugcomplete',
2367 [(b'o', b'options', None, _(b'show the command options'))],
2374 [(b'o', b'options', None, _(b'show the command options'))],
2368 _(b'[-o] CMD'),
2375 _(b'[-o] CMD'),
2369 helpcategory=command.CATEGORY_HELP,
2376 helpcategory=command.CATEGORY_HELP,
2370 norepo=True,
2377 norepo=True,
2371 )
2378 )
2372 def debugcomplete(ui, cmd=b'', **opts):
2379 def debugcomplete(ui, cmd=b'', **opts):
2373 """returns the completion list associated with the given command"""
2380 """returns the completion list associated with the given command"""
2374
2381
2375 if opts.get('options'):
2382 if opts.get('options'):
2376 options = []
2383 options = []
2377 otables = [globalopts]
2384 otables = [globalopts]
2378 if cmd:
2385 if cmd:
2379 aliases, entry = cmdutil.findcmd(cmd, table, False)
2386 aliases, entry = cmdutil.findcmd(cmd, table, False)
2380 otables.append(entry[1])
2387 otables.append(entry[1])
2381 for t in otables:
2388 for t in otables:
2382 for o in t:
2389 for o in t:
2383 if b"(DEPRECATED)" in o[3]:
2390 if b"(DEPRECATED)" in o[3]:
2384 continue
2391 continue
2385 if o[0]:
2392 if o[0]:
2386 options.append(b'-%s' % o[0])
2393 options.append(b'-%s' % o[0])
2387 options.append(b'--%s' % o[1])
2394 options.append(b'--%s' % o[1])
2388 ui.write(b"%s\n" % b"\n".join(options))
2395 ui.write(b"%s\n" % b"\n".join(options))
2389 return
2396 return
2390
2397
2391 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2398 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2392 if ui.verbose:
2399 if ui.verbose:
2393 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2400 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2394 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2401 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2395
2402
2396
2403
2397 @command(
2404 @command(
2398 b'diff',
2405 b'diff',
2399 [
2406 [
2400 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2407 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2401 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2408 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2402 ]
2409 ]
2403 + diffopts
2410 + diffopts
2404 + diffopts2
2411 + diffopts2
2405 + walkopts
2412 + walkopts
2406 + subrepoopts,
2413 + subrepoopts,
2407 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2414 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2408 helpcategory=command.CATEGORY_FILE_CONTENTS,
2415 helpcategory=command.CATEGORY_FILE_CONTENTS,
2409 helpbasic=True,
2416 helpbasic=True,
2410 inferrepo=True,
2417 inferrepo=True,
2411 intents={INTENT_READONLY},
2418 intents={INTENT_READONLY},
2412 )
2419 )
2413 def diff(ui, repo, *pats, **opts):
2420 def diff(ui, repo, *pats, **opts):
2414 """diff repository (or selected files)
2421 """diff repository (or selected files)
2415
2422
2416 Show differences between revisions for the specified files.
2423 Show differences between revisions for the specified files.
2417
2424
2418 Differences between files are shown using the unified diff format.
2425 Differences between files are shown using the unified diff format.
2419
2426
2420 .. note::
2427 .. note::
2421
2428
2422 :hg:`diff` may generate unexpected results for merges, as it will
2429 :hg:`diff` may generate unexpected results for merges, as it will
2423 default to comparing against the working directory's first
2430 default to comparing against the working directory's first
2424 parent changeset if no revisions are specified.
2431 parent changeset if no revisions are specified.
2425
2432
2426 When two revision arguments are given, then changes are shown
2433 When two revision arguments are given, then changes are shown
2427 between those revisions. If only one revision is specified then
2434 between those revisions. If only one revision is specified then
2428 that revision is compared to the working directory, and, when no
2435 that revision is compared to the working directory, and, when no
2429 revisions are specified, the working directory files are compared
2436 revisions are specified, the working directory files are compared
2430 to its first parent.
2437 to its first parent.
2431
2438
2432 Alternatively you can specify -c/--change with a revision to see
2439 Alternatively you can specify -c/--change with a revision to see
2433 the changes in that changeset relative to its first parent.
2440 the changes in that changeset relative to its first parent.
2434
2441
2435 Without the -a/--text option, diff will avoid generating diffs of
2442 Without the -a/--text option, diff will avoid generating diffs of
2436 files it detects as binary. With -a, diff will generate a diff
2443 files it detects as binary. With -a, diff will generate a diff
2437 anyway, probably with undesirable results.
2444 anyway, probably with undesirable results.
2438
2445
2439 Use the -g/--git option to generate diffs in the git extended diff
2446 Use the -g/--git option to generate diffs in the git extended diff
2440 format. For more information, read :hg:`help diffs`.
2447 format. For more information, read :hg:`help diffs`.
2441
2448
2442 .. container:: verbose
2449 .. container:: verbose
2443
2450
2444 Examples:
2451 Examples:
2445
2452
2446 - compare a file in the current working directory to its parent::
2453 - compare a file in the current working directory to its parent::
2447
2454
2448 hg diff foo.c
2455 hg diff foo.c
2449
2456
2450 - compare two historical versions of a directory, with rename info::
2457 - compare two historical versions of a directory, with rename info::
2451
2458
2452 hg diff --git -r 1.0:1.2 lib/
2459 hg diff --git -r 1.0:1.2 lib/
2453
2460
2454 - get change stats relative to the last change on some date::
2461 - get change stats relative to the last change on some date::
2455
2462
2456 hg diff --stat -r "date('may 2')"
2463 hg diff --stat -r "date('may 2')"
2457
2464
2458 - diff all newly-added files that contain a keyword::
2465 - diff all newly-added files that contain a keyword::
2459
2466
2460 hg diff "set:added() and grep(GNU)"
2467 hg diff "set:added() and grep(GNU)"
2461
2468
2462 - compare a revision and its parents::
2469 - compare a revision and its parents::
2463
2470
2464 hg diff -c 9353 # compare against first parent
2471 hg diff -c 9353 # compare against first parent
2465 hg diff -r 9353^:9353 # same using revset syntax
2472 hg diff -r 9353^:9353 # same using revset syntax
2466 hg diff -r 9353^2:9353 # compare against the second parent
2473 hg diff -r 9353^2:9353 # compare against the second parent
2467
2474
2468 Returns 0 on success.
2475 Returns 0 on success.
2469 """
2476 """
2470
2477
2471 opts = pycompat.byteskwargs(opts)
2478 opts = pycompat.byteskwargs(opts)
2472 revs = opts.get(b'rev')
2479 revs = opts.get(b'rev')
2473 change = opts.get(b'change')
2480 change = opts.get(b'change')
2474 stat = opts.get(b'stat')
2481 stat = opts.get(b'stat')
2475 reverse = opts.get(b'reverse')
2482 reverse = opts.get(b'reverse')
2476
2483
2477 if revs and change:
2484 if revs and change:
2478 msg = _(b'cannot specify --rev and --change at the same time')
2485 msg = _(b'cannot specify --rev and --change at the same time')
2479 raise error.Abort(msg)
2486 raise error.Abort(msg)
2480 elif change:
2487 elif change:
2481 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2488 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2482 ctx2 = scmutil.revsingle(repo, change, None)
2489 ctx2 = scmutil.revsingle(repo, change, None)
2483 ctx1 = ctx2.p1()
2490 ctx1 = ctx2.p1()
2484 else:
2491 else:
2485 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2492 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2486 ctx1, ctx2 = scmutil.revpair(repo, revs)
2493 ctx1, ctx2 = scmutil.revpair(repo, revs)
2487 node1, node2 = ctx1.node(), ctx2.node()
2494 node1, node2 = ctx1.node(), ctx2.node()
2488
2495
2489 if reverse:
2496 if reverse:
2490 node1, node2 = node2, node1
2497 node1, node2 = node2, node1
2491
2498
2492 diffopts = patch.diffallopts(ui, opts)
2499 diffopts = patch.diffallopts(ui, opts)
2493 m = scmutil.match(ctx2, pats, opts)
2500 m = scmutil.match(ctx2, pats, opts)
2494 m = repo.narrowmatch(m)
2501 m = repo.narrowmatch(m)
2495 ui.pager(b'diff')
2502 ui.pager(b'diff')
2496 logcmdutil.diffordiffstat(
2503 logcmdutil.diffordiffstat(
2497 ui,
2504 ui,
2498 repo,
2505 repo,
2499 diffopts,
2506 diffopts,
2500 node1,
2507 node1,
2501 node2,
2508 node2,
2502 m,
2509 m,
2503 stat=stat,
2510 stat=stat,
2504 listsubrepos=opts.get(b'subrepos'),
2511 listsubrepos=opts.get(b'subrepos'),
2505 root=opts.get(b'root'),
2512 root=opts.get(b'root'),
2506 )
2513 )
2507
2514
2508
2515
2509 @command(
2516 @command(
2510 b'export',
2517 b'export',
2511 [
2518 [
2512 (
2519 (
2513 b'B',
2520 b'B',
2514 b'bookmark',
2521 b'bookmark',
2515 b'',
2522 b'',
2516 _(b'export changes only reachable by given bookmark'),
2523 _(b'export changes only reachable by given bookmark'),
2517 _(b'BOOKMARK'),
2524 _(b'BOOKMARK'),
2518 ),
2525 ),
2519 (
2526 (
2520 b'o',
2527 b'o',
2521 b'output',
2528 b'output',
2522 b'',
2529 b'',
2523 _(b'print output to file with formatted name'),
2530 _(b'print output to file with formatted name'),
2524 _(b'FORMAT'),
2531 _(b'FORMAT'),
2525 ),
2532 ),
2526 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2533 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2527 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2534 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2528 ]
2535 ]
2529 + diffopts
2536 + diffopts
2530 + formatteropts,
2537 + formatteropts,
2531 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2538 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2532 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2539 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2533 helpbasic=True,
2540 helpbasic=True,
2534 intents={INTENT_READONLY},
2541 intents={INTENT_READONLY},
2535 )
2542 )
2536 def export(ui, repo, *changesets, **opts):
2543 def export(ui, repo, *changesets, **opts):
2537 """dump the header and diffs for one or more changesets
2544 """dump the header and diffs for one or more changesets
2538
2545
2539 Print the changeset header and diffs for one or more revisions.
2546 Print the changeset header and diffs for one or more revisions.
2540 If no revision is given, the parent of the working directory is used.
2547 If no revision is given, the parent of the working directory is used.
2541
2548
2542 The information shown in the changeset header is: author, date,
2549 The information shown in the changeset header is: author, date,
2543 branch name (if non-default), changeset hash, parent(s) and commit
2550 branch name (if non-default), changeset hash, parent(s) and commit
2544 comment.
2551 comment.
2545
2552
2546 .. note::
2553 .. note::
2547
2554
2548 :hg:`export` may generate unexpected diff output for merge
2555 :hg:`export` may generate unexpected diff output for merge
2549 changesets, as it will compare the merge changeset against its
2556 changesets, as it will compare the merge changeset against its
2550 first parent only.
2557 first parent only.
2551
2558
2552 Output may be to a file, in which case the name of the file is
2559 Output may be to a file, in which case the name of the file is
2553 given using a template string. See :hg:`help templates`. In addition
2560 given using a template string. See :hg:`help templates`. In addition
2554 to the common template keywords, the following formatting rules are
2561 to the common template keywords, the following formatting rules are
2555 supported:
2562 supported:
2556
2563
2557 :``%%``: literal "%" character
2564 :``%%``: literal "%" character
2558 :``%H``: changeset hash (40 hexadecimal digits)
2565 :``%H``: changeset hash (40 hexadecimal digits)
2559 :``%N``: number of patches being generated
2566 :``%N``: number of patches being generated
2560 :``%R``: changeset revision number
2567 :``%R``: changeset revision number
2561 :``%b``: basename of the exporting repository
2568 :``%b``: basename of the exporting repository
2562 :``%h``: short-form changeset hash (12 hexadecimal digits)
2569 :``%h``: short-form changeset hash (12 hexadecimal digits)
2563 :``%m``: first line of the commit message (only alphanumeric characters)
2570 :``%m``: first line of the commit message (only alphanumeric characters)
2564 :``%n``: zero-padded sequence number, starting at 1
2571 :``%n``: zero-padded sequence number, starting at 1
2565 :``%r``: zero-padded changeset revision number
2572 :``%r``: zero-padded changeset revision number
2566 :``\\``: literal "\\" character
2573 :``\\``: literal "\\" character
2567
2574
2568 Without the -a/--text option, export will avoid generating diffs
2575 Without the -a/--text option, export will avoid generating diffs
2569 of files it detects as binary. With -a, export will generate a
2576 of files it detects as binary. With -a, export will generate a
2570 diff anyway, probably with undesirable results.
2577 diff anyway, probably with undesirable results.
2571
2578
2572 With -B/--bookmark changesets reachable by the given bookmark are
2579 With -B/--bookmark changesets reachable by the given bookmark are
2573 selected.
2580 selected.
2574
2581
2575 Use the -g/--git option to generate diffs in the git extended diff
2582 Use the -g/--git option to generate diffs in the git extended diff
2576 format. See :hg:`help diffs` for more information.
2583 format. See :hg:`help diffs` for more information.
2577
2584
2578 With the --switch-parent option, the diff will be against the
2585 With the --switch-parent option, the diff will be against the
2579 second parent. It can be useful to review a merge.
2586 second parent. It can be useful to review a merge.
2580
2587
2581 .. container:: verbose
2588 .. container:: verbose
2582
2589
2583 Template:
2590 Template:
2584
2591
2585 The following keywords are supported in addition to the common template
2592 The following keywords are supported in addition to the common template
2586 keywords and functions. See also :hg:`help templates`.
2593 keywords and functions. See also :hg:`help templates`.
2587
2594
2588 :diff: String. Diff content.
2595 :diff: String. Diff content.
2589 :parents: List of strings. Parent nodes of the changeset.
2596 :parents: List of strings. Parent nodes of the changeset.
2590
2597
2591 Examples:
2598 Examples:
2592
2599
2593 - use export and import to transplant a bugfix to the current
2600 - use export and import to transplant a bugfix to the current
2594 branch::
2601 branch::
2595
2602
2596 hg export -r 9353 | hg import -
2603 hg export -r 9353 | hg import -
2597
2604
2598 - export all the changesets between two revisions to a file with
2605 - export all the changesets between two revisions to a file with
2599 rename information::
2606 rename information::
2600
2607
2601 hg export --git -r 123:150 > changes.txt
2608 hg export --git -r 123:150 > changes.txt
2602
2609
2603 - split outgoing changes into a series of patches with
2610 - split outgoing changes into a series of patches with
2604 descriptive names::
2611 descriptive names::
2605
2612
2606 hg export -r "outgoing()" -o "%n-%m.patch"
2613 hg export -r "outgoing()" -o "%n-%m.patch"
2607
2614
2608 Returns 0 on success.
2615 Returns 0 on success.
2609 """
2616 """
2610 opts = pycompat.byteskwargs(opts)
2617 opts = pycompat.byteskwargs(opts)
2611 bookmark = opts.get(b'bookmark')
2618 bookmark = opts.get(b'bookmark')
2612 changesets += tuple(opts.get(b'rev', []))
2619 changesets += tuple(opts.get(b'rev', []))
2613
2620
2614 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2621 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2615
2622
2616 if bookmark:
2623 if bookmark:
2617 if bookmark not in repo._bookmarks:
2624 if bookmark not in repo._bookmarks:
2618 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2625 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2619
2626
2620 revs = scmutil.bookmarkrevs(repo, bookmark)
2627 revs = scmutil.bookmarkrevs(repo, bookmark)
2621 else:
2628 else:
2622 if not changesets:
2629 if not changesets:
2623 changesets = [b'.']
2630 changesets = [b'.']
2624
2631
2625 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2632 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2626 revs = scmutil.revrange(repo, changesets)
2633 revs = scmutil.revrange(repo, changesets)
2627
2634
2628 if not revs:
2635 if not revs:
2629 raise error.Abort(_(b"export requires at least one changeset"))
2636 raise error.Abort(_(b"export requires at least one changeset"))
2630 if len(revs) > 1:
2637 if len(revs) > 1:
2631 ui.note(_(b'exporting patches:\n'))
2638 ui.note(_(b'exporting patches:\n'))
2632 else:
2639 else:
2633 ui.note(_(b'exporting patch:\n'))
2640 ui.note(_(b'exporting patch:\n'))
2634
2641
2635 fntemplate = opts.get(b'output')
2642 fntemplate = opts.get(b'output')
2636 if cmdutil.isstdiofilename(fntemplate):
2643 if cmdutil.isstdiofilename(fntemplate):
2637 fntemplate = b''
2644 fntemplate = b''
2638
2645
2639 if fntemplate:
2646 if fntemplate:
2640 fm = formatter.nullformatter(ui, b'export', opts)
2647 fm = formatter.nullformatter(ui, b'export', opts)
2641 else:
2648 else:
2642 ui.pager(b'export')
2649 ui.pager(b'export')
2643 fm = ui.formatter(b'export', opts)
2650 fm = ui.formatter(b'export', opts)
2644 with fm:
2651 with fm:
2645 cmdutil.export(
2652 cmdutil.export(
2646 repo,
2653 repo,
2647 revs,
2654 revs,
2648 fm,
2655 fm,
2649 fntemplate=fntemplate,
2656 fntemplate=fntemplate,
2650 switch_parent=opts.get(b'switch_parent'),
2657 switch_parent=opts.get(b'switch_parent'),
2651 opts=patch.diffallopts(ui, opts),
2658 opts=patch.diffallopts(ui, opts),
2652 )
2659 )
2653
2660
2654
2661
2655 @command(
2662 @command(
2656 b'files',
2663 b'files',
2657 [
2664 [
2658 (
2665 (
2659 b'r',
2666 b'r',
2660 b'rev',
2667 b'rev',
2661 b'',
2668 b'',
2662 _(b'search the repository as it is in REV'),
2669 _(b'search the repository as it is in REV'),
2663 _(b'REV'),
2670 _(b'REV'),
2664 ),
2671 ),
2665 (
2672 (
2666 b'0',
2673 b'0',
2667 b'print0',
2674 b'print0',
2668 None,
2675 None,
2669 _(b'end filenames with NUL, for use with xargs'),
2676 _(b'end filenames with NUL, for use with xargs'),
2670 ),
2677 ),
2671 ]
2678 ]
2672 + walkopts
2679 + walkopts
2673 + formatteropts
2680 + formatteropts
2674 + subrepoopts,
2681 + subrepoopts,
2675 _(b'[OPTION]... [FILE]...'),
2682 _(b'[OPTION]... [FILE]...'),
2676 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2677 intents={INTENT_READONLY},
2684 intents={INTENT_READONLY},
2678 )
2685 )
2679 def files(ui, repo, *pats, **opts):
2686 def files(ui, repo, *pats, **opts):
2680 """list tracked files
2687 """list tracked files
2681
2688
2682 Print files under Mercurial control in the working directory or
2689 Print files under Mercurial control in the working directory or
2683 specified revision for given files (excluding removed files).
2690 specified revision for given files (excluding removed files).
2684 Files can be specified as filenames or filesets.
2691 Files can be specified as filenames or filesets.
2685
2692
2686 If no files are given to match, this command prints the names
2693 If no files are given to match, this command prints the names
2687 of all files under Mercurial control.
2694 of all files under Mercurial control.
2688
2695
2689 .. container:: verbose
2696 .. container:: verbose
2690
2697
2691 Template:
2698 Template:
2692
2699
2693 The following keywords are supported in addition to the common template
2700 The following keywords are supported in addition to the common template
2694 keywords and functions. See also :hg:`help templates`.
2701 keywords and functions. See also :hg:`help templates`.
2695
2702
2696 :flags: String. Character denoting file's symlink and executable bits.
2703 :flags: String. Character denoting file's symlink and executable bits.
2697 :path: String. Repository-absolute path of the file.
2704 :path: String. Repository-absolute path of the file.
2698 :size: Integer. Size of the file in bytes.
2705 :size: Integer. Size of the file in bytes.
2699
2706
2700 Examples:
2707 Examples:
2701
2708
2702 - list all files under the current directory::
2709 - list all files under the current directory::
2703
2710
2704 hg files .
2711 hg files .
2705
2712
2706 - shows sizes and flags for current revision::
2713 - shows sizes and flags for current revision::
2707
2714
2708 hg files -vr .
2715 hg files -vr .
2709
2716
2710 - list all files named README::
2717 - list all files named README::
2711
2718
2712 hg files -I "**/README"
2719 hg files -I "**/README"
2713
2720
2714 - list all binary files::
2721 - list all binary files::
2715
2722
2716 hg files "set:binary()"
2723 hg files "set:binary()"
2717
2724
2718 - find files containing a regular expression::
2725 - find files containing a regular expression::
2719
2726
2720 hg files "set:grep('bob')"
2727 hg files "set:grep('bob')"
2721
2728
2722 - search tracked file contents with xargs and grep::
2729 - search tracked file contents with xargs and grep::
2723
2730
2724 hg files -0 | xargs -0 grep foo
2731 hg files -0 | xargs -0 grep foo
2725
2732
2726 See :hg:`help patterns` and :hg:`help filesets` for more information
2733 See :hg:`help patterns` and :hg:`help filesets` for more information
2727 on specifying file patterns.
2734 on specifying file patterns.
2728
2735
2729 Returns 0 if a match is found, 1 otherwise.
2736 Returns 0 if a match is found, 1 otherwise.
2730
2737
2731 """
2738 """
2732
2739
2733 opts = pycompat.byteskwargs(opts)
2740 opts = pycompat.byteskwargs(opts)
2734 rev = opts.get(b'rev')
2741 rev = opts.get(b'rev')
2735 if rev:
2742 if rev:
2736 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2743 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2737 ctx = scmutil.revsingle(repo, rev, None)
2744 ctx = scmutil.revsingle(repo, rev, None)
2738
2745
2739 end = b'\n'
2746 end = b'\n'
2740 if opts.get(b'print0'):
2747 if opts.get(b'print0'):
2741 end = b'\0'
2748 end = b'\0'
2742 fmt = b'%s' + end
2749 fmt = b'%s' + end
2743
2750
2744 m = scmutil.match(ctx, pats, opts)
2751 m = scmutil.match(ctx, pats, opts)
2745 ui.pager(b'files')
2752 ui.pager(b'files')
2746 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2753 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2747 with ui.formatter(b'files', opts) as fm:
2754 with ui.formatter(b'files', opts) as fm:
2748 return cmdutil.files(
2755 return cmdutil.files(
2749 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2756 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2750 )
2757 )
2751
2758
2752
2759
2753 @command(
2760 @command(
2754 b'forget',
2761 b'forget',
2755 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2762 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2756 + walkopts
2763 + walkopts
2757 + dryrunopts,
2764 + dryrunopts,
2758 _(b'[OPTION]... FILE...'),
2765 _(b'[OPTION]... FILE...'),
2759 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2766 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2760 helpbasic=True,
2767 helpbasic=True,
2761 inferrepo=True,
2768 inferrepo=True,
2762 )
2769 )
2763 def forget(ui, repo, *pats, **opts):
2770 def forget(ui, repo, *pats, **opts):
2764 """forget the specified files on the next commit
2771 """forget the specified files on the next commit
2765
2772
2766 Mark the specified files so they will no longer be tracked
2773 Mark the specified files so they will no longer be tracked
2767 after the next commit.
2774 after the next commit.
2768
2775
2769 This only removes files from the current branch, not from the
2776 This only removes files from the current branch, not from the
2770 entire project history, and it does not delete them from the
2777 entire project history, and it does not delete them from the
2771 working directory.
2778 working directory.
2772
2779
2773 To delete the file from the working directory, see :hg:`remove`.
2780 To delete the file from the working directory, see :hg:`remove`.
2774
2781
2775 To undo a forget before the next commit, see :hg:`add`.
2782 To undo a forget before the next commit, see :hg:`add`.
2776
2783
2777 .. container:: verbose
2784 .. container:: verbose
2778
2785
2779 Examples:
2786 Examples:
2780
2787
2781 - forget newly-added binary files::
2788 - forget newly-added binary files::
2782
2789
2783 hg forget "set:added() and binary()"
2790 hg forget "set:added() and binary()"
2784
2791
2785 - forget files that would be excluded by .hgignore::
2792 - forget files that would be excluded by .hgignore::
2786
2793
2787 hg forget "set:hgignore()"
2794 hg forget "set:hgignore()"
2788
2795
2789 Returns 0 on success.
2796 Returns 0 on success.
2790 """
2797 """
2791
2798
2792 opts = pycompat.byteskwargs(opts)
2799 opts = pycompat.byteskwargs(opts)
2793 if not pats:
2800 if not pats:
2794 raise error.Abort(_(b'no files specified'))
2801 raise error.Abort(_(b'no files specified'))
2795
2802
2796 m = scmutil.match(repo[None], pats, opts)
2803 m = scmutil.match(repo[None], pats, opts)
2797 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2804 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2798 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2805 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2799 rejected = cmdutil.forget(
2806 rejected = cmdutil.forget(
2800 ui,
2807 ui,
2801 repo,
2808 repo,
2802 m,
2809 m,
2803 prefix=b"",
2810 prefix=b"",
2804 uipathfn=uipathfn,
2811 uipathfn=uipathfn,
2805 explicitonly=False,
2812 explicitonly=False,
2806 dryrun=dryrun,
2813 dryrun=dryrun,
2807 interactive=interactive,
2814 interactive=interactive,
2808 )[0]
2815 )[0]
2809 return rejected and 1 or 0
2816 return rejected and 1 or 0
2810
2817
2811
2818
2812 @command(
2819 @command(
2813 b'graft',
2820 b'graft',
2814 [
2821 [
2815 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2822 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2816 (
2823 (
2817 b'',
2824 b'',
2818 b'base',
2825 b'base',
2819 b'',
2826 b'',
2820 _(b'base revision when doing the graft merge (ADVANCED)'),
2827 _(b'base revision when doing the graft merge (ADVANCED)'),
2821 _(b'REV'),
2828 _(b'REV'),
2822 ),
2829 ),
2823 (b'c', b'continue', False, _(b'resume interrupted graft')),
2830 (b'c', b'continue', False, _(b'resume interrupted graft')),
2824 (b'', b'stop', False, _(b'stop interrupted graft')),
2831 (b'', b'stop', False, _(b'stop interrupted graft')),
2825 (b'', b'abort', False, _(b'abort interrupted graft')),
2832 (b'', b'abort', False, _(b'abort interrupted graft')),
2826 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2833 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2827 (b'', b'log', None, _(b'append graft info to log message')),
2834 (b'', b'log', None, _(b'append graft info to log message')),
2828 (
2835 (
2829 b'',
2836 b'',
2830 b'no-commit',
2837 b'no-commit',
2831 None,
2838 None,
2832 _(b"don't commit, just apply the changes in working directory"),
2839 _(b"don't commit, just apply the changes in working directory"),
2833 ),
2840 ),
2834 (b'f', b'force', False, _(b'force graft')),
2841 (b'f', b'force', False, _(b'force graft')),
2835 (
2842 (
2836 b'D',
2843 b'D',
2837 b'currentdate',
2844 b'currentdate',
2838 False,
2845 False,
2839 _(b'record the current date as commit date'),
2846 _(b'record the current date as commit date'),
2840 ),
2847 ),
2841 (
2848 (
2842 b'U',
2849 b'U',
2843 b'currentuser',
2850 b'currentuser',
2844 False,
2851 False,
2845 _(b'record the current user as committer'),
2852 _(b'record the current user as committer'),
2846 ),
2853 ),
2847 ]
2854 ]
2848 + commitopts2
2855 + commitopts2
2849 + mergetoolopts
2856 + mergetoolopts
2850 + dryrunopts,
2857 + dryrunopts,
2851 _(b'[OPTION]... [-r REV]... REV...'),
2858 _(b'[OPTION]... [-r REV]... REV...'),
2852 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2859 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2853 )
2860 )
2854 def graft(ui, repo, *revs, **opts):
2861 def graft(ui, repo, *revs, **opts):
2855 '''copy changes from other branches onto the current branch
2862 '''copy changes from other branches onto the current branch
2856
2863
2857 This command uses Mercurial's merge logic to copy individual
2864 This command uses Mercurial's merge logic to copy individual
2858 changes from other branches without merging branches in the
2865 changes from other branches without merging branches in the
2859 history graph. This is sometimes known as 'backporting' or
2866 history graph. This is sometimes known as 'backporting' or
2860 'cherry-picking'. By default, graft will copy user, date, and
2867 'cherry-picking'. By default, graft will copy user, date, and
2861 description from the source changesets.
2868 description from the source changesets.
2862
2869
2863 Changesets that are ancestors of the current revision, that have
2870 Changesets that are ancestors of the current revision, that have
2864 already been grafted, or that are merges will be skipped.
2871 already been grafted, or that are merges will be skipped.
2865
2872
2866 If --log is specified, log messages will have a comment appended
2873 If --log is specified, log messages will have a comment appended
2867 of the form::
2874 of the form::
2868
2875
2869 (grafted from CHANGESETHASH)
2876 (grafted from CHANGESETHASH)
2870
2877
2871 If --force is specified, revisions will be grafted even if they
2878 If --force is specified, revisions will be grafted even if they
2872 are already ancestors of, or have been grafted to, the destination.
2879 are already ancestors of, or have been grafted to, the destination.
2873 This is useful when the revisions have since been backed out.
2880 This is useful when the revisions have since been backed out.
2874
2881
2875 If a graft merge results in conflicts, the graft process is
2882 If a graft merge results in conflicts, the graft process is
2876 interrupted so that the current merge can be manually resolved.
2883 interrupted so that the current merge can be manually resolved.
2877 Once all conflicts are addressed, the graft process can be
2884 Once all conflicts are addressed, the graft process can be
2878 continued with the -c/--continue option.
2885 continued with the -c/--continue option.
2879
2886
2880 The -c/--continue option reapplies all the earlier options.
2887 The -c/--continue option reapplies all the earlier options.
2881
2888
2882 .. container:: verbose
2889 .. container:: verbose
2883
2890
2884 The --base option exposes more of how graft internally uses merge with a
2891 The --base option exposes more of how graft internally uses merge with a
2885 custom base revision. --base can be used to specify another ancestor than
2892 custom base revision. --base can be used to specify another ancestor than
2886 the first and only parent.
2893 the first and only parent.
2887
2894
2888 The command::
2895 The command::
2889
2896
2890 hg graft -r 345 --base 234
2897 hg graft -r 345 --base 234
2891
2898
2892 is thus pretty much the same as::
2899 is thus pretty much the same as::
2893
2900
2894 hg diff -r 234 -r 345 | hg import
2901 hg diff -r 234 -r 345 | hg import
2895
2902
2896 but using merge to resolve conflicts and track moved files.
2903 but using merge to resolve conflicts and track moved files.
2897
2904
2898 The result of a merge can thus be backported as a single commit by
2905 The result of a merge can thus be backported as a single commit by
2899 specifying one of the merge parents as base, and thus effectively
2906 specifying one of the merge parents as base, and thus effectively
2900 grafting the changes from the other side.
2907 grafting the changes from the other side.
2901
2908
2902 It is also possible to collapse multiple changesets and clean up history
2909 It is also possible to collapse multiple changesets and clean up history
2903 by specifying another ancestor as base, much like rebase --collapse
2910 by specifying another ancestor as base, much like rebase --collapse
2904 --keep.
2911 --keep.
2905
2912
2906 The commit message can be tweaked after the fact using commit --amend .
2913 The commit message can be tweaked after the fact using commit --amend .
2907
2914
2908 For using non-ancestors as the base to backout changes, see the backout
2915 For using non-ancestors as the base to backout changes, see the backout
2909 command and the hidden --parent option.
2916 command and the hidden --parent option.
2910
2917
2911 .. container:: verbose
2918 .. container:: verbose
2912
2919
2913 Examples:
2920 Examples:
2914
2921
2915 - copy a single change to the stable branch and edit its description::
2922 - copy a single change to the stable branch and edit its description::
2916
2923
2917 hg update stable
2924 hg update stable
2918 hg graft --edit 9393
2925 hg graft --edit 9393
2919
2926
2920 - graft a range of changesets with one exception, updating dates::
2927 - graft a range of changesets with one exception, updating dates::
2921
2928
2922 hg graft -D "2085::2093 and not 2091"
2929 hg graft -D "2085::2093 and not 2091"
2923
2930
2924 - continue a graft after resolving conflicts::
2931 - continue a graft after resolving conflicts::
2925
2932
2926 hg graft -c
2933 hg graft -c
2927
2934
2928 - show the source of a grafted changeset::
2935 - show the source of a grafted changeset::
2929
2936
2930 hg log --debug -r .
2937 hg log --debug -r .
2931
2938
2932 - show revisions sorted by date::
2939 - show revisions sorted by date::
2933
2940
2934 hg log -r "sort(all(), date)"
2941 hg log -r "sort(all(), date)"
2935
2942
2936 - backport the result of a merge as a single commit::
2943 - backport the result of a merge as a single commit::
2937
2944
2938 hg graft -r 123 --base 123^
2945 hg graft -r 123 --base 123^
2939
2946
2940 - land a feature branch as one changeset::
2947 - land a feature branch as one changeset::
2941
2948
2942 hg up -cr default
2949 hg up -cr default
2943 hg graft -r featureX --base "ancestor('featureX', 'default')"
2950 hg graft -r featureX --base "ancestor('featureX', 'default')"
2944
2951
2945 See :hg:`help revisions` for more about specifying revisions.
2952 See :hg:`help revisions` for more about specifying revisions.
2946
2953
2947 Returns 0 on successful completion.
2954 Returns 0 on successful completion.
2948 '''
2955 '''
2949 with repo.wlock():
2956 with repo.wlock():
2950 return _dograft(ui, repo, *revs, **opts)
2957 return _dograft(ui, repo, *revs, **opts)
2951
2958
2952
2959
2953 def _dograft(ui, repo, *revs, **opts):
2960 def _dograft(ui, repo, *revs, **opts):
2954 opts = pycompat.byteskwargs(opts)
2961 opts = pycompat.byteskwargs(opts)
2955 if revs and opts.get(b'rev'):
2962 if revs and opts.get(b'rev'):
2956 ui.warn(
2963 ui.warn(
2957 _(
2964 _(
2958 b'warning: inconsistent use of --rev might give unexpected '
2965 b'warning: inconsistent use of --rev might give unexpected '
2959 b'revision ordering!\n'
2966 b'revision ordering!\n'
2960 )
2967 )
2961 )
2968 )
2962
2969
2963 revs = list(revs)
2970 revs = list(revs)
2964 revs.extend(opts.get(b'rev'))
2971 revs.extend(opts.get(b'rev'))
2965 basectx = None
2972 basectx = None
2966 if opts.get(b'base'):
2973 if opts.get(b'base'):
2967 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2974 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2968 # a dict of data to be stored in state file
2975 # a dict of data to be stored in state file
2969 statedata = {}
2976 statedata = {}
2970 # list of new nodes created by ongoing graft
2977 # list of new nodes created by ongoing graft
2971 statedata[b'newnodes'] = []
2978 statedata[b'newnodes'] = []
2972
2979
2973 cmdutil.resolvecommitoptions(ui, opts)
2980 cmdutil.resolvecommitoptions(ui, opts)
2974
2981
2975 editor = cmdutil.getcommiteditor(
2982 editor = cmdutil.getcommiteditor(
2976 editform=b'graft', **pycompat.strkwargs(opts)
2983 editform=b'graft', **pycompat.strkwargs(opts)
2977 )
2984 )
2978
2985
2979 cont = False
2986 cont = False
2980 if opts.get(b'no_commit'):
2987 if opts.get(b'no_commit'):
2981 if opts.get(b'edit'):
2988 if opts.get(b'edit'):
2982 raise error.Abort(
2989 raise error.Abort(
2983 _(b"cannot specify --no-commit and --edit together")
2990 _(b"cannot specify --no-commit and --edit together")
2984 )
2991 )
2985 if opts.get(b'currentuser'):
2992 if opts.get(b'currentuser'):
2986 raise error.Abort(
2993 raise error.Abort(
2987 _(b"cannot specify --no-commit and --currentuser together")
2994 _(b"cannot specify --no-commit and --currentuser together")
2988 )
2995 )
2989 if opts.get(b'currentdate'):
2996 if opts.get(b'currentdate'):
2990 raise error.Abort(
2997 raise error.Abort(
2991 _(b"cannot specify --no-commit and --currentdate together")
2998 _(b"cannot specify --no-commit and --currentdate together")
2992 )
2999 )
2993 if opts.get(b'log'):
3000 if opts.get(b'log'):
2994 raise error.Abort(
3001 raise error.Abort(
2995 _(b"cannot specify --no-commit and --log together")
3002 _(b"cannot specify --no-commit and --log together")
2996 )
3003 )
2997
3004
2998 graftstate = statemod.cmdstate(repo, b'graftstate')
3005 graftstate = statemod.cmdstate(repo, b'graftstate')
2999
3006
3000 if opts.get(b'stop'):
3007 if opts.get(b'stop'):
3001 if opts.get(b'continue'):
3008 if opts.get(b'continue'):
3002 raise error.Abort(
3009 raise error.Abort(
3003 _(b"cannot use '--continue' and '--stop' together")
3010 _(b"cannot use '--continue' and '--stop' together")
3004 )
3011 )
3005 if opts.get(b'abort'):
3012 if opts.get(b'abort'):
3006 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3013 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3007
3014
3008 if any(
3015 if any(
3009 (
3016 (
3010 opts.get(b'edit'),
3017 opts.get(b'edit'),
3011 opts.get(b'log'),
3018 opts.get(b'log'),
3012 opts.get(b'user'),
3019 opts.get(b'user'),
3013 opts.get(b'date'),
3020 opts.get(b'date'),
3014 opts.get(b'currentdate'),
3021 opts.get(b'currentdate'),
3015 opts.get(b'currentuser'),
3022 opts.get(b'currentuser'),
3016 opts.get(b'rev'),
3023 opts.get(b'rev'),
3017 )
3024 )
3018 ):
3025 ):
3019 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3026 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3020 return _stopgraft(ui, repo, graftstate)
3027 return _stopgraft(ui, repo, graftstate)
3021 elif opts.get(b'abort'):
3028 elif opts.get(b'abort'):
3022 if opts.get(b'continue'):
3029 if opts.get(b'continue'):
3023 raise error.Abort(
3030 raise error.Abort(
3024 _(b"cannot use '--continue' and '--abort' together")
3031 _(b"cannot use '--continue' and '--abort' together")
3025 )
3032 )
3026 if any(
3033 if any(
3027 (
3034 (
3028 opts.get(b'edit'),
3035 opts.get(b'edit'),
3029 opts.get(b'log'),
3036 opts.get(b'log'),
3030 opts.get(b'user'),
3037 opts.get(b'user'),
3031 opts.get(b'date'),
3038 opts.get(b'date'),
3032 opts.get(b'currentdate'),
3039 opts.get(b'currentdate'),
3033 opts.get(b'currentuser'),
3040 opts.get(b'currentuser'),
3034 opts.get(b'rev'),
3041 opts.get(b'rev'),
3035 )
3042 )
3036 ):
3043 ):
3037 raise error.Abort(
3044 raise error.Abort(
3038 _(b"cannot specify any other flag with '--abort'")
3045 _(b"cannot specify any other flag with '--abort'")
3039 )
3046 )
3040
3047
3041 return cmdutil.abortgraft(ui, repo, graftstate)
3048 return cmdutil.abortgraft(ui, repo, graftstate)
3042 elif opts.get(b'continue'):
3049 elif opts.get(b'continue'):
3043 cont = True
3050 cont = True
3044 if revs:
3051 if revs:
3045 raise error.Abort(_(b"can't specify --continue and revisions"))
3052 raise error.Abort(_(b"can't specify --continue and revisions"))
3046 # read in unfinished revisions
3053 # read in unfinished revisions
3047 if graftstate.exists():
3054 if graftstate.exists():
3048 statedata = cmdutil.readgraftstate(repo, graftstate)
3055 statedata = cmdutil.readgraftstate(repo, graftstate)
3049 if statedata.get(b'date'):
3056 if statedata.get(b'date'):
3050 opts[b'date'] = statedata[b'date']
3057 opts[b'date'] = statedata[b'date']
3051 if statedata.get(b'user'):
3058 if statedata.get(b'user'):
3052 opts[b'user'] = statedata[b'user']
3059 opts[b'user'] = statedata[b'user']
3053 if statedata.get(b'log'):
3060 if statedata.get(b'log'):
3054 opts[b'log'] = True
3061 opts[b'log'] = True
3055 if statedata.get(b'no_commit'):
3062 if statedata.get(b'no_commit'):
3056 opts[b'no_commit'] = statedata.get(b'no_commit')
3063 opts[b'no_commit'] = statedata.get(b'no_commit')
3057 nodes = statedata[b'nodes']
3064 nodes = statedata[b'nodes']
3058 revs = [repo[node].rev() for node in nodes]
3065 revs = [repo[node].rev() for node in nodes]
3059 else:
3066 else:
3060 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3067 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3061 else:
3068 else:
3062 if not revs:
3069 if not revs:
3063 raise error.Abort(_(b'no revisions specified'))
3070 raise error.Abort(_(b'no revisions specified'))
3064 cmdutil.checkunfinished(repo)
3071 cmdutil.checkunfinished(repo)
3065 cmdutil.bailifchanged(repo)
3072 cmdutil.bailifchanged(repo)
3066 revs = scmutil.revrange(repo, revs)
3073 revs = scmutil.revrange(repo, revs)
3067
3074
3068 skipped = set()
3075 skipped = set()
3069 if basectx is None:
3076 if basectx is None:
3070 # check for merges
3077 # check for merges
3071 for rev in repo.revs(b'%ld and merge()', revs):
3078 for rev in repo.revs(b'%ld and merge()', revs):
3072 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3079 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3073 skipped.add(rev)
3080 skipped.add(rev)
3074 revs = [r for r in revs if r not in skipped]
3081 revs = [r for r in revs if r not in skipped]
3075 if not revs:
3082 if not revs:
3076 return -1
3083 return -1
3077 if basectx is not None and len(revs) != 1:
3084 if basectx is not None and len(revs) != 1:
3078 raise error.Abort(_(b'only one revision allowed with --base '))
3085 raise error.Abort(_(b'only one revision allowed with --base '))
3079
3086
3080 # Don't check in the --continue case, in effect retaining --force across
3087 # Don't check in the --continue case, in effect retaining --force across
3081 # --continues. That's because without --force, any revisions we decided to
3088 # --continues. That's because without --force, any revisions we decided to
3082 # skip would have been filtered out here, so they wouldn't have made their
3089 # skip would have been filtered out here, so they wouldn't have made their
3083 # way to the graftstate. With --force, any revisions we would have otherwise
3090 # way to the graftstate. With --force, any revisions we would have otherwise
3084 # skipped would not have been filtered out, and if they hadn't been applied
3091 # skipped would not have been filtered out, and if they hadn't been applied
3085 # already, they'd have been in the graftstate.
3092 # already, they'd have been in the graftstate.
3086 if not (cont or opts.get(b'force')) and basectx is None:
3093 if not (cont or opts.get(b'force')) and basectx is None:
3087 # check for ancestors of dest branch
3094 # check for ancestors of dest branch
3088 ancestors = repo.revs(b'%ld & (::.)', revs)
3095 ancestors = repo.revs(b'%ld & (::.)', revs)
3089 for rev in ancestors:
3096 for rev in ancestors:
3090 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3097 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3091
3098
3092 revs = [r for r in revs if r not in ancestors]
3099 revs = [r for r in revs if r not in ancestors]
3093
3100
3094 if not revs:
3101 if not revs:
3095 return -1
3102 return -1
3096
3103
3097 # analyze revs for earlier grafts
3104 # analyze revs for earlier grafts
3098 ids = {}
3105 ids = {}
3099 for ctx in repo.set(b"%ld", revs):
3106 for ctx in repo.set(b"%ld", revs):
3100 ids[ctx.hex()] = ctx.rev()
3107 ids[ctx.hex()] = ctx.rev()
3101 n = ctx.extra().get(b'source')
3108 n = ctx.extra().get(b'source')
3102 if n:
3109 if n:
3103 ids[n] = ctx.rev()
3110 ids[n] = ctx.rev()
3104
3111
3105 # check ancestors for earlier grafts
3112 # check ancestors for earlier grafts
3106 ui.debug(b'scanning for duplicate grafts\n')
3113 ui.debug(b'scanning for duplicate grafts\n')
3107
3114
3108 # The only changesets we can be sure doesn't contain grafts of any
3115 # The only changesets we can be sure doesn't contain grafts of any
3109 # revs, are the ones that are common ancestors of *all* revs:
3116 # revs, are the ones that are common ancestors of *all* revs:
3110 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3117 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3111 ctx = repo[rev]
3118 ctx = repo[rev]
3112 n = ctx.extra().get(b'source')
3119 n = ctx.extra().get(b'source')
3113 if n in ids:
3120 if n in ids:
3114 try:
3121 try:
3115 r = repo[n].rev()
3122 r = repo[n].rev()
3116 except error.RepoLookupError:
3123 except error.RepoLookupError:
3117 r = None
3124 r = None
3118 if r in revs:
3125 if r in revs:
3119 ui.warn(
3126 ui.warn(
3120 _(
3127 _(
3121 b'skipping revision %d:%s '
3128 b'skipping revision %d:%s '
3122 b'(already grafted to %d:%s)\n'
3129 b'(already grafted to %d:%s)\n'
3123 )
3130 )
3124 % (r, repo[r], rev, ctx)
3131 % (r, repo[r], rev, ctx)
3125 )
3132 )
3126 revs.remove(r)
3133 revs.remove(r)
3127 elif ids[n] in revs:
3134 elif ids[n] in revs:
3128 if r is None:
3135 if r is None:
3129 ui.warn(
3136 ui.warn(
3130 _(
3137 _(
3131 b'skipping already grafted revision %d:%s '
3138 b'skipping already grafted revision %d:%s '
3132 b'(%d:%s also has unknown origin %s)\n'
3139 b'(%d:%s also has unknown origin %s)\n'
3133 )
3140 )
3134 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3141 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3135 )
3142 )
3136 else:
3143 else:
3137 ui.warn(
3144 ui.warn(
3138 _(
3145 _(
3139 b'skipping already grafted revision %d:%s '
3146 b'skipping already grafted revision %d:%s '
3140 b'(%d:%s also has origin %d:%s)\n'
3147 b'(%d:%s also has origin %d:%s)\n'
3141 )
3148 )
3142 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3149 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3143 )
3150 )
3144 revs.remove(ids[n])
3151 revs.remove(ids[n])
3145 elif ctx.hex() in ids:
3152 elif ctx.hex() in ids:
3146 r = ids[ctx.hex()]
3153 r = ids[ctx.hex()]
3147 if r in revs:
3154 if r in revs:
3148 ui.warn(
3155 ui.warn(
3149 _(
3156 _(
3150 b'skipping already grafted revision %d:%s '
3157 b'skipping already grafted revision %d:%s '
3151 b'(was grafted from %d:%s)\n'
3158 b'(was grafted from %d:%s)\n'
3152 )
3159 )
3153 % (r, repo[r], rev, ctx)
3160 % (r, repo[r], rev, ctx)
3154 )
3161 )
3155 revs.remove(r)
3162 revs.remove(r)
3156 if not revs:
3163 if not revs:
3157 return -1
3164 return -1
3158
3165
3159 if opts.get(b'no_commit'):
3166 if opts.get(b'no_commit'):
3160 statedata[b'no_commit'] = True
3167 statedata[b'no_commit'] = True
3161 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3168 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3162 desc = b'%d:%s "%s"' % (
3169 desc = b'%d:%s "%s"' % (
3163 ctx.rev(),
3170 ctx.rev(),
3164 ctx,
3171 ctx,
3165 ctx.description().split(b'\n', 1)[0],
3172 ctx.description().split(b'\n', 1)[0],
3166 )
3173 )
3167 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3174 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3168 if names:
3175 if names:
3169 desc += b' (%s)' % b' '.join(names)
3176 desc += b' (%s)' % b' '.join(names)
3170 ui.status(_(b'grafting %s\n') % desc)
3177 ui.status(_(b'grafting %s\n') % desc)
3171 if opts.get(b'dry_run'):
3178 if opts.get(b'dry_run'):
3172 continue
3179 continue
3173
3180
3174 source = ctx.extra().get(b'source')
3181 source = ctx.extra().get(b'source')
3175 extra = {}
3182 extra = {}
3176 if source:
3183 if source:
3177 extra[b'source'] = source
3184 extra[b'source'] = source
3178 extra[b'intermediate-source'] = ctx.hex()
3185 extra[b'intermediate-source'] = ctx.hex()
3179 else:
3186 else:
3180 extra[b'source'] = ctx.hex()
3187 extra[b'source'] = ctx.hex()
3181 user = ctx.user()
3188 user = ctx.user()
3182 if opts.get(b'user'):
3189 if opts.get(b'user'):
3183 user = opts[b'user']
3190 user = opts[b'user']
3184 statedata[b'user'] = user
3191 statedata[b'user'] = user
3185 date = ctx.date()
3192 date = ctx.date()
3186 if opts.get(b'date'):
3193 if opts.get(b'date'):
3187 date = opts[b'date']
3194 date = opts[b'date']
3188 statedata[b'date'] = date
3195 statedata[b'date'] = date
3189 message = ctx.description()
3196 message = ctx.description()
3190 if opts.get(b'log'):
3197 if opts.get(b'log'):
3191 message += b'\n(grafted from %s)' % ctx.hex()
3198 message += b'\n(grafted from %s)' % ctx.hex()
3192 statedata[b'log'] = True
3199 statedata[b'log'] = True
3193
3200
3194 # we don't merge the first commit when continuing
3201 # we don't merge the first commit when continuing
3195 if not cont:
3202 if not cont:
3196 # perform the graft merge with p1(rev) as 'ancestor'
3203 # perform the graft merge with p1(rev) as 'ancestor'
3197 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3204 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3198 base = ctx.p1() if basectx is None else basectx
3205 base = ctx.p1() if basectx is None else basectx
3199 with ui.configoverride(overrides, b'graft'):
3206 with ui.configoverride(overrides, b'graft'):
3200 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3207 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3201 # report any conflicts
3208 # report any conflicts
3202 if stats.unresolvedcount > 0:
3209 if stats.unresolvedcount > 0:
3203 # write out state for --continue
3210 # write out state for --continue
3204 nodes = [repo[rev].hex() for rev in revs[pos:]]
3211 nodes = [repo[rev].hex() for rev in revs[pos:]]
3205 statedata[b'nodes'] = nodes
3212 statedata[b'nodes'] = nodes
3206 stateversion = 1
3213 stateversion = 1
3207 graftstate.save(stateversion, statedata)
3214 graftstate.save(stateversion, statedata)
3208 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3215 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3209 raise error.Abort(
3216 raise error.Abort(
3210 _(b"unresolved conflicts, can't continue"), hint=hint
3217 _(b"unresolved conflicts, can't continue"), hint=hint
3211 )
3218 )
3212 else:
3219 else:
3213 cont = False
3220 cont = False
3214
3221
3215 # commit if --no-commit is false
3222 # commit if --no-commit is false
3216 if not opts.get(b'no_commit'):
3223 if not opts.get(b'no_commit'):
3217 node = repo.commit(
3224 node = repo.commit(
3218 text=message, user=user, date=date, extra=extra, editor=editor
3225 text=message, user=user, date=date, extra=extra, editor=editor
3219 )
3226 )
3220 if node is None:
3227 if node is None:
3221 ui.warn(
3228 ui.warn(
3222 _(b'note: graft of %d:%s created no changes to commit\n')
3229 _(b'note: graft of %d:%s created no changes to commit\n')
3223 % (ctx.rev(), ctx)
3230 % (ctx.rev(), ctx)
3224 )
3231 )
3225 # checking that newnodes exist because old state files won't have it
3232 # checking that newnodes exist because old state files won't have it
3226 elif statedata.get(b'newnodes') is not None:
3233 elif statedata.get(b'newnodes') is not None:
3227 statedata[b'newnodes'].append(node)
3234 statedata[b'newnodes'].append(node)
3228
3235
3229 # remove state when we complete successfully
3236 # remove state when we complete successfully
3230 if not opts.get(b'dry_run'):
3237 if not opts.get(b'dry_run'):
3231 graftstate.delete()
3238 graftstate.delete()
3232
3239
3233 return 0
3240 return 0
3234
3241
3235
3242
3236 def _stopgraft(ui, repo, graftstate):
3243 def _stopgraft(ui, repo, graftstate):
3237 """stop the interrupted graft"""
3244 """stop the interrupted graft"""
3238 if not graftstate.exists():
3245 if not graftstate.exists():
3239 raise error.Abort(_(b"no interrupted graft found"))
3246 raise error.Abort(_(b"no interrupted graft found"))
3240 pctx = repo[b'.']
3247 pctx = repo[b'.']
3241 hg.updaterepo(repo, pctx.node(), overwrite=True)
3248 hg.updaterepo(repo, pctx.node(), overwrite=True)
3242 graftstate.delete()
3249 graftstate.delete()
3243 ui.status(_(b"stopped the interrupted graft\n"))
3250 ui.status(_(b"stopped the interrupted graft\n"))
3244 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3251 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3245 return 0
3252 return 0
3246
3253
3247
3254
3248 statemod.addunfinished(
3255 statemod.addunfinished(
3249 b'graft',
3256 b'graft',
3250 fname=b'graftstate',
3257 fname=b'graftstate',
3251 clearable=True,
3258 clearable=True,
3252 stopflag=True,
3259 stopflag=True,
3253 continueflag=True,
3260 continueflag=True,
3254 abortfunc=cmdutil.hgabortgraft,
3261 abortfunc=cmdutil.hgabortgraft,
3255 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3262 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3256 )
3263 )
3257
3264
3258
3265
3259 @command(
3266 @command(
3260 b'grep',
3267 b'grep',
3261 [
3268 [
3262 (b'0', b'print0', None, _(b'end fields with NUL')),
3269 (b'0', b'print0', None, _(b'end fields with NUL')),
3263 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3270 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3264 (
3271 (
3265 b'',
3272 b'',
3266 b'diff',
3273 b'diff',
3267 None,
3274 None,
3268 _(
3275 _(
3269 b'search revision differences for when the pattern was added '
3276 b'search revision differences for when the pattern was added '
3270 b'or removed'
3277 b'or removed'
3271 ),
3278 ),
3272 ),
3279 ),
3273 (b'a', b'text', None, _(b'treat all files as text')),
3280 (b'a', b'text', None, _(b'treat all files as text')),
3274 (
3281 (
3275 b'f',
3282 b'f',
3276 b'follow',
3283 b'follow',
3277 None,
3284 None,
3278 _(
3285 _(
3279 b'follow changeset history,'
3286 b'follow changeset history,'
3280 b' or file history across copies and renames'
3287 b' or file history across copies and renames'
3281 ),
3288 ),
3282 ),
3289 ),
3283 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3290 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3284 (
3291 (
3285 b'l',
3292 b'l',
3286 b'files-with-matches',
3293 b'files-with-matches',
3287 None,
3294 None,
3288 _(b'print only filenames and revisions that match'),
3295 _(b'print only filenames and revisions that match'),
3289 ),
3296 ),
3290 (b'n', b'line-number', None, _(b'print matching line numbers')),
3297 (b'n', b'line-number', None, _(b'print matching line numbers')),
3291 (
3298 (
3292 b'r',
3299 b'r',
3293 b'rev',
3300 b'rev',
3294 [],
3301 [],
3295 _(b'search files changed within revision range'),
3302 _(b'search files changed within revision range'),
3296 _(b'REV'),
3303 _(b'REV'),
3297 ),
3304 ),
3298 (
3305 (
3299 b'',
3306 b'',
3300 b'all-files',
3307 b'all-files',
3301 None,
3308 None,
3302 _(
3309 _(
3303 b'include all files in the changeset while grepping (DEPRECATED)'
3310 b'include all files in the changeset while grepping (DEPRECATED)'
3304 ),
3311 ),
3305 ),
3312 ),
3306 (b'u', b'user', None, _(b'list the author (long with -v)')),
3313 (b'u', b'user', None, _(b'list the author (long with -v)')),
3307 (b'd', b'date', None, _(b'list the date (short with -q)')),
3314 (b'd', b'date', None, _(b'list the date (short with -q)')),
3308 ]
3315 ]
3309 + formatteropts
3316 + formatteropts
3310 + walkopts,
3317 + walkopts,
3311 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3318 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3312 helpcategory=command.CATEGORY_FILE_CONTENTS,
3319 helpcategory=command.CATEGORY_FILE_CONTENTS,
3313 inferrepo=True,
3320 inferrepo=True,
3314 intents={INTENT_READONLY},
3321 intents={INTENT_READONLY},
3315 )
3322 )
3316 def grep(ui, repo, pattern, *pats, **opts):
3323 def grep(ui, repo, pattern, *pats, **opts):
3317 """search for a pattern in specified files
3324 """search for a pattern in specified files
3318
3325
3319 Search the working directory or revision history for a regular
3326 Search the working directory or revision history for a regular
3320 expression in the specified files for the entire repository.
3327 expression in the specified files for the entire repository.
3321
3328
3322 By default, grep searches the repository files in the working
3329 By default, grep searches the repository files in the working
3323 directory and prints the files where it finds a match. To specify
3330 directory and prints the files where it finds a match. To specify
3324 historical revisions instead of the working directory, use the
3331 historical revisions instead of the working directory, use the
3325 --rev flag.
3332 --rev flag.
3326
3333
3327 To search instead historical revision differences that contains a
3334 To search instead historical revision differences that contains a
3328 change in match status ("-" for a match that becomes a non-match,
3335 change in match status ("-" for a match that becomes a non-match,
3329 or "+" for a non-match that becomes a match), use the --diff flag.
3336 or "+" for a non-match that becomes a match), use the --diff flag.
3330
3337
3331 PATTERN can be any Python (roughly Perl-compatible) regular
3338 PATTERN can be any Python (roughly Perl-compatible) regular
3332 expression.
3339 expression.
3333
3340
3334 If no FILEs are specified and the --rev flag isn't supplied, all
3341 If no FILEs are specified and the --rev flag isn't supplied, all
3335 files in the working directory are searched. When using the --rev
3342 files in the working directory are searched. When using the --rev
3336 flag and specifying FILEs, use the --follow argument to also
3343 flag and specifying FILEs, use the --follow argument to also
3337 follow the specified FILEs across renames and copies.
3344 follow the specified FILEs across renames and copies.
3338
3345
3339 .. container:: verbose
3346 .. container:: verbose
3340
3347
3341 Template:
3348 Template:
3342
3349
3343 The following keywords are supported in addition to the common template
3350 The following keywords are supported in addition to the common template
3344 keywords and functions. See also :hg:`help templates`.
3351 keywords and functions. See also :hg:`help templates`.
3345
3352
3346 :change: String. Character denoting insertion ``+`` or removal ``-``.
3353 :change: String. Character denoting insertion ``+`` or removal ``-``.
3347 Available if ``--diff`` is specified.
3354 Available if ``--diff`` is specified.
3348 :lineno: Integer. Line number of the match.
3355 :lineno: Integer. Line number of the match.
3349 :path: String. Repository-absolute path of the file.
3356 :path: String. Repository-absolute path of the file.
3350 :texts: List of text chunks.
3357 :texts: List of text chunks.
3351
3358
3352 And each entry of ``{texts}`` provides the following sub-keywords.
3359 And each entry of ``{texts}`` provides the following sub-keywords.
3353
3360
3354 :matched: Boolean. True if the chunk matches the specified pattern.
3361 :matched: Boolean. True if the chunk matches the specified pattern.
3355 :text: String. Chunk content.
3362 :text: String. Chunk content.
3356
3363
3357 See :hg:`help templates.operators` for the list expansion syntax.
3364 See :hg:`help templates.operators` for the list expansion syntax.
3358
3365
3359 Returns 0 if a match is found, 1 otherwise.
3366 Returns 0 if a match is found, 1 otherwise.
3360
3367
3361 """
3368 """
3362 opts = pycompat.byteskwargs(opts)
3369 opts = pycompat.byteskwargs(opts)
3363 diff = opts.get(b'all') or opts.get(b'diff')
3370 diff = opts.get(b'all') or opts.get(b'diff')
3364 if diff and opts.get(b'all_files'):
3371 if diff and opts.get(b'all_files'):
3365 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3372 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3366 if opts.get(b'all_files') is None and not diff:
3373 if opts.get(b'all_files') is None and not diff:
3367 opts[b'all_files'] = True
3374 opts[b'all_files'] = True
3368 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3375 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3369 all_files = opts.get(b'all_files')
3376 all_files = opts.get(b'all_files')
3370 if plaingrep:
3377 if plaingrep:
3371 opts[b'rev'] = [b'wdir()']
3378 opts[b'rev'] = [b'wdir()']
3372
3379
3373 reflags = re.M
3380 reflags = re.M
3374 if opts.get(b'ignore_case'):
3381 if opts.get(b'ignore_case'):
3375 reflags |= re.I
3382 reflags |= re.I
3376 try:
3383 try:
3377 regexp = util.re.compile(pattern, reflags)
3384 regexp = util.re.compile(pattern, reflags)
3378 except re.error as inst:
3385 except re.error as inst:
3379 ui.warn(
3386 ui.warn(
3380 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3387 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3381 )
3388 )
3382 return 1
3389 return 1
3383 sep, eol = b':', b'\n'
3390 sep, eol = b':', b'\n'
3384 if opts.get(b'print0'):
3391 if opts.get(b'print0'):
3385 sep = eol = b'\0'
3392 sep = eol = b'\0'
3386
3393
3387 getfile = util.lrucachefunc(repo.file)
3394 getfile = util.lrucachefunc(repo.file)
3388
3395
3389 def matchlines(body):
3396 def matchlines(body):
3390 begin = 0
3397 begin = 0
3391 linenum = 0
3398 linenum = 0
3392 while begin < len(body):
3399 while begin < len(body):
3393 match = regexp.search(body, begin)
3400 match = regexp.search(body, begin)
3394 if not match:
3401 if not match:
3395 break
3402 break
3396 mstart, mend = match.span()
3403 mstart, mend = match.span()
3397 linenum += body.count(b'\n', begin, mstart) + 1
3404 linenum += body.count(b'\n', begin, mstart) + 1
3398 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3405 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3399 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3406 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3400 lend = begin - 1
3407 lend = begin - 1
3401 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3408 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3402
3409
3403 class linestate(object):
3410 class linestate(object):
3404 def __init__(self, line, linenum, colstart, colend):
3411 def __init__(self, line, linenum, colstart, colend):
3405 self.line = line
3412 self.line = line
3406 self.linenum = linenum
3413 self.linenum = linenum
3407 self.colstart = colstart
3414 self.colstart = colstart
3408 self.colend = colend
3415 self.colend = colend
3409
3416
3410 def __hash__(self):
3417 def __hash__(self):
3411 return hash((self.linenum, self.line))
3418 return hash((self.linenum, self.line))
3412
3419
3413 def __eq__(self, other):
3420 def __eq__(self, other):
3414 return self.line == other.line
3421 return self.line == other.line
3415
3422
3416 def findpos(self):
3423 def findpos(self):
3417 """Iterate all (start, end) indices of matches"""
3424 """Iterate all (start, end) indices of matches"""
3418 yield self.colstart, self.colend
3425 yield self.colstart, self.colend
3419 p = self.colend
3426 p = self.colend
3420 while p < len(self.line):
3427 while p < len(self.line):
3421 m = regexp.search(self.line, p)
3428 m = regexp.search(self.line, p)
3422 if not m:
3429 if not m:
3423 break
3430 break
3424 yield m.span()
3431 yield m.span()
3425 p = m.end()
3432 p = m.end()
3426
3433
3427 matches = {}
3434 matches = {}
3428 copies = {}
3435 copies = {}
3429
3436
3430 def grepbody(fn, rev, body):
3437 def grepbody(fn, rev, body):
3431 matches[rev].setdefault(fn, [])
3438 matches[rev].setdefault(fn, [])
3432 m = matches[rev][fn]
3439 m = matches[rev][fn]
3433 if body is None:
3440 if body is None:
3434 return
3441 return
3435
3442
3436 for lnum, cstart, cend, line in matchlines(body):
3443 for lnum, cstart, cend, line in matchlines(body):
3437 s = linestate(line, lnum, cstart, cend)
3444 s = linestate(line, lnum, cstart, cend)
3438 m.append(s)
3445 m.append(s)
3439
3446
3440 def difflinestates(a, b):
3447 def difflinestates(a, b):
3441 sm = difflib.SequenceMatcher(None, a, b)
3448 sm = difflib.SequenceMatcher(None, a, b)
3442 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3449 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3443 if tag == 'insert':
3450 if tag == 'insert':
3444 for i in pycompat.xrange(blo, bhi):
3451 for i in pycompat.xrange(blo, bhi):
3445 yield (b'+', b[i])
3452 yield (b'+', b[i])
3446 elif tag == 'delete':
3453 elif tag == 'delete':
3447 for i in pycompat.xrange(alo, ahi):
3454 for i in pycompat.xrange(alo, ahi):
3448 yield (b'-', a[i])
3455 yield (b'-', a[i])
3449 elif tag == 'replace':
3456 elif tag == 'replace':
3450 for i in pycompat.xrange(alo, ahi):
3457 for i in pycompat.xrange(alo, ahi):
3451 yield (b'-', a[i])
3458 yield (b'-', a[i])
3452 for i in pycompat.xrange(blo, bhi):
3459 for i in pycompat.xrange(blo, bhi):
3453 yield (b'+', b[i])
3460 yield (b'+', b[i])
3454
3461
3455 uipathfn = scmutil.getuipathfn(repo)
3462 uipathfn = scmutil.getuipathfn(repo)
3456
3463
3457 def display(fm, fn, ctx, pstates, states):
3464 def display(fm, fn, ctx, pstates, states):
3458 rev = scmutil.intrev(ctx)
3465 rev = scmutil.intrev(ctx)
3459 if fm.isplain():
3466 if fm.isplain():
3460 formatuser = ui.shortuser
3467 formatuser = ui.shortuser
3461 else:
3468 else:
3462 formatuser = pycompat.bytestr
3469 formatuser = pycompat.bytestr
3463 if ui.quiet:
3470 if ui.quiet:
3464 datefmt = b'%Y-%m-%d'
3471 datefmt = b'%Y-%m-%d'
3465 else:
3472 else:
3466 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3473 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3467 found = False
3474 found = False
3468
3475
3469 @util.cachefunc
3476 @util.cachefunc
3470 def binary():
3477 def binary():
3471 flog = getfile(fn)
3478 flog = getfile(fn)
3472 try:
3479 try:
3473 return stringutil.binary(flog.read(ctx.filenode(fn)))
3480 return stringutil.binary(flog.read(ctx.filenode(fn)))
3474 except error.WdirUnsupported:
3481 except error.WdirUnsupported:
3475 return ctx[fn].isbinary()
3482 return ctx[fn].isbinary()
3476
3483
3477 fieldnamemap = {b'linenumber': b'lineno'}
3484 fieldnamemap = {b'linenumber': b'lineno'}
3478 if diff:
3485 if diff:
3479 iter = difflinestates(pstates, states)
3486 iter = difflinestates(pstates, states)
3480 else:
3487 else:
3481 iter = [(b'', l) for l in states]
3488 iter = [(b'', l) for l in states]
3482 for change, l in iter:
3489 for change, l in iter:
3483 fm.startitem()
3490 fm.startitem()
3484 fm.context(ctx=ctx)
3491 fm.context(ctx=ctx)
3485 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3492 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3486 fm.plain(uipathfn(fn), label=b'grep.filename')
3493 fm.plain(uipathfn(fn), label=b'grep.filename')
3487
3494
3488 cols = [
3495 cols = [
3489 (b'rev', b'%d', rev, not plaingrep, b''),
3496 (b'rev', b'%d', rev, not plaingrep, b''),
3490 (
3497 (
3491 b'linenumber',
3498 b'linenumber',
3492 b'%d',
3499 b'%d',
3493 l.linenum,
3500 l.linenum,
3494 opts.get(b'line_number'),
3501 opts.get(b'line_number'),
3495 b'',
3502 b'',
3496 ),
3503 ),
3497 ]
3504 ]
3498 if diff:
3505 if diff:
3499 cols.append(
3506 cols.append(
3500 (
3507 (
3501 b'change',
3508 b'change',
3502 b'%s',
3509 b'%s',
3503 change,
3510 change,
3504 True,
3511 True,
3505 b'grep.inserted '
3512 b'grep.inserted '
3506 if change == b'+'
3513 if change == b'+'
3507 else b'grep.deleted ',
3514 else b'grep.deleted ',
3508 )
3515 )
3509 )
3516 )
3510 cols.extend(
3517 cols.extend(
3511 [
3518 [
3512 (
3519 (
3513 b'user',
3520 b'user',
3514 b'%s',
3521 b'%s',
3515 formatuser(ctx.user()),
3522 formatuser(ctx.user()),
3516 opts.get(b'user'),
3523 opts.get(b'user'),
3517 b'',
3524 b'',
3518 ),
3525 ),
3519 (
3526 (
3520 b'date',
3527 b'date',
3521 b'%s',
3528 b'%s',
3522 fm.formatdate(ctx.date(), datefmt),
3529 fm.formatdate(ctx.date(), datefmt),
3523 opts.get(b'date'),
3530 opts.get(b'date'),
3524 b'',
3531 b'',
3525 ),
3532 ),
3526 ]
3533 ]
3527 )
3534 )
3528 for name, fmt, data, cond, extra_label in cols:
3535 for name, fmt, data, cond, extra_label in cols:
3529 if cond:
3536 if cond:
3530 fm.plain(sep, label=b'grep.sep')
3537 fm.plain(sep, label=b'grep.sep')
3531 field = fieldnamemap.get(name, name)
3538 field = fieldnamemap.get(name, name)
3532 label = extra_label + (b'grep.%s' % name)
3539 label = extra_label + (b'grep.%s' % name)
3533 fm.condwrite(cond, field, fmt, data, label=label)
3540 fm.condwrite(cond, field, fmt, data, label=label)
3534 if not opts.get(b'files_with_matches'):
3541 if not opts.get(b'files_with_matches'):
3535 fm.plain(sep, label=b'grep.sep')
3542 fm.plain(sep, label=b'grep.sep')
3536 if not opts.get(b'text') and binary():
3543 if not opts.get(b'text') and binary():
3537 fm.plain(_(b" Binary file matches"))
3544 fm.plain(_(b" Binary file matches"))
3538 else:
3545 else:
3539 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3546 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3540 fm.plain(eol)
3547 fm.plain(eol)
3541 found = True
3548 found = True
3542 if opts.get(b'files_with_matches'):
3549 if opts.get(b'files_with_matches'):
3543 break
3550 break
3544 return found
3551 return found
3545
3552
3546 def displaymatches(fm, l):
3553 def displaymatches(fm, l):
3547 p = 0
3554 p = 0
3548 for s, e in l.findpos():
3555 for s, e in l.findpos():
3549 if p < s:
3556 if p < s:
3550 fm.startitem()
3557 fm.startitem()
3551 fm.write(b'text', b'%s', l.line[p:s])
3558 fm.write(b'text', b'%s', l.line[p:s])
3552 fm.data(matched=False)
3559 fm.data(matched=False)
3553 fm.startitem()
3560 fm.startitem()
3554 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3561 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3555 fm.data(matched=True)
3562 fm.data(matched=True)
3556 p = e
3563 p = e
3557 if p < len(l.line):
3564 if p < len(l.line):
3558 fm.startitem()
3565 fm.startitem()
3559 fm.write(b'text', b'%s', l.line[p:])
3566 fm.write(b'text', b'%s', l.line[p:])
3560 fm.data(matched=False)
3567 fm.data(matched=False)
3561 fm.end()
3568 fm.end()
3562
3569
3563 skip = set()
3570 skip = set()
3564 revfiles = {}
3571 revfiles = {}
3565 match = scmutil.match(repo[None], pats, opts)
3572 match = scmutil.match(repo[None], pats, opts)
3566 found = False
3573 found = False
3567 follow = opts.get(b'follow')
3574 follow = opts.get(b'follow')
3568
3575
3569 getrenamed = scmutil.getrenamedfn(repo)
3576 getrenamed = scmutil.getrenamedfn(repo)
3570
3577
3571 def get_file_content(filename, filelog, filenode, context, revision):
3578 def get_file_content(filename, filelog, filenode, context, revision):
3572 try:
3579 try:
3573 content = filelog.read(filenode)
3580 content = filelog.read(filenode)
3574 except error.WdirUnsupported:
3581 except error.WdirUnsupported:
3575 content = context[filename].data()
3582 content = context[filename].data()
3576 except error.CensoredNodeError:
3583 except error.CensoredNodeError:
3577 content = None
3584 content = None
3578 ui.warn(
3585 ui.warn(
3579 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3586 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3580 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3587 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3581 )
3588 )
3582 return content
3589 return content
3583
3590
3584 def prep(ctx, fns):
3591 def prep(ctx, fns):
3585 rev = ctx.rev()
3592 rev = ctx.rev()
3586 pctx = ctx.p1()
3593 pctx = ctx.p1()
3587 parent = pctx.rev()
3594 parent = pctx.rev()
3588 matches.setdefault(rev, {})
3595 matches.setdefault(rev, {})
3589 matches.setdefault(parent, {})
3596 matches.setdefault(parent, {})
3590 files = revfiles.setdefault(rev, [])
3597 files = revfiles.setdefault(rev, [])
3591 for fn in fns:
3598 for fn in fns:
3592 flog = getfile(fn)
3599 flog = getfile(fn)
3593 try:
3600 try:
3594 fnode = ctx.filenode(fn)
3601 fnode = ctx.filenode(fn)
3595 except error.LookupError:
3602 except error.LookupError:
3596 continue
3603 continue
3597
3604
3598 copy = None
3605 copy = None
3599 if follow:
3606 if follow:
3600 copy = getrenamed(fn, rev)
3607 copy = getrenamed(fn, rev)
3601 if copy:
3608 if copy:
3602 copies.setdefault(rev, {})[fn] = copy
3609 copies.setdefault(rev, {})[fn] = copy
3603 if fn in skip:
3610 if fn in skip:
3604 skip.add(copy)
3611 skip.add(copy)
3605 if fn in skip:
3612 if fn in skip:
3606 continue
3613 continue
3607 files.append(fn)
3614 files.append(fn)
3608
3615
3609 if fn not in matches[rev]:
3616 if fn not in matches[rev]:
3610 content = get_file_content(fn, flog, fnode, ctx, rev)
3617 content = get_file_content(fn, flog, fnode, ctx, rev)
3611 grepbody(fn, rev, content)
3618 grepbody(fn, rev, content)
3612
3619
3613 pfn = copy or fn
3620 pfn = copy or fn
3614 if pfn not in matches[parent]:
3621 if pfn not in matches[parent]:
3615 try:
3622 try:
3616 pfnode = pctx.filenode(pfn)
3623 pfnode = pctx.filenode(pfn)
3617 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3624 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3618 grepbody(pfn, parent, pcontent)
3625 grepbody(pfn, parent, pcontent)
3619 except error.LookupError:
3626 except error.LookupError:
3620 pass
3627 pass
3621
3628
3622 ui.pager(b'grep')
3629 ui.pager(b'grep')
3623 fm = ui.formatter(b'grep', opts)
3630 fm = ui.formatter(b'grep', opts)
3624 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3631 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3625 rev = ctx.rev()
3632 rev = ctx.rev()
3626 parent = ctx.p1().rev()
3633 parent = ctx.p1().rev()
3627 for fn in sorted(revfiles.get(rev, [])):
3634 for fn in sorted(revfiles.get(rev, [])):
3628 states = matches[rev][fn]
3635 states = matches[rev][fn]
3629 copy = copies.get(rev, {}).get(fn)
3636 copy = copies.get(rev, {}).get(fn)
3630 if fn in skip:
3637 if fn in skip:
3631 if copy:
3638 if copy:
3632 skip.add(copy)
3639 skip.add(copy)
3633 continue
3640 continue
3634 pstates = matches.get(parent, {}).get(copy or fn, [])
3641 pstates = matches.get(parent, {}).get(copy or fn, [])
3635 if pstates or states:
3642 if pstates or states:
3636 r = display(fm, fn, ctx, pstates, states)
3643 r = display(fm, fn, ctx, pstates, states)
3637 found = found or r
3644 found = found or r
3638 if r and not diff and not all_files:
3645 if r and not diff and not all_files:
3639 skip.add(fn)
3646 skip.add(fn)
3640 if copy:
3647 if copy:
3641 skip.add(copy)
3648 skip.add(copy)
3642 del revfiles[rev]
3649 del revfiles[rev]
3643 # We will keep the matches dict for the duration of the window
3650 # We will keep the matches dict for the duration of the window
3644 # clear the matches dict once the window is over
3651 # clear the matches dict once the window is over
3645 if not revfiles:
3652 if not revfiles:
3646 matches.clear()
3653 matches.clear()
3647 fm.end()
3654 fm.end()
3648
3655
3649 return not found
3656 return not found
3650
3657
3651
3658
3652 @command(
3659 @command(
3653 b'heads',
3660 b'heads',
3654 [
3661 [
3655 (
3662 (
3656 b'r',
3663 b'r',
3657 b'rev',
3664 b'rev',
3658 b'',
3665 b'',
3659 _(b'show only heads which are descendants of STARTREV'),
3666 _(b'show only heads which are descendants of STARTREV'),
3660 _(b'STARTREV'),
3667 _(b'STARTREV'),
3661 ),
3668 ),
3662 (b't', b'topo', False, _(b'show topological heads only')),
3669 (b't', b'topo', False, _(b'show topological heads only')),
3663 (
3670 (
3664 b'a',
3671 b'a',
3665 b'active',
3672 b'active',
3666 False,
3673 False,
3667 _(b'show active branchheads only (DEPRECATED)'),
3674 _(b'show active branchheads only (DEPRECATED)'),
3668 ),
3675 ),
3669 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3676 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3670 ]
3677 ]
3671 + templateopts,
3678 + templateopts,
3672 _(b'[-ct] [-r STARTREV] [REV]...'),
3679 _(b'[-ct] [-r STARTREV] [REV]...'),
3673 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3680 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3674 intents={INTENT_READONLY},
3681 intents={INTENT_READONLY},
3675 )
3682 )
3676 def heads(ui, repo, *branchrevs, **opts):
3683 def heads(ui, repo, *branchrevs, **opts):
3677 """show branch heads
3684 """show branch heads
3678
3685
3679 With no arguments, show all open branch heads in the repository.
3686 With no arguments, show all open branch heads in the repository.
3680 Branch heads are changesets that have no descendants on the
3687 Branch heads are changesets that have no descendants on the
3681 same branch. They are where development generally takes place and
3688 same branch. They are where development generally takes place and
3682 are the usual targets for update and merge operations.
3689 are the usual targets for update and merge operations.
3683
3690
3684 If one or more REVs are given, only open branch heads on the
3691 If one or more REVs are given, only open branch heads on the
3685 branches associated with the specified changesets are shown. This
3692 branches associated with the specified changesets are shown. This
3686 means that you can use :hg:`heads .` to see the heads on the
3693 means that you can use :hg:`heads .` to see the heads on the
3687 currently checked-out branch.
3694 currently checked-out branch.
3688
3695
3689 If -c/--closed is specified, also show branch heads marked closed
3696 If -c/--closed is specified, also show branch heads marked closed
3690 (see :hg:`commit --close-branch`).
3697 (see :hg:`commit --close-branch`).
3691
3698
3692 If STARTREV is specified, only those heads that are descendants of
3699 If STARTREV is specified, only those heads that are descendants of
3693 STARTREV will be displayed.
3700 STARTREV will be displayed.
3694
3701
3695 If -t/--topo is specified, named branch mechanics will be ignored and only
3702 If -t/--topo is specified, named branch mechanics will be ignored and only
3696 topological heads (changesets with no children) will be shown.
3703 topological heads (changesets with no children) will be shown.
3697
3704
3698 Returns 0 if matching heads are found, 1 if not.
3705 Returns 0 if matching heads are found, 1 if not.
3699 """
3706 """
3700
3707
3701 opts = pycompat.byteskwargs(opts)
3708 opts = pycompat.byteskwargs(opts)
3702 start = None
3709 start = None
3703 rev = opts.get(b'rev')
3710 rev = opts.get(b'rev')
3704 if rev:
3711 if rev:
3705 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3712 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3706 start = scmutil.revsingle(repo, rev, None).node()
3713 start = scmutil.revsingle(repo, rev, None).node()
3707
3714
3708 if opts.get(b'topo'):
3715 if opts.get(b'topo'):
3709 heads = [repo[h] for h in repo.heads(start)]
3716 heads = [repo[h] for h in repo.heads(start)]
3710 else:
3717 else:
3711 heads = []
3718 heads = []
3712 for branch in repo.branchmap():
3719 for branch in repo.branchmap():
3713 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3720 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3714 heads = [repo[h] for h in heads]
3721 heads = [repo[h] for h in heads]
3715
3722
3716 if branchrevs:
3723 if branchrevs:
3717 branches = set(
3724 branches = set(
3718 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3725 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3719 )
3726 )
3720 heads = [h for h in heads if h.branch() in branches]
3727 heads = [h for h in heads if h.branch() in branches]
3721
3728
3722 if opts.get(b'active') and branchrevs:
3729 if opts.get(b'active') and branchrevs:
3723 dagheads = repo.heads(start)
3730 dagheads = repo.heads(start)
3724 heads = [h for h in heads if h.node() in dagheads]
3731 heads = [h for h in heads if h.node() in dagheads]
3725
3732
3726 if branchrevs:
3733 if branchrevs:
3727 haveheads = set(h.branch() for h in heads)
3734 haveheads = set(h.branch() for h in heads)
3728 if branches - haveheads:
3735 if branches - haveheads:
3729 headless = b', '.join(b for b in branches - haveheads)
3736 headless = b', '.join(b for b in branches - haveheads)
3730 msg = _(b'no open branch heads found on branches %s')
3737 msg = _(b'no open branch heads found on branches %s')
3731 if opts.get(b'rev'):
3738 if opts.get(b'rev'):
3732 msg += _(b' (started at %s)') % opts[b'rev']
3739 msg += _(b' (started at %s)') % opts[b'rev']
3733 ui.warn((msg + b'\n') % headless)
3740 ui.warn((msg + b'\n') % headless)
3734
3741
3735 if not heads:
3742 if not heads:
3736 return 1
3743 return 1
3737
3744
3738 ui.pager(b'heads')
3745 ui.pager(b'heads')
3739 heads = sorted(heads, key=lambda x: -(x.rev()))
3746 heads = sorted(heads, key=lambda x: -(x.rev()))
3740 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3747 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3741 for ctx in heads:
3748 for ctx in heads:
3742 displayer.show(ctx)
3749 displayer.show(ctx)
3743 displayer.close()
3750 displayer.close()
3744
3751
3745
3752
3746 @command(
3753 @command(
3747 b'help',
3754 b'help',
3748 [
3755 [
3749 (b'e', b'extension', None, _(b'show only help for extensions')),
3756 (b'e', b'extension', None, _(b'show only help for extensions')),
3750 (b'c', b'command', None, _(b'show only help for commands')),
3757 (b'c', b'command', None, _(b'show only help for commands')),
3751 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3758 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3752 (
3759 (
3753 b's',
3760 b's',
3754 b'system',
3761 b'system',
3755 [],
3762 [],
3756 _(b'show help for specific platform(s)'),
3763 _(b'show help for specific platform(s)'),
3757 _(b'PLATFORM'),
3764 _(b'PLATFORM'),
3758 ),
3765 ),
3759 ],
3766 ],
3760 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3767 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3761 helpcategory=command.CATEGORY_HELP,
3768 helpcategory=command.CATEGORY_HELP,
3762 norepo=True,
3769 norepo=True,
3763 intents={INTENT_READONLY},
3770 intents={INTENT_READONLY},
3764 )
3771 )
3765 def help_(ui, name=None, **opts):
3772 def help_(ui, name=None, **opts):
3766 """show help for a given topic or a help overview
3773 """show help for a given topic or a help overview
3767
3774
3768 With no arguments, print a list of commands with short help messages.
3775 With no arguments, print a list of commands with short help messages.
3769
3776
3770 Given a topic, extension, or command name, print help for that
3777 Given a topic, extension, or command name, print help for that
3771 topic.
3778 topic.
3772
3779
3773 Returns 0 if successful.
3780 Returns 0 if successful.
3774 """
3781 """
3775
3782
3776 keep = opts.get('system') or []
3783 keep = opts.get('system') or []
3777 if len(keep) == 0:
3784 if len(keep) == 0:
3778 if pycompat.sysplatform.startswith(b'win'):
3785 if pycompat.sysplatform.startswith(b'win'):
3779 keep.append(b'windows')
3786 keep.append(b'windows')
3780 elif pycompat.sysplatform == b'OpenVMS':
3787 elif pycompat.sysplatform == b'OpenVMS':
3781 keep.append(b'vms')
3788 keep.append(b'vms')
3782 elif pycompat.sysplatform == b'plan9':
3789 elif pycompat.sysplatform == b'plan9':
3783 keep.append(b'plan9')
3790 keep.append(b'plan9')
3784 else:
3791 else:
3785 keep.append(b'unix')
3792 keep.append(b'unix')
3786 keep.append(pycompat.sysplatform.lower())
3793 keep.append(pycompat.sysplatform.lower())
3787 if ui.verbose:
3794 if ui.verbose:
3788 keep.append(b'verbose')
3795 keep.append(b'verbose')
3789
3796
3790 commands = sys.modules[__name__]
3797 commands = sys.modules[__name__]
3791 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3798 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3792 ui.pager(b'help')
3799 ui.pager(b'help')
3793 ui.write(formatted)
3800 ui.write(formatted)
3794
3801
3795
3802
3796 @command(
3803 @command(
3797 b'identify|id',
3804 b'identify|id',
3798 [
3805 [
3799 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3806 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3800 (b'n', b'num', None, _(b'show local revision number')),
3807 (b'n', b'num', None, _(b'show local revision number')),
3801 (b'i', b'id', None, _(b'show global revision id')),
3808 (b'i', b'id', None, _(b'show global revision id')),
3802 (b'b', b'branch', None, _(b'show branch')),
3809 (b'b', b'branch', None, _(b'show branch')),
3803 (b't', b'tags', None, _(b'show tags')),
3810 (b't', b'tags', None, _(b'show tags')),
3804 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3811 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3805 ]
3812 ]
3806 + remoteopts
3813 + remoteopts
3807 + formatteropts,
3814 + formatteropts,
3808 _(b'[-nibtB] [-r REV] [SOURCE]'),
3815 _(b'[-nibtB] [-r REV] [SOURCE]'),
3809 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3816 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3810 optionalrepo=True,
3817 optionalrepo=True,
3811 intents={INTENT_READONLY},
3818 intents={INTENT_READONLY},
3812 )
3819 )
3813 def identify(
3820 def identify(
3814 ui,
3821 ui,
3815 repo,
3822 repo,
3816 source=None,
3823 source=None,
3817 rev=None,
3824 rev=None,
3818 num=None,
3825 num=None,
3819 id=None,
3826 id=None,
3820 branch=None,
3827 branch=None,
3821 tags=None,
3828 tags=None,
3822 bookmarks=None,
3829 bookmarks=None,
3823 **opts
3830 **opts
3824 ):
3831 ):
3825 """identify the working directory or specified revision
3832 """identify the working directory or specified revision
3826
3833
3827 Print a summary identifying the repository state at REV using one or
3834 Print a summary identifying the repository state at REV using one or
3828 two parent hash identifiers, followed by a "+" if the working
3835 two parent hash identifiers, followed by a "+" if the working
3829 directory has uncommitted changes, the branch name (if not default),
3836 directory has uncommitted changes, the branch name (if not default),
3830 a list of tags, and a list of bookmarks.
3837 a list of tags, and a list of bookmarks.
3831
3838
3832 When REV is not given, print a summary of the current state of the
3839 When REV is not given, print a summary of the current state of the
3833 repository including the working directory. Specify -r. to get information
3840 repository including the working directory. Specify -r. to get information
3834 of the working directory parent without scanning uncommitted changes.
3841 of the working directory parent without scanning uncommitted changes.
3835
3842
3836 Specifying a path to a repository root or Mercurial bundle will
3843 Specifying a path to a repository root or Mercurial bundle will
3837 cause lookup to operate on that repository/bundle.
3844 cause lookup to operate on that repository/bundle.
3838
3845
3839 .. container:: verbose
3846 .. container:: verbose
3840
3847
3841 Template:
3848 Template:
3842
3849
3843 The following keywords are supported in addition to the common template
3850 The following keywords are supported in addition to the common template
3844 keywords and functions. See also :hg:`help templates`.
3851 keywords and functions. See also :hg:`help templates`.
3845
3852
3846 :dirty: String. Character ``+`` denoting if the working directory has
3853 :dirty: String. Character ``+`` denoting if the working directory has
3847 uncommitted changes.
3854 uncommitted changes.
3848 :id: String. One or two nodes, optionally followed by ``+``.
3855 :id: String. One or two nodes, optionally followed by ``+``.
3849 :parents: List of strings. Parent nodes of the changeset.
3856 :parents: List of strings. Parent nodes of the changeset.
3850
3857
3851 Examples:
3858 Examples:
3852
3859
3853 - generate a build identifier for the working directory::
3860 - generate a build identifier for the working directory::
3854
3861
3855 hg id --id > build-id.dat
3862 hg id --id > build-id.dat
3856
3863
3857 - find the revision corresponding to a tag::
3864 - find the revision corresponding to a tag::
3858
3865
3859 hg id -n -r 1.3
3866 hg id -n -r 1.3
3860
3867
3861 - check the most recent revision of a remote repository::
3868 - check the most recent revision of a remote repository::
3862
3869
3863 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3870 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3864
3871
3865 See :hg:`log` for generating more information about specific revisions,
3872 See :hg:`log` for generating more information about specific revisions,
3866 including full hash identifiers.
3873 including full hash identifiers.
3867
3874
3868 Returns 0 if successful.
3875 Returns 0 if successful.
3869 """
3876 """
3870
3877
3871 opts = pycompat.byteskwargs(opts)
3878 opts = pycompat.byteskwargs(opts)
3872 if not repo and not source:
3879 if not repo and not source:
3873 raise error.Abort(
3880 raise error.Abort(
3874 _(b"there is no Mercurial repository here (.hg not found)")
3881 _(b"there is no Mercurial repository here (.hg not found)")
3875 )
3882 )
3876
3883
3877 default = not (num or id or branch or tags or bookmarks)
3884 default = not (num or id or branch or tags or bookmarks)
3878 output = []
3885 output = []
3879 revs = []
3886 revs = []
3880
3887
3881 if source:
3888 if source:
3882 source, branches = hg.parseurl(ui.expandpath(source))
3889 source, branches = hg.parseurl(ui.expandpath(source))
3883 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3890 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3884 repo = peer.local()
3891 repo = peer.local()
3885 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3892 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3886
3893
3887 fm = ui.formatter(b'identify', opts)
3894 fm = ui.formatter(b'identify', opts)
3888 fm.startitem()
3895 fm.startitem()
3889
3896
3890 if not repo:
3897 if not repo:
3891 if num or branch or tags:
3898 if num or branch or tags:
3892 raise error.Abort(
3899 raise error.Abort(
3893 _(b"can't query remote revision number, branch, or tags")
3900 _(b"can't query remote revision number, branch, or tags")
3894 )
3901 )
3895 if not rev and revs:
3902 if not rev and revs:
3896 rev = revs[0]
3903 rev = revs[0]
3897 if not rev:
3904 if not rev:
3898 rev = b"tip"
3905 rev = b"tip"
3899
3906
3900 remoterev = peer.lookup(rev)
3907 remoterev = peer.lookup(rev)
3901 hexrev = fm.hexfunc(remoterev)
3908 hexrev = fm.hexfunc(remoterev)
3902 if default or id:
3909 if default or id:
3903 output = [hexrev]
3910 output = [hexrev]
3904 fm.data(id=hexrev)
3911 fm.data(id=hexrev)
3905
3912
3906 @util.cachefunc
3913 @util.cachefunc
3907 def getbms():
3914 def getbms():
3908 bms = []
3915 bms = []
3909
3916
3910 if b'bookmarks' in peer.listkeys(b'namespaces'):
3917 if b'bookmarks' in peer.listkeys(b'namespaces'):
3911 hexremoterev = hex(remoterev)
3918 hexremoterev = hex(remoterev)
3912 bms = [
3919 bms = [
3913 bm
3920 bm
3914 for bm, bmr in pycompat.iteritems(
3921 for bm, bmr in pycompat.iteritems(
3915 peer.listkeys(b'bookmarks')
3922 peer.listkeys(b'bookmarks')
3916 )
3923 )
3917 if bmr == hexremoterev
3924 if bmr == hexremoterev
3918 ]
3925 ]
3919
3926
3920 return sorted(bms)
3927 return sorted(bms)
3921
3928
3922 if fm.isplain():
3929 if fm.isplain():
3923 if bookmarks:
3930 if bookmarks:
3924 output.extend(getbms())
3931 output.extend(getbms())
3925 elif default and not ui.quiet:
3932 elif default and not ui.quiet:
3926 # multiple bookmarks for a single parent separated by '/'
3933 # multiple bookmarks for a single parent separated by '/'
3927 bm = b'/'.join(getbms())
3934 bm = b'/'.join(getbms())
3928 if bm:
3935 if bm:
3929 output.append(bm)
3936 output.append(bm)
3930 else:
3937 else:
3931 fm.data(node=hex(remoterev))
3938 fm.data(node=hex(remoterev))
3932 if bookmarks or b'bookmarks' in fm.datahint():
3939 if bookmarks or b'bookmarks' in fm.datahint():
3933 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3940 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3934 else:
3941 else:
3935 if rev:
3942 if rev:
3936 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3943 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3937 ctx = scmutil.revsingle(repo, rev, None)
3944 ctx = scmutil.revsingle(repo, rev, None)
3938
3945
3939 if ctx.rev() is None:
3946 if ctx.rev() is None:
3940 ctx = repo[None]
3947 ctx = repo[None]
3941 parents = ctx.parents()
3948 parents = ctx.parents()
3942 taglist = []
3949 taglist = []
3943 for p in parents:
3950 for p in parents:
3944 taglist.extend(p.tags())
3951 taglist.extend(p.tags())
3945
3952
3946 dirty = b""
3953 dirty = b""
3947 if ctx.dirty(missing=True, merge=False, branch=False):
3954 if ctx.dirty(missing=True, merge=False, branch=False):
3948 dirty = b'+'
3955 dirty = b'+'
3949 fm.data(dirty=dirty)
3956 fm.data(dirty=dirty)
3950
3957
3951 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3958 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3952 if default or id:
3959 if default or id:
3953 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3960 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3954 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3961 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3955
3962
3956 if num:
3963 if num:
3957 numoutput = [b"%d" % p.rev() for p in parents]
3964 numoutput = [b"%d" % p.rev() for p in parents]
3958 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3965 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3959
3966
3960 fm.data(
3967 fm.data(
3961 parents=fm.formatlist(
3968 parents=fm.formatlist(
3962 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3969 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3963 )
3970 )
3964 )
3971 )
3965 else:
3972 else:
3966 hexoutput = fm.hexfunc(ctx.node())
3973 hexoutput = fm.hexfunc(ctx.node())
3967 if default or id:
3974 if default or id:
3968 output = [hexoutput]
3975 output = [hexoutput]
3969 fm.data(id=hexoutput)
3976 fm.data(id=hexoutput)
3970
3977
3971 if num:
3978 if num:
3972 output.append(pycompat.bytestr(ctx.rev()))
3979 output.append(pycompat.bytestr(ctx.rev()))
3973 taglist = ctx.tags()
3980 taglist = ctx.tags()
3974
3981
3975 if default and not ui.quiet:
3982 if default and not ui.quiet:
3976 b = ctx.branch()
3983 b = ctx.branch()
3977 if b != b'default':
3984 if b != b'default':
3978 output.append(b"(%s)" % b)
3985 output.append(b"(%s)" % b)
3979
3986
3980 # multiple tags for a single parent separated by '/'
3987 # multiple tags for a single parent separated by '/'
3981 t = b'/'.join(taglist)
3988 t = b'/'.join(taglist)
3982 if t:
3989 if t:
3983 output.append(t)
3990 output.append(t)
3984
3991
3985 # multiple bookmarks for a single parent separated by '/'
3992 # multiple bookmarks for a single parent separated by '/'
3986 bm = b'/'.join(ctx.bookmarks())
3993 bm = b'/'.join(ctx.bookmarks())
3987 if bm:
3994 if bm:
3988 output.append(bm)
3995 output.append(bm)
3989 else:
3996 else:
3990 if branch:
3997 if branch:
3991 output.append(ctx.branch())
3998 output.append(ctx.branch())
3992
3999
3993 if tags:
4000 if tags:
3994 output.extend(taglist)
4001 output.extend(taglist)
3995
4002
3996 if bookmarks:
4003 if bookmarks:
3997 output.extend(ctx.bookmarks())
4004 output.extend(ctx.bookmarks())
3998
4005
3999 fm.data(node=ctx.hex())
4006 fm.data(node=ctx.hex())
4000 fm.data(branch=ctx.branch())
4007 fm.data(branch=ctx.branch())
4001 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4008 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4002 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4009 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4003 fm.context(ctx=ctx)
4010 fm.context(ctx=ctx)
4004
4011
4005 fm.plain(b"%s\n" % b' '.join(output))
4012 fm.plain(b"%s\n" % b' '.join(output))
4006 fm.end()
4013 fm.end()
4007
4014
4008
4015
4009 @command(
4016 @command(
4010 b'import|patch',
4017 b'import|patch',
4011 [
4018 [
4012 (
4019 (
4013 b'p',
4020 b'p',
4014 b'strip',
4021 b'strip',
4015 1,
4022 1,
4016 _(
4023 _(
4017 b'directory strip option for patch. This has the same '
4024 b'directory strip option for patch. This has the same '
4018 b'meaning as the corresponding patch option'
4025 b'meaning as the corresponding patch option'
4019 ),
4026 ),
4020 _(b'NUM'),
4027 _(b'NUM'),
4021 ),
4028 ),
4022 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4029 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4023 (b'', b'secret', None, _(b'use the secret phase for committing')),
4030 (b'', b'secret', None, _(b'use the secret phase for committing')),
4024 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4031 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4025 (
4032 (
4026 b'f',
4033 b'f',
4027 b'force',
4034 b'force',
4028 None,
4035 None,
4029 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4036 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4030 ),
4037 ),
4031 (
4038 (
4032 b'',
4039 b'',
4033 b'no-commit',
4040 b'no-commit',
4034 None,
4041 None,
4035 _(b"don't commit, just update the working directory"),
4042 _(b"don't commit, just update the working directory"),
4036 ),
4043 ),
4037 (
4044 (
4038 b'',
4045 b'',
4039 b'bypass',
4046 b'bypass',
4040 None,
4047 None,
4041 _(b"apply patch without touching the working directory"),
4048 _(b"apply patch without touching the working directory"),
4042 ),
4049 ),
4043 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4050 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4044 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4051 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4045 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4052 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4046 (
4053 (
4047 b'',
4054 b'',
4048 b'import-branch',
4055 b'import-branch',
4049 None,
4056 None,
4050 _(b'use any branch information in patch (implied by --exact)'),
4057 _(b'use any branch information in patch (implied by --exact)'),
4051 ),
4058 ),
4052 ]
4059 ]
4053 + commitopts
4060 + commitopts
4054 + commitopts2
4061 + commitopts2
4055 + similarityopts,
4062 + similarityopts,
4056 _(b'[OPTION]... PATCH...'),
4063 _(b'[OPTION]... PATCH...'),
4057 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4064 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4058 )
4065 )
4059 def import_(ui, repo, patch1=None, *patches, **opts):
4066 def import_(ui, repo, patch1=None, *patches, **opts):
4060 """import an ordered set of patches
4067 """import an ordered set of patches
4061
4068
4062 Import a list of patches and commit them individually (unless
4069 Import a list of patches and commit them individually (unless
4063 --no-commit is specified).
4070 --no-commit is specified).
4064
4071
4065 To read a patch from standard input (stdin), use "-" as the patch
4072 To read a patch from standard input (stdin), use "-" as the patch
4066 name. If a URL is specified, the patch will be downloaded from
4073 name. If a URL is specified, the patch will be downloaded from
4067 there.
4074 there.
4068
4075
4069 Import first applies changes to the working directory (unless
4076 Import first applies changes to the working directory (unless
4070 --bypass is specified), import will abort if there are outstanding
4077 --bypass is specified), import will abort if there are outstanding
4071 changes.
4078 changes.
4072
4079
4073 Use --bypass to apply and commit patches directly to the
4080 Use --bypass to apply and commit patches directly to the
4074 repository, without affecting the working directory. Without
4081 repository, without affecting the working directory. Without
4075 --exact, patches will be applied on top of the working directory
4082 --exact, patches will be applied on top of the working directory
4076 parent revision.
4083 parent revision.
4077
4084
4078 You can import a patch straight from a mail message. Even patches
4085 You can import a patch straight from a mail message. Even patches
4079 as attachments work (to use the body part, it must have type
4086 as attachments work (to use the body part, it must have type
4080 text/plain or text/x-patch). From and Subject headers of email
4087 text/plain or text/x-patch). From and Subject headers of email
4081 message are used as default committer and commit message. All
4088 message are used as default committer and commit message. All
4082 text/plain body parts before first diff are added to the commit
4089 text/plain body parts before first diff are added to the commit
4083 message.
4090 message.
4084
4091
4085 If the imported patch was generated by :hg:`export`, user and
4092 If the imported patch was generated by :hg:`export`, user and
4086 description from patch override values from message headers and
4093 description from patch override values from message headers and
4087 body. Values given on command line with -m/--message and -u/--user
4094 body. Values given on command line with -m/--message and -u/--user
4088 override these.
4095 override these.
4089
4096
4090 If --exact is specified, import will set the working directory to
4097 If --exact is specified, import will set the working directory to
4091 the parent of each patch before applying it, and will abort if the
4098 the parent of each patch before applying it, and will abort if the
4092 resulting changeset has a different ID than the one recorded in
4099 resulting changeset has a different ID than the one recorded in
4093 the patch. This will guard against various ways that portable
4100 the patch. This will guard against various ways that portable
4094 patch formats and mail systems might fail to transfer Mercurial
4101 patch formats and mail systems might fail to transfer Mercurial
4095 data or metadata. See :hg:`bundle` for lossless transmission.
4102 data or metadata. See :hg:`bundle` for lossless transmission.
4096
4103
4097 Use --partial to ensure a changeset will be created from the patch
4104 Use --partial to ensure a changeset will be created from the patch
4098 even if some hunks fail to apply. Hunks that fail to apply will be
4105 even if some hunks fail to apply. Hunks that fail to apply will be
4099 written to a <target-file>.rej file. Conflicts can then be resolved
4106 written to a <target-file>.rej file. Conflicts can then be resolved
4100 by hand before :hg:`commit --amend` is run to update the created
4107 by hand before :hg:`commit --amend` is run to update the created
4101 changeset. This flag exists to let people import patches that
4108 changeset. This flag exists to let people import patches that
4102 partially apply without losing the associated metadata (author,
4109 partially apply without losing the associated metadata (author,
4103 date, description, ...).
4110 date, description, ...).
4104
4111
4105 .. note::
4112 .. note::
4106
4113
4107 When no hunks apply cleanly, :hg:`import --partial` will create
4114 When no hunks apply cleanly, :hg:`import --partial` will create
4108 an empty changeset, importing only the patch metadata.
4115 an empty changeset, importing only the patch metadata.
4109
4116
4110 With -s/--similarity, hg will attempt to discover renames and
4117 With -s/--similarity, hg will attempt to discover renames and
4111 copies in the patch in the same way as :hg:`addremove`.
4118 copies in the patch in the same way as :hg:`addremove`.
4112
4119
4113 It is possible to use external patch programs to perform the patch
4120 It is possible to use external patch programs to perform the patch
4114 by setting the ``ui.patch`` configuration option. For the default
4121 by setting the ``ui.patch`` configuration option. For the default
4115 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4122 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4116 See :hg:`help config` for more information about configuration
4123 See :hg:`help config` for more information about configuration
4117 files and how to use these options.
4124 files and how to use these options.
4118
4125
4119 See :hg:`help dates` for a list of formats valid for -d/--date.
4126 See :hg:`help dates` for a list of formats valid for -d/--date.
4120
4127
4121 .. container:: verbose
4128 .. container:: verbose
4122
4129
4123 Examples:
4130 Examples:
4124
4131
4125 - import a traditional patch from a website and detect renames::
4132 - import a traditional patch from a website and detect renames::
4126
4133
4127 hg import -s 80 http://example.com/bugfix.patch
4134 hg import -s 80 http://example.com/bugfix.patch
4128
4135
4129 - import a changeset from an hgweb server::
4136 - import a changeset from an hgweb server::
4130
4137
4131 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4138 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4132
4139
4133 - import all the patches in an Unix-style mbox::
4140 - import all the patches in an Unix-style mbox::
4134
4141
4135 hg import incoming-patches.mbox
4142 hg import incoming-patches.mbox
4136
4143
4137 - import patches from stdin::
4144 - import patches from stdin::
4138
4145
4139 hg import -
4146 hg import -
4140
4147
4141 - attempt to exactly restore an exported changeset (not always
4148 - attempt to exactly restore an exported changeset (not always
4142 possible)::
4149 possible)::
4143
4150
4144 hg import --exact proposed-fix.patch
4151 hg import --exact proposed-fix.patch
4145
4152
4146 - use an external tool to apply a patch which is too fuzzy for
4153 - use an external tool to apply a patch which is too fuzzy for
4147 the default internal tool.
4154 the default internal tool.
4148
4155
4149 hg import --config ui.patch="patch --merge" fuzzy.patch
4156 hg import --config ui.patch="patch --merge" fuzzy.patch
4150
4157
4151 - change the default fuzzing from 2 to a less strict 7
4158 - change the default fuzzing from 2 to a less strict 7
4152
4159
4153 hg import --config ui.fuzz=7 fuzz.patch
4160 hg import --config ui.fuzz=7 fuzz.patch
4154
4161
4155 Returns 0 on success, 1 on partial success (see --partial).
4162 Returns 0 on success, 1 on partial success (see --partial).
4156 """
4163 """
4157
4164
4158 opts = pycompat.byteskwargs(opts)
4165 opts = pycompat.byteskwargs(opts)
4159 if not patch1:
4166 if not patch1:
4160 raise error.Abort(_(b'need at least one patch to import'))
4167 raise error.Abort(_(b'need at least one patch to import'))
4161
4168
4162 patches = (patch1,) + patches
4169 patches = (patch1,) + patches
4163
4170
4164 date = opts.get(b'date')
4171 date = opts.get(b'date')
4165 if date:
4172 if date:
4166 opts[b'date'] = dateutil.parsedate(date)
4173 opts[b'date'] = dateutil.parsedate(date)
4167
4174
4168 exact = opts.get(b'exact')
4175 exact = opts.get(b'exact')
4169 update = not opts.get(b'bypass')
4176 update = not opts.get(b'bypass')
4170 if not update and opts.get(b'no_commit'):
4177 if not update and opts.get(b'no_commit'):
4171 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4178 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4172 if opts.get(b'secret') and opts.get(b'no_commit'):
4179 if opts.get(b'secret') and opts.get(b'no_commit'):
4173 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4180 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4174 try:
4181 try:
4175 sim = float(opts.get(b'similarity') or 0)
4182 sim = float(opts.get(b'similarity') or 0)
4176 except ValueError:
4183 except ValueError:
4177 raise error.Abort(_(b'similarity must be a number'))
4184 raise error.Abort(_(b'similarity must be a number'))
4178 if sim < 0 or sim > 100:
4185 if sim < 0 or sim > 100:
4179 raise error.Abort(_(b'similarity must be between 0 and 100'))
4186 raise error.Abort(_(b'similarity must be between 0 and 100'))
4180 if sim and not update:
4187 if sim and not update:
4181 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4188 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4182 if exact:
4189 if exact:
4183 if opts.get(b'edit'):
4190 if opts.get(b'edit'):
4184 raise error.Abort(_(b'cannot use --exact with --edit'))
4191 raise error.Abort(_(b'cannot use --exact with --edit'))
4185 if opts.get(b'prefix'):
4192 if opts.get(b'prefix'):
4186 raise error.Abort(_(b'cannot use --exact with --prefix'))
4193 raise error.Abort(_(b'cannot use --exact with --prefix'))
4187
4194
4188 base = opts[b"base"]
4195 base = opts[b"base"]
4189 msgs = []
4196 msgs = []
4190 ret = 0
4197 ret = 0
4191
4198
4192 with repo.wlock():
4199 with repo.wlock():
4193 if update:
4200 if update:
4194 cmdutil.checkunfinished(repo)
4201 cmdutil.checkunfinished(repo)
4195 if exact or not opts.get(b'force'):
4202 if exact or not opts.get(b'force'):
4196 cmdutil.bailifchanged(repo)
4203 cmdutil.bailifchanged(repo)
4197
4204
4198 if not opts.get(b'no_commit'):
4205 if not opts.get(b'no_commit'):
4199 lock = repo.lock
4206 lock = repo.lock
4200 tr = lambda: repo.transaction(b'import')
4207 tr = lambda: repo.transaction(b'import')
4201 dsguard = util.nullcontextmanager
4208 dsguard = util.nullcontextmanager
4202 else:
4209 else:
4203 lock = util.nullcontextmanager
4210 lock = util.nullcontextmanager
4204 tr = util.nullcontextmanager
4211 tr = util.nullcontextmanager
4205 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4212 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4206 with lock(), tr(), dsguard():
4213 with lock(), tr(), dsguard():
4207 parents = repo[None].parents()
4214 parents = repo[None].parents()
4208 for patchurl in patches:
4215 for patchurl in patches:
4209 if patchurl == b'-':
4216 if patchurl == b'-':
4210 ui.status(_(b'applying patch from stdin\n'))
4217 ui.status(_(b'applying patch from stdin\n'))
4211 patchfile = ui.fin
4218 patchfile = ui.fin
4212 patchurl = b'stdin' # for error message
4219 patchurl = b'stdin' # for error message
4213 else:
4220 else:
4214 patchurl = os.path.join(base, patchurl)
4221 patchurl = os.path.join(base, patchurl)
4215 ui.status(_(b'applying %s\n') % patchurl)
4222 ui.status(_(b'applying %s\n') % patchurl)
4216 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4223 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4217
4224
4218 haspatch = False
4225 haspatch = False
4219 for hunk in patch.split(patchfile):
4226 for hunk in patch.split(patchfile):
4220 with patch.extract(ui, hunk) as patchdata:
4227 with patch.extract(ui, hunk) as patchdata:
4221 msg, node, rej = cmdutil.tryimportone(
4228 msg, node, rej = cmdutil.tryimportone(
4222 ui, repo, patchdata, parents, opts, msgs, hg.clean
4229 ui, repo, patchdata, parents, opts, msgs, hg.clean
4223 )
4230 )
4224 if msg:
4231 if msg:
4225 haspatch = True
4232 haspatch = True
4226 ui.note(msg + b'\n')
4233 ui.note(msg + b'\n')
4227 if update or exact:
4234 if update or exact:
4228 parents = repo[None].parents()
4235 parents = repo[None].parents()
4229 else:
4236 else:
4230 parents = [repo[node]]
4237 parents = [repo[node]]
4231 if rej:
4238 if rej:
4232 ui.write_err(_(b"patch applied partially\n"))
4239 ui.write_err(_(b"patch applied partially\n"))
4233 ui.write_err(
4240 ui.write_err(
4234 _(
4241 _(
4235 b"(fix the .rej files and run "
4242 b"(fix the .rej files and run "
4236 b"`hg commit --amend`)\n"
4243 b"`hg commit --amend`)\n"
4237 )
4244 )
4238 )
4245 )
4239 ret = 1
4246 ret = 1
4240 break
4247 break
4241
4248
4242 if not haspatch:
4249 if not haspatch:
4243 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4250 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4244
4251
4245 if msgs:
4252 if msgs:
4246 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4253 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4247 return ret
4254 return ret
4248
4255
4249
4256
4250 @command(
4257 @command(
4251 b'incoming|in',
4258 b'incoming|in',
4252 [
4259 [
4253 (
4260 (
4254 b'f',
4261 b'f',
4255 b'force',
4262 b'force',
4256 None,
4263 None,
4257 _(b'run even if remote repository is unrelated'),
4264 _(b'run even if remote repository is unrelated'),
4258 ),
4265 ),
4259 (b'n', b'newest-first', None, _(b'show newest record first')),
4266 (b'n', b'newest-first', None, _(b'show newest record first')),
4260 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4267 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4261 (
4268 (
4262 b'r',
4269 b'r',
4263 b'rev',
4270 b'rev',
4264 [],
4271 [],
4265 _(b'a remote changeset intended to be added'),
4272 _(b'a remote changeset intended to be added'),
4266 _(b'REV'),
4273 _(b'REV'),
4267 ),
4274 ),
4268 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4275 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4269 (
4276 (
4270 b'b',
4277 b'b',
4271 b'branch',
4278 b'branch',
4272 [],
4279 [],
4273 _(b'a specific branch you would like to pull'),
4280 _(b'a specific branch you would like to pull'),
4274 _(b'BRANCH'),
4281 _(b'BRANCH'),
4275 ),
4282 ),
4276 ]
4283 ]
4277 + logopts
4284 + logopts
4278 + remoteopts
4285 + remoteopts
4279 + subrepoopts,
4286 + subrepoopts,
4280 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4287 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4281 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4288 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4282 )
4289 )
4283 def incoming(ui, repo, source=b"default", **opts):
4290 def incoming(ui, repo, source=b"default", **opts):
4284 """show new changesets found in source
4291 """show new changesets found in source
4285
4292
4286 Show new changesets found in the specified path/URL or the default
4293 Show new changesets found in the specified path/URL or the default
4287 pull location. These are the changesets that would have been pulled
4294 pull location. These are the changesets that would have been pulled
4288 by :hg:`pull` at the time you issued this command.
4295 by :hg:`pull` at the time you issued this command.
4289
4296
4290 See pull for valid source format details.
4297 See pull for valid source format details.
4291
4298
4292 .. container:: verbose
4299 .. container:: verbose
4293
4300
4294 With -B/--bookmarks, the result of bookmark comparison between
4301 With -B/--bookmarks, the result of bookmark comparison between
4295 local and remote repositories is displayed. With -v/--verbose,
4302 local and remote repositories is displayed. With -v/--verbose,
4296 status is also displayed for each bookmark like below::
4303 status is also displayed for each bookmark like below::
4297
4304
4298 BM1 01234567890a added
4305 BM1 01234567890a added
4299 BM2 1234567890ab advanced
4306 BM2 1234567890ab advanced
4300 BM3 234567890abc diverged
4307 BM3 234567890abc diverged
4301 BM4 34567890abcd changed
4308 BM4 34567890abcd changed
4302
4309
4303 The action taken locally when pulling depends on the
4310 The action taken locally when pulling depends on the
4304 status of each bookmark:
4311 status of each bookmark:
4305
4312
4306 :``added``: pull will create it
4313 :``added``: pull will create it
4307 :``advanced``: pull will update it
4314 :``advanced``: pull will update it
4308 :``diverged``: pull will create a divergent bookmark
4315 :``diverged``: pull will create a divergent bookmark
4309 :``changed``: result depends on remote changesets
4316 :``changed``: result depends on remote changesets
4310
4317
4311 From the point of view of pulling behavior, bookmark
4318 From the point of view of pulling behavior, bookmark
4312 existing only in the remote repository are treated as ``added``,
4319 existing only in the remote repository are treated as ``added``,
4313 even if it is in fact locally deleted.
4320 even if it is in fact locally deleted.
4314
4321
4315 .. container:: verbose
4322 .. container:: verbose
4316
4323
4317 For remote repository, using --bundle avoids downloading the
4324 For remote repository, using --bundle avoids downloading the
4318 changesets twice if the incoming is followed by a pull.
4325 changesets twice if the incoming is followed by a pull.
4319
4326
4320 Examples:
4327 Examples:
4321
4328
4322 - show incoming changes with patches and full description::
4329 - show incoming changes with patches and full description::
4323
4330
4324 hg incoming -vp
4331 hg incoming -vp
4325
4332
4326 - show incoming changes excluding merges, store a bundle::
4333 - show incoming changes excluding merges, store a bundle::
4327
4334
4328 hg in -vpM --bundle incoming.hg
4335 hg in -vpM --bundle incoming.hg
4329 hg pull incoming.hg
4336 hg pull incoming.hg
4330
4337
4331 - briefly list changes inside a bundle::
4338 - briefly list changes inside a bundle::
4332
4339
4333 hg in changes.hg -T "{desc|firstline}\\n"
4340 hg in changes.hg -T "{desc|firstline}\\n"
4334
4341
4335 Returns 0 if there are incoming changes, 1 otherwise.
4342 Returns 0 if there are incoming changes, 1 otherwise.
4336 """
4343 """
4337 opts = pycompat.byteskwargs(opts)
4344 opts = pycompat.byteskwargs(opts)
4338 if opts.get(b'graph'):
4345 if opts.get(b'graph'):
4339 logcmdutil.checkunsupportedgraphflags([], opts)
4346 logcmdutil.checkunsupportedgraphflags([], opts)
4340
4347
4341 def display(other, chlist, displayer):
4348 def display(other, chlist, displayer):
4342 revdag = logcmdutil.graphrevs(other, chlist, opts)
4349 revdag = logcmdutil.graphrevs(other, chlist, opts)
4343 logcmdutil.displaygraph(
4350 logcmdutil.displaygraph(
4344 ui, repo, revdag, displayer, graphmod.asciiedges
4351 ui, repo, revdag, displayer, graphmod.asciiedges
4345 )
4352 )
4346
4353
4347 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4354 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4348 return 0
4355 return 0
4349
4356
4350 if opts.get(b'bundle') and opts.get(b'subrepos'):
4357 if opts.get(b'bundle') and opts.get(b'subrepos'):
4351 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4358 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4352
4359
4353 if opts.get(b'bookmarks'):
4360 if opts.get(b'bookmarks'):
4354 source, branches = hg.parseurl(
4361 source, branches = hg.parseurl(
4355 ui.expandpath(source), opts.get(b'branch')
4362 ui.expandpath(source), opts.get(b'branch')
4356 )
4363 )
4357 other = hg.peer(repo, opts, source)
4364 other = hg.peer(repo, opts, source)
4358 if b'bookmarks' not in other.listkeys(b'namespaces'):
4365 if b'bookmarks' not in other.listkeys(b'namespaces'):
4359 ui.warn(_(b"remote doesn't support bookmarks\n"))
4366 ui.warn(_(b"remote doesn't support bookmarks\n"))
4360 return 0
4367 return 0
4361 ui.pager(b'incoming')
4368 ui.pager(b'incoming')
4362 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4369 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4363 return bookmarks.incoming(ui, repo, other)
4370 return bookmarks.incoming(ui, repo, other)
4364
4371
4365 repo._subtoppath = ui.expandpath(source)
4372 repo._subtoppath = ui.expandpath(source)
4366 try:
4373 try:
4367 return hg.incoming(ui, repo, source, opts)
4374 return hg.incoming(ui, repo, source, opts)
4368 finally:
4375 finally:
4369 del repo._subtoppath
4376 del repo._subtoppath
4370
4377
4371
4378
4372 @command(
4379 @command(
4373 b'init',
4380 b'init',
4374 remoteopts,
4381 remoteopts,
4375 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4382 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4376 helpcategory=command.CATEGORY_REPO_CREATION,
4383 helpcategory=command.CATEGORY_REPO_CREATION,
4377 helpbasic=True,
4384 helpbasic=True,
4378 norepo=True,
4385 norepo=True,
4379 )
4386 )
4380 def init(ui, dest=b".", **opts):
4387 def init(ui, dest=b".", **opts):
4381 """create a new repository in the given directory
4388 """create a new repository in the given directory
4382
4389
4383 Initialize a new repository in the given directory. If the given
4390 Initialize a new repository in the given directory. If the given
4384 directory does not exist, it will be created.
4391 directory does not exist, it will be created.
4385
4392
4386 If no directory is given, the current directory is used.
4393 If no directory is given, the current directory is used.
4387
4394
4388 It is possible to specify an ``ssh://`` URL as the destination.
4395 It is possible to specify an ``ssh://`` URL as the destination.
4389 See :hg:`help urls` for more information.
4396 See :hg:`help urls` for more information.
4390
4397
4391 Returns 0 on success.
4398 Returns 0 on success.
4392 """
4399 """
4393 opts = pycompat.byteskwargs(opts)
4400 opts = pycompat.byteskwargs(opts)
4394 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4401 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4395
4402
4396
4403
4397 @command(
4404 @command(
4398 b'locate',
4405 b'locate',
4399 [
4406 [
4400 (
4407 (
4401 b'r',
4408 b'r',
4402 b'rev',
4409 b'rev',
4403 b'',
4410 b'',
4404 _(b'search the repository as it is in REV'),
4411 _(b'search the repository as it is in REV'),
4405 _(b'REV'),
4412 _(b'REV'),
4406 ),
4413 ),
4407 (
4414 (
4408 b'0',
4415 b'0',
4409 b'print0',
4416 b'print0',
4410 None,
4417 None,
4411 _(b'end filenames with NUL, for use with xargs'),
4418 _(b'end filenames with NUL, for use with xargs'),
4412 ),
4419 ),
4413 (
4420 (
4414 b'f',
4421 b'f',
4415 b'fullpath',
4422 b'fullpath',
4416 None,
4423 None,
4417 _(b'print complete paths from the filesystem root'),
4424 _(b'print complete paths from the filesystem root'),
4418 ),
4425 ),
4419 ]
4426 ]
4420 + walkopts,
4427 + walkopts,
4421 _(b'[OPTION]... [PATTERN]...'),
4428 _(b'[OPTION]... [PATTERN]...'),
4422 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4429 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4423 )
4430 )
4424 def locate(ui, repo, *pats, **opts):
4431 def locate(ui, repo, *pats, **opts):
4425 """locate files matching specific patterns (DEPRECATED)
4432 """locate files matching specific patterns (DEPRECATED)
4426
4433
4427 Print files under Mercurial control in the working directory whose
4434 Print files under Mercurial control in the working directory whose
4428 names match the given patterns.
4435 names match the given patterns.
4429
4436
4430 By default, this command searches all directories in the working
4437 By default, this command searches all directories in the working
4431 directory. To search just the current directory and its
4438 directory. To search just the current directory and its
4432 subdirectories, use "--include .".
4439 subdirectories, use "--include .".
4433
4440
4434 If no patterns are given to match, this command prints the names
4441 If no patterns are given to match, this command prints the names
4435 of all files under Mercurial control in the working directory.
4442 of all files under Mercurial control in the working directory.
4436
4443
4437 If you want to feed the output of this command into the "xargs"
4444 If you want to feed the output of this command into the "xargs"
4438 command, use the -0 option to both this command and "xargs". This
4445 command, use the -0 option to both this command and "xargs". This
4439 will avoid the problem of "xargs" treating single filenames that
4446 will avoid the problem of "xargs" treating single filenames that
4440 contain whitespace as multiple filenames.
4447 contain whitespace as multiple filenames.
4441
4448
4442 See :hg:`help files` for a more versatile command.
4449 See :hg:`help files` for a more versatile command.
4443
4450
4444 Returns 0 if a match is found, 1 otherwise.
4451 Returns 0 if a match is found, 1 otherwise.
4445 """
4452 """
4446 opts = pycompat.byteskwargs(opts)
4453 opts = pycompat.byteskwargs(opts)
4447 if opts.get(b'print0'):
4454 if opts.get(b'print0'):
4448 end = b'\0'
4455 end = b'\0'
4449 else:
4456 else:
4450 end = b'\n'
4457 end = b'\n'
4451 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4458 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4452
4459
4453 ret = 1
4460 ret = 1
4454 m = scmutil.match(
4461 m = scmutil.match(
4455 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4462 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4456 )
4463 )
4457
4464
4458 ui.pager(b'locate')
4465 ui.pager(b'locate')
4459 if ctx.rev() is None:
4466 if ctx.rev() is None:
4460 # When run on the working copy, "locate" includes removed files, so
4467 # When run on the working copy, "locate" includes removed files, so
4461 # we get the list of files from the dirstate.
4468 # we get the list of files from the dirstate.
4462 filesgen = sorted(repo.dirstate.matches(m))
4469 filesgen = sorted(repo.dirstate.matches(m))
4463 else:
4470 else:
4464 filesgen = ctx.matches(m)
4471 filesgen = ctx.matches(m)
4465 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4472 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4466 for abs in filesgen:
4473 for abs in filesgen:
4467 if opts.get(b'fullpath'):
4474 if opts.get(b'fullpath'):
4468 ui.write(repo.wjoin(abs), end)
4475 ui.write(repo.wjoin(abs), end)
4469 else:
4476 else:
4470 ui.write(uipathfn(abs), end)
4477 ui.write(uipathfn(abs), end)
4471 ret = 0
4478 ret = 0
4472
4479
4473 return ret
4480 return ret
4474
4481
4475
4482
4476 @command(
4483 @command(
4477 b'log|history',
4484 b'log|history',
4478 [
4485 [
4479 (
4486 (
4480 b'f',
4487 b'f',
4481 b'follow',
4488 b'follow',
4482 None,
4489 None,
4483 _(
4490 _(
4484 b'follow changeset history, or file history across copies and renames'
4491 b'follow changeset history, or file history across copies and renames'
4485 ),
4492 ),
4486 ),
4493 ),
4487 (
4494 (
4488 b'',
4495 b'',
4489 b'follow-first',
4496 b'follow-first',
4490 None,
4497 None,
4491 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4498 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4492 ),
4499 ),
4493 (
4500 (
4494 b'd',
4501 b'd',
4495 b'date',
4502 b'date',
4496 b'',
4503 b'',
4497 _(b'show revisions matching date spec'),
4504 _(b'show revisions matching date spec'),
4498 _(b'DATE'),
4505 _(b'DATE'),
4499 ),
4506 ),
4500 (b'C', b'copies', None, _(b'show copied files')),
4507 (b'C', b'copies', None, _(b'show copied files')),
4501 (
4508 (
4502 b'k',
4509 b'k',
4503 b'keyword',
4510 b'keyword',
4504 [],
4511 [],
4505 _(b'do case-insensitive search for a given text'),
4512 _(b'do case-insensitive search for a given text'),
4506 _(b'TEXT'),
4513 _(b'TEXT'),
4507 ),
4514 ),
4508 (
4515 (
4509 b'r',
4516 b'r',
4510 b'rev',
4517 b'rev',
4511 [],
4518 [],
4512 _(b'show the specified revision or revset'),
4519 _(b'show the specified revision or revset'),
4513 _(b'REV'),
4520 _(b'REV'),
4514 ),
4521 ),
4515 (
4522 (
4516 b'L',
4523 b'L',
4517 b'line-range',
4524 b'line-range',
4518 [],
4525 [],
4519 _(b'follow line range of specified file (EXPERIMENTAL)'),
4526 _(b'follow line range of specified file (EXPERIMENTAL)'),
4520 _(b'FILE,RANGE'),
4527 _(b'FILE,RANGE'),
4521 ),
4528 ),
4522 (
4529 (
4523 b'',
4530 b'',
4524 b'removed',
4531 b'removed',
4525 None,
4532 None,
4526 _(b'include revisions where files were removed'),
4533 _(b'include revisions where files were removed'),
4527 ),
4534 ),
4528 (
4535 (
4529 b'm',
4536 b'm',
4530 b'only-merges',
4537 b'only-merges',
4531 None,
4538 None,
4532 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4539 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4533 ),
4540 ),
4534 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4541 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4535 (
4542 (
4536 b'',
4543 b'',
4537 b'only-branch',
4544 b'only-branch',
4538 [],
4545 [],
4539 _(
4546 _(
4540 b'show only changesets within the given named branch (DEPRECATED)'
4547 b'show only changesets within the given named branch (DEPRECATED)'
4541 ),
4548 ),
4542 _(b'BRANCH'),
4549 _(b'BRANCH'),
4543 ),
4550 ),
4544 (
4551 (
4545 b'b',
4552 b'b',
4546 b'branch',
4553 b'branch',
4547 [],
4554 [],
4548 _(b'show changesets within the given named branch'),
4555 _(b'show changesets within the given named branch'),
4549 _(b'BRANCH'),
4556 _(b'BRANCH'),
4550 ),
4557 ),
4551 (
4558 (
4552 b'P',
4559 b'P',
4553 b'prune',
4560 b'prune',
4554 [],
4561 [],
4555 _(b'do not display revision or any of its ancestors'),
4562 _(b'do not display revision or any of its ancestors'),
4556 _(b'REV'),
4563 _(b'REV'),
4557 ),
4564 ),
4558 ]
4565 ]
4559 + logopts
4566 + logopts
4560 + walkopts,
4567 + walkopts,
4561 _(b'[OPTION]... [FILE]'),
4568 _(b'[OPTION]... [FILE]'),
4562 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4569 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4563 helpbasic=True,
4570 helpbasic=True,
4564 inferrepo=True,
4571 inferrepo=True,
4565 intents={INTENT_READONLY},
4572 intents={INTENT_READONLY},
4566 )
4573 )
4567 def log(ui, repo, *pats, **opts):
4574 def log(ui, repo, *pats, **opts):
4568 """show revision history of entire repository or files
4575 """show revision history of entire repository or files
4569
4576
4570 Print the revision history of the specified files or the entire
4577 Print the revision history of the specified files or the entire
4571 project.
4578 project.
4572
4579
4573 If no revision range is specified, the default is ``tip:0`` unless
4580 If no revision range is specified, the default is ``tip:0`` unless
4574 --follow is set, in which case the working directory parent is
4581 --follow is set, in which case the working directory parent is
4575 used as the starting revision.
4582 used as the starting revision.
4576
4583
4577 File history is shown without following rename or copy history of
4584 File history is shown without following rename or copy history of
4578 files. Use -f/--follow with a filename to follow history across
4585 files. Use -f/--follow with a filename to follow history across
4579 renames and copies. --follow without a filename will only show
4586 renames and copies. --follow without a filename will only show
4580 ancestors of the starting revision.
4587 ancestors of the starting revision.
4581
4588
4582 By default this command prints revision number and changeset id,
4589 By default this command prints revision number and changeset id,
4583 tags, non-trivial parents, user, date and time, and a summary for
4590 tags, non-trivial parents, user, date and time, and a summary for
4584 each commit. When the -v/--verbose switch is used, the list of
4591 each commit. When the -v/--verbose switch is used, the list of
4585 changed files and full commit message are shown.
4592 changed files and full commit message are shown.
4586
4593
4587 With --graph the revisions are shown as an ASCII art DAG with the most
4594 With --graph the revisions are shown as an ASCII art DAG with the most
4588 recent changeset at the top.
4595 recent changeset at the top.
4589 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4596 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4590 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4597 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4591 changeset from the lines below is a parent of the 'o' merge on the same
4598 changeset from the lines below is a parent of the 'o' merge on the same
4592 line.
4599 line.
4593 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4600 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4594 of a '|' indicates one or more revisions in a path are omitted.
4601 of a '|' indicates one or more revisions in a path are omitted.
4595
4602
4596 .. container:: verbose
4603 .. container:: verbose
4597
4604
4598 Use -L/--line-range FILE,M:N options to follow the history of lines
4605 Use -L/--line-range FILE,M:N options to follow the history of lines
4599 from M to N in FILE. With -p/--patch only diff hunks affecting
4606 from M to N in FILE. With -p/--patch only diff hunks affecting
4600 specified line range will be shown. This option requires --follow;
4607 specified line range will be shown. This option requires --follow;
4601 it can be specified multiple times. Currently, this option is not
4608 it can be specified multiple times. Currently, this option is not
4602 compatible with --graph. This option is experimental.
4609 compatible with --graph. This option is experimental.
4603
4610
4604 .. note::
4611 .. note::
4605
4612
4606 :hg:`log --patch` may generate unexpected diff output for merge
4613 :hg:`log --patch` may generate unexpected diff output for merge
4607 changesets, as it will only compare the merge changeset against
4614 changesets, as it will only compare the merge changeset against
4608 its first parent. Also, only files different from BOTH parents
4615 its first parent. Also, only files different from BOTH parents
4609 will appear in files:.
4616 will appear in files:.
4610
4617
4611 .. note::
4618 .. note::
4612
4619
4613 For performance reasons, :hg:`log FILE` may omit duplicate changes
4620 For performance reasons, :hg:`log FILE` may omit duplicate changes
4614 made on branches and will not show removals or mode changes. To
4621 made on branches and will not show removals or mode changes. To
4615 see all such changes, use the --removed switch.
4622 see all such changes, use the --removed switch.
4616
4623
4617 .. container:: verbose
4624 .. container:: verbose
4618
4625
4619 .. note::
4626 .. note::
4620
4627
4621 The history resulting from -L/--line-range options depends on diff
4628 The history resulting from -L/--line-range options depends on diff
4622 options; for instance if white-spaces are ignored, respective changes
4629 options; for instance if white-spaces are ignored, respective changes
4623 with only white-spaces in specified line range will not be listed.
4630 with only white-spaces in specified line range will not be listed.
4624
4631
4625 .. container:: verbose
4632 .. container:: verbose
4626
4633
4627 Some examples:
4634 Some examples:
4628
4635
4629 - changesets with full descriptions and file lists::
4636 - changesets with full descriptions and file lists::
4630
4637
4631 hg log -v
4638 hg log -v
4632
4639
4633 - changesets ancestral to the working directory::
4640 - changesets ancestral to the working directory::
4634
4641
4635 hg log -f
4642 hg log -f
4636
4643
4637 - last 10 commits on the current branch::
4644 - last 10 commits on the current branch::
4638
4645
4639 hg log -l 10 -b .
4646 hg log -l 10 -b .
4640
4647
4641 - changesets showing all modifications of a file, including removals::
4648 - changesets showing all modifications of a file, including removals::
4642
4649
4643 hg log --removed file.c
4650 hg log --removed file.c
4644
4651
4645 - all changesets that touch a directory, with diffs, excluding merges::
4652 - all changesets that touch a directory, with diffs, excluding merges::
4646
4653
4647 hg log -Mp lib/
4654 hg log -Mp lib/
4648
4655
4649 - all revision numbers that match a keyword::
4656 - all revision numbers that match a keyword::
4650
4657
4651 hg log -k bug --template "{rev}\\n"
4658 hg log -k bug --template "{rev}\\n"
4652
4659
4653 - the full hash identifier of the working directory parent::
4660 - the full hash identifier of the working directory parent::
4654
4661
4655 hg log -r . --template "{node}\\n"
4662 hg log -r . --template "{node}\\n"
4656
4663
4657 - list available log templates::
4664 - list available log templates::
4658
4665
4659 hg log -T list
4666 hg log -T list
4660
4667
4661 - check if a given changeset is included in a tagged release::
4668 - check if a given changeset is included in a tagged release::
4662
4669
4663 hg log -r "a21ccf and ancestor(1.9)"
4670 hg log -r "a21ccf and ancestor(1.9)"
4664
4671
4665 - find all changesets by some user in a date range::
4672 - find all changesets by some user in a date range::
4666
4673
4667 hg log -k alice -d "may 2008 to jul 2008"
4674 hg log -k alice -d "may 2008 to jul 2008"
4668
4675
4669 - summary of all changesets after the last tag::
4676 - summary of all changesets after the last tag::
4670
4677
4671 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4678 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4672
4679
4673 - changesets touching lines 13 to 23 for file.c::
4680 - changesets touching lines 13 to 23 for file.c::
4674
4681
4675 hg log -L file.c,13:23
4682 hg log -L file.c,13:23
4676
4683
4677 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4684 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4678 main.c with patch::
4685 main.c with patch::
4679
4686
4680 hg log -L file.c,13:23 -L main.c,2:6 -p
4687 hg log -L file.c,13:23 -L main.c,2:6 -p
4681
4688
4682 See :hg:`help dates` for a list of formats valid for -d/--date.
4689 See :hg:`help dates` for a list of formats valid for -d/--date.
4683
4690
4684 See :hg:`help revisions` for more about specifying and ordering
4691 See :hg:`help revisions` for more about specifying and ordering
4685 revisions.
4692 revisions.
4686
4693
4687 See :hg:`help templates` for more about pre-packaged styles and
4694 See :hg:`help templates` for more about pre-packaged styles and
4688 specifying custom templates. The default template used by the log
4695 specifying custom templates. The default template used by the log
4689 command can be customized via the ``ui.logtemplate`` configuration
4696 command can be customized via the ``ui.logtemplate`` configuration
4690 setting.
4697 setting.
4691
4698
4692 Returns 0 on success.
4699 Returns 0 on success.
4693
4700
4694 """
4701 """
4695 opts = pycompat.byteskwargs(opts)
4702 opts = pycompat.byteskwargs(opts)
4696 linerange = opts.get(b'line_range')
4703 linerange = opts.get(b'line_range')
4697
4704
4698 if linerange and not opts.get(b'follow'):
4705 if linerange and not opts.get(b'follow'):
4699 raise error.Abort(_(b'--line-range requires --follow'))
4706 raise error.Abort(_(b'--line-range requires --follow'))
4700
4707
4701 if linerange and pats:
4708 if linerange and pats:
4702 # TODO: take pats as patterns with no line-range filter
4709 # TODO: take pats as patterns with no line-range filter
4703 raise error.Abort(
4710 raise error.Abort(
4704 _(b'FILE arguments are not compatible with --line-range option')
4711 _(b'FILE arguments are not compatible with --line-range option')
4705 )
4712 )
4706
4713
4707 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4714 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4708 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4715 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4709 if linerange:
4716 if linerange:
4710 # TODO: should follow file history from logcmdutil._initialrevs(),
4717 # TODO: should follow file history from logcmdutil._initialrevs(),
4711 # then filter the result by logcmdutil._makerevset() and --limit
4718 # then filter the result by logcmdutil._makerevset() and --limit
4712 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4719 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4713
4720
4714 getcopies = None
4721 getcopies = None
4715 if opts.get(b'copies'):
4722 if opts.get(b'copies'):
4716 endrev = None
4723 endrev = None
4717 if revs:
4724 if revs:
4718 endrev = revs.max() + 1
4725 endrev = revs.max() + 1
4719 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4726 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4720
4727
4721 ui.pager(b'log')
4728 ui.pager(b'log')
4722 displayer = logcmdutil.changesetdisplayer(
4729 displayer = logcmdutil.changesetdisplayer(
4723 ui, repo, opts, differ, buffered=True
4730 ui, repo, opts, differ, buffered=True
4724 )
4731 )
4725 if opts.get(b'graph'):
4732 if opts.get(b'graph'):
4726 displayfn = logcmdutil.displaygraphrevs
4733 displayfn = logcmdutil.displaygraphrevs
4727 else:
4734 else:
4728 displayfn = logcmdutil.displayrevs
4735 displayfn = logcmdutil.displayrevs
4729 displayfn(ui, repo, revs, displayer, getcopies)
4736 displayfn(ui, repo, revs, displayer, getcopies)
4730
4737
4731
4738
4732 @command(
4739 @command(
4733 b'manifest',
4740 b'manifest',
4734 [
4741 [
4735 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4742 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4736 (b'', b'all', False, _(b"list files from all revisions")),
4743 (b'', b'all', False, _(b"list files from all revisions")),
4737 ]
4744 ]
4738 + formatteropts,
4745 + formatteropts,
4739 _(b'[-r REV]'),
4746 _(b'[-r REV]'),
4740 helpcategory=command.CATEGORY_MAINTENANCE,
4747 helpcategory=command.CATEGORY_MAINTENANCE,
4741 intents={INTENT_READONLY},
4748 intents={INTENT_READONLY},
4742 )
4749 )
4743 def manifest(ui, repo, node=None, rev=None, **opts):
4750 def manifest(ui, repo, node=None, rev=None, **opts):
4744 """output the current or given revision of the project manifest
4751 """output the current or given revision of the project manifest
4745
4752
4746 Print a list of version controlled files for the given revision.
4753 Print a list of version controlled files for the given revision.
4747 If no revision is given, the first parent of the working directory
4754 If no revision is given, the first parent of the working directory
4748 is used, or the null revision if no revision is checked out.
4755 is used, or the null revision if no revision is checked out.
4749
4756
4750 With -v, print file permissions, symlink and executable bits.
4757 With -v, print file permissions, symlink and executable bits.
4751 With --debug, print file revision hashes.
4758 With --debug, print file revision hashes.
4752
4759
4753 If option --all is specified, the list of all files from all revisions
4760 If option --all is specified, the list of all files from all revisions
4754 is printed. This includes deleted and renamed files.
4761 is printed. This includes deleted and renamed files.
4755
4762
4756 Returns 0 on success.
4763 Returns 0 on success.
4757 """
4764 """
4758 opts = pycompat.byteskwargs(opts)
4765 opts = pycompat.byteskwargs(opts)
4759 fm = ui.formatter(b'manifest', opts)
4766 fm = ui.formatter(b'manifest', opts)
4760
4767
4761 if opts.get(b'all'):
4768 if opts.get(b'all'):
4762 if rev or node:
4769 if rev or node:
4763 raise error.Abort(_(b"can't specify a revision with --all"))
4770 raise error.Abort(_(b"can't specify a revision with --all"))
4764
4771
4765 res = set()
4772 res = set()
4766 for rev in repo:
4773 for rev in repo:
4767 ctx = repo[rev]
4774 ctx = repo[rev]
4768 res |= set(ctx.files())
4775 res |= set(ctx.files())
4769
4776
4770 ui.pager(b'manifest')
4777 ui.pager(b'manifest')
4771 for f in sorted(res):
4778 for f in sorted(res):
4772 fm.startitem()
4779 fm.startitem()
4773 fm.write(b"path", b'%s\n', f)
4780 fm.write(b"path", b'%s\n', f)
4774 fm.end()
4781 fm.end()
4775 return
4782 return
4776
4783
4777 if rev and node:
4784 if rev and node:
4778 raise error.Abort(_(b"please specify just one revision"))
4785 raise error.Abort(_(b"please specify just one revision"))
4779
4786
4780 if not node:
4787 if not node:
4781 node = rev
4788 node = rev
4782
4789
4783 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4790 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4784 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4791 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4785 if node:
4792 if node:
4786 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4793 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4787 ctx = scmutil.revsingle(repo, node)
4794 ctx = scmutil.revsingle(repo, node)
4788 mf = ctx.manifest()
4795 mf = ctx.manifest()
4789 ui.pager(b'manifest')
4796 ui.pager(b'manifest')
4790 for f in ctx:
4797 for f in ctx:
4791 fm.startitem()
4798 fm.startitem()
4792 fm.context(ctx=ctx)
4799 fm.context(ctx=ctx)
4793 fl = ctx[f].flags()
4800 fl = ctx[f].flags()
4794 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4801 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4795 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4802 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4796 fm.write(b'path', b'%s\n', f)
4803 fm.write(b'path', b'%s\n', f)
4797 fm.end()
4804 fm.end()
4798
4805
4799
4806
4800 @command(
4807 @command(
4801 b'merge',
4808 b'merge',
4802 [
4809 [
4803 (
4810 (
4804 b'f',
4811 b'f',
4805 b'force',
4812 b'force',
4806 None,
4813 None,
4807 _(b'force a merge including outstanding changes (DEPRECATED)'),
4814 _(b'force a merge including outstanding changes (DEPRECATED)'),
4808 ),
4815 ),
4809 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4816 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4810 (
4817 (
4811 b'P',
4818 b'P',
4812 b'preview',
4819 b'preview',
4813 None,
4820 None,
4814 _(b'review revisions to merge (no merge is performed)'),
4821 _(b'review revisions to merge (no merge is performed)'),
4815 ),
4822 ),
4816 (b'', b'abort', None, _(b'abort the ongoing merge')),
4823 (b'', b'abort', None, _(b'abort the ongoing merge')),
4817 ]
4824 ]
4818 + mergetoolopts,
4825 + mergetoolopts,
4819 _(b'[-P] [[-r] REV]'),
4826 _(b'[-P] [[-r] REV]'),
4820 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4827 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4821 helpbasic=True,
4828 helpbasic=True,
4822 )
4829 )
4823 def merge(ui, repo, node=None, **opts):
4830 def merge(ui, repo, node=None, **opts):
4824 """merge another revision into working directory
4831 """merge another revision into working directory
4825
4832
4826 The current working directory is updated with all changes made in
4833 The current working directory is updated with all changes made in
4827 the requested revision since the last common predecessor revision.
4834 the requested revision since the last common predecessor revision.
4828
4835
4829 Files that changed between either parent are marked as changed for
4836 Files that changed between either parent are marked as changed for
4830 the next commit and a commit must be performed before any further
4837 the next commit and a commit must be performed before any further
4831 updates to the repository are allowed. The next commit will have
4838 updates to the repository are allowed. The next commit will have
4832 two parents.
4839 two parents.
4833
4840
4834 ``--tool`` can be used to specify the merge tool used for file
4841 ``--tool`` can be used to specify the merge tool used for file
4835 merges. It overrides the HGMERGE environment variable and your
4842 merges. It overrides the HGMERGE environment variable and your
4836 configuration files. See :hg:`help merge-tools` for options.
4843 configuration files. See :hg:`help merge-tools` for options.
4837
4844
4838 If no revision is specified, the working directory's parent is a
4845 If no revision is specified, the working directory's parent is a
4839 head revision, and the current branch contains exactly one other
4846 head revision, and the current branch contains exactly one other
4840 head, the other head is merged with by default. Otherwise, an
4847 head, the other head is merged with by default. Otherwise, an
4841 explicit revision with which to merge must be provided.
4848 explicit revision with which to merge must be provided.
4842
4849
4843 See :hg:`help resolve` for information on handling file conflicts.
4850 See :hg:`help resolve` for information on handling file conflicts.
4844
4851
4845 To undo an uncommitted merge, use :hg:`merge --abort` which
4852 To undo an uncommitted merge, use :hg:`merge --abort` which
4846 will check out a clean copy of the original merge parent, losing
4853 will check out a clean copy of the original merge parent, losing
4847 all changes.
4854 all changes.
4848
4855
4849 Returns 0 on success, 1 if there are unresolved files.
4856 Returns 0 on success, 1 if there are unresolved files.
4850 """
4857 """
4851
4858
4852 opts = pycompat.byteskwargs(opts)
4859 opts = pycompat.byteskwargs(opts)
4853 abort = opts.get(b'abort')
4860 abort = opts.get(b'abort')
4854 if abort and repo.dirstate.p2() == nullid:
4861 if abort and repo.dirstate.p2() == nullid:
4855 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4862 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4856 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4863 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4857 if abort:
4864 if abort:
4858 state = cmdutil.getunfinishedstate(repo)
4865 state = cmdutil.getunfinishedstate(repo)
4859 if state and state._opname != b'merge':
4866 if state and state._opname != b'merge':
4860 raise error.Abort(
4867 raise error.Abort(
4861 _(b'cannot abort merge with %s in progress') % (state._opname),
4868 _(b'cannot abort merge with %s in progress') % (state._opname),
4862 hint=state.hint(),
4869 hint=state.hint(),
4863 )
4870 )
4864 if node:
4871 if node:
4865 raise error.Abort(_(b"cannot specify a node with --abort"))
4872 raise error.Abort(_(b"cannot specify a node with --abort"))
4866 return hg.abortmerge(repo.ui, repo)
4873 return hg.abortmerge(repo.ui, repo)
4867
4874
4868 if opts.get(b'rev') and node:
4875 if opts.get(b'rev') and node:
4869 raise error.Abort(_(b"please specify just one revision"))
4876 raise error.Abort(_(b"please specify just one revision"))
4870 if not node:
4877 if not node:
4871 node = opts.get(b'rev')
4878 node = opts.get(b'rev')
4872
4879
4873 if node:
4880 if node:
4874 node = scmutil.revsingle(repo, node).node()
4881 node = scmutil.revsingle(repo, node).node()
4875 else:
4882 else:
4876 if ui.configbool(b'commands', b'merge.require-rev'):
4883 if ui.configbool(b'commands', b'merge.require-rev'):
4877 raise error.Abort(
4884 raise error.Abort(
4878 _(
4885 _(
4879 b'configuration requires specifying revision to merge '
4886 b'configuration requires specifying revision to merge '
4880 b'with'
4887 b'with'
4881 )
4888 )
4882 )
4889 )
4883 node = repo[destutil.destmerge(repo)].node()
4890 node = repo[destutil.destmerge(repo)].node()
4884
4891
4885 if node is None:
4892 if node is None:
4886 raise error.Abort(_(b'merging with the working copy has no effect'))
4893 raise error.Abort(_(b'merging with the working copy has no effect'))
4887
4894
4888 if opts.get(b'preview'):
4895 if opts.get(b'preview'):
4889 # find nodes that are ancestors of p2 but not of p1
4896 # find nodes that are ancestors of p2 but not of p1
4890 p1 = repo[b'.'].node()
4897 p1 = repo[b'.'].node()
4891 p2 = node
4898 p2 = node
4892 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4899 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4893
4900
4894 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4901 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4895 for node in nodes:
4902 for node in nodes:
4896 displayer.show(repo[node])
4903 displayer.show(repo[node])
4897 displayer.close()
4904 displayer.close()
4898 return 0
4905 return 0
4899
4906
4900 # ui.forcemerge is an internal variable, do not document
4907 # ui.forcemerge is an internal variable, do not document
4901 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4908 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4902 with ui.configoverride(overrides, b'merge'):
4909 with ui.configoverride(overrides, b'merge'):
4903 force = opts.get(b'force')
4910 force = opts.get(b'force')
4904 labels = [b'working copy', b'merge rev']
4911 labels = [b'working copy', b'merge rev']
4905 return hg.merge(
4912 return hg.merge(
4906 repo, node, force=force, mergeforce=force, labels=labels
4913 repo, node, force=force, mergeforce=force, labels=labels
4907 )
4914 )
4908
4915
4909
4916
4910 statemod.addunfinished(
4917 statemod.addunfinished(
4911 b'merge',
4918 b'merge',
4912 fname=None,
4919 fname=None,
4913 clearable=True,
4920 clearable=True,
4914 allowcommit=True,
4921 allowcommit=True,
4915 cmdmsg=_(b'outstanding uncommitted merge'),
4922 cmdmsg=_(b'outstanding uncommitted merge'),
4916 abortfunc=hg.abortmerge,
4923 abortfunc=hg.abortmerge,
4917 statushint=_(
4924 statushint=_(
4918 b'To continue: hg commit\nTo abort: hg merge --abort'
4925 b'To continue: hg commit\nTo abort: hg merge --abort'
4919 ),
4926 ),
4920 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4927 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4921 )
4928 )
4922
4929
4923
4930
4924 @command(
4931 @command(
4925 b'outgoing|out',
4932 b'outgoing|out',
4926 [
4933 [
4927 (
4934 (
4928 b'f',
4935 b'f',
4929 b'force',
4936 b'force',
4930 None,
4937 None,
4931 _(b'run even when the destination is unrelated'),
4938 _(b'run even when the destination is unrelated'),
4932 ),
4939 ),
4933 (
4940 (
4934 b'r',
4941 b'r',
4935 b'rev',
4942 b'rev',
4936 [],
4943 [],
4937 _(b'a changeset intended to be included in the destination'),
4944 _(b'a changeset intended to be included in the destination'),
4938 _(b'REV'),
4945 _(b'REV'),
4939 ),
4946 ),
4940 (b'n', b'newest-first', None, _(b'show newest record first')),
4947 (b'n', b'newest-first', None, _(b'show newest record first')),
4941 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4948 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4942 (
4949 (
4943 b'b',
4950 b'b',
4944 b'branch',
4951 b'branch',
4945 [],
4952 [],
4946 _(b'a specific branch you would like to push'),
4953 _(b'a specific branch you would like to push'),
4947 _(b'BRANCH'),
4954 _(b'BRANCH'),
4948 ),
4955 ),
4949 ]
4956 ]
4950 + logopts
4957 + logopts
4951 + remoteopts
4958 + remoteopts
4952 + subrepoopts,
4959 + subrepoopts,
4953 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4960 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4954 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4961 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4955 )
4962 )
4956 def outgoing(ui, repo, dest=None, **opts):
4963 def outgoing(ui, repo, dest=None, **opts):
4957 """show changesets not found in the destination
4964 """show changesets not found in the destination
4958
4965
4959 Show changesets not found in the specified destination repository
4966 Show changesets not found in the specified destination repository
4960 or the default push location. These are the changesets that would
4967 or the default push location. These are the changesets that would
4961 be pushed if a push was requested.
4968 be pushed if a push was requested.
4962
4969
4963 See pull for details of valid destination formats.
4970 See pull for details of valid destination formats.
4964
4971
4965 .. container:: verbose
4972 .. container:: verbose
4966
4973
4967 With -B/--bookmarks, the result of bookmark comparison between
4974 With -B/--bookmarks, the result of bookmark comparison between
4968 local and remote repositories is displayed. With -v/--verbose,
4975 local and remote repositories is displayed. With -v/--verbose,
4969 status is also displayed for each bookmark like below::
4976 status is also displayed for each bookmark like below::
4970
4977
4971 BM1 01234567890a added
4978 BM1 01234567890a added
4972 BM2 deleted
4979 BM2 deleted
4973 BM3 234567890abc advanced
4980 BM3 234567890abc advanced
4974 BM4 34567890abcd diverged
4981 BM4 34567890abcd diverged
4975 BM5 4567890abcde changed
4982 BM5 4567890abcde changed
4976
4983
4977 The action taken when pushing depends on the
4984 The action taken when pushing depends on the
4978 status of each bookmark:
4985 status of each bookmark:
4979
4986
4980 :``added``: push with ``-B`` will create it
4987 :``added``: push with ``-B`` will create it
4981 :``deleted``: push with ``-B`` will delete it
4988 :``deleted``: push with ``-B`` will delete it
4982 :``advanced``: push will update it
4989 :``advanced``: push will update it
4983 :``diverged``: push with ``-B`` will update it
4990 :``diverged``: push with ``-B`` will update it
4984 :``changed``: push with ``-B`` will update it
4991 :``changed``: push with ``-B`` will update it
4985
4992
4986 From the point of view of pushing behavior, bookmarks
4993 From the point of view of pushing behavior, bookmarks
4987 existing only in the remote repository are treated as
4994 existing only in the remote repository are treated as
4988 ``deleted``, even if it is in fact added remotely.
4995 ``deleted``, even if it is in fact added remotely.
4989
4996
4990 Returns 0 if there are outgoing changes, 1 otherwise.
4997 Returns 0 if there are outgoing changes, 1 otherwise.
4991 """
4998 """
4992 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4999 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4993 # style URLs, so don't overwrite dest.
5000 # style URLs, so don't overwrite dest.
4994 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5001 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4995 if not path:
5002 if not path:
4996 raise error.Abort(
5003 raise error.Abort(
4997 _(b'default repository not configured!'),
5004 _(b'default repository not configured!'),
4998 hint=_(b"see 'hg help config.paths'"),
5005 hint=_(b"see 'hg help config.paths'"),
4999 )
5006 )
5000
5007
5001 opts = pycompat.byteskwargs(opts)
5008 opts = pycompat.byteskwargs(opts)
5002 if opts.get(b'graph'):
5009 if opts.get(b'graph'):
5003 logcmdutil.checkunsupportedgraphflags([], opts)
5010 logcmdutil.checkunsupportedgraphflags([], opts)
5004 o, other = hg._outgoing(ui, repo, dest, opts)
5011 o, other = hg._outgoing(ui, repo, dest, opts)
5005 if not o:
5012 if not o:
5006 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5013 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5007 return
5014 return
5008
5015
5009 revdag = logcmdutil.graphrevs(repo, o, opts)
5016 revdag = logcmdutil.graphrevs(repo, o, opts)
5010 ui.pager(b'outgoing')
5017 ui.pager(b'outgoing')
5011 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5018 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5012 logcmdutil.displaygraph(
5019 logcmdutil.displaygraph(
5013 ui, repo, revdag, displayer, graphmod.asciiedges
5020 ui, repo, revdag, displayer, graphmod.asciiedges
5014 )
5021 )
5015 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5022 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5016 return 0
5023 return 0
5017
5024
5018 if opts.get(b'bookmarks'):
5025 if opts.get(b'bookmarks'):
5019 dest = path.pushloc or path.loc
5026 dest = path.pushloc or path.loc
5020 other = hg.peer(repo, opts, dest)
5027 other = hg.peer(repo, opts, dest)
5021 if b'bookmarks' not in other.listkeys(b'namespaces'):
5028 if b'bookmarks' not in other.listkeys(b'namespaces'):
5022 ui.warn(_(b"remote doesn't support bookmarks\n"))
5029 ui.warn(_(b"remote doesn't support bookmarks\n"))
5023 return 0
5030 return 0
5024 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5031 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5025 ui.pager(b'outgoing')
5032 ui.pager(b'outgoing')
5026 return bookmarks.outgoing(ui, repo, other)
5033 return bookmarks.outgoing(ui, repo, other)
5027
5034
5028 repo._subtoppath = path.pushloc or path.loc
5035 repo._subtoppath = path.pushloc or path.loc
5029 try:
5036 try:
5030 return hg.outgoing(ui, repo, dest, opts)
5037 return hg.outgoing(ui, repo, dest, opts)
5031 finally:
5038 finally:
5032 del repo._subtoppath
5039 del repo._subtoppath
5033
5040
5034
5041
5035 @command(
5042 @command(
5036 b'parents',
5043 b'parents',
5037 [
5044 [
5038 (
5045 (
5039 b'r',
5046 b'r',
5040 b'rev',
5047 b'rev',
5041 b'',
5048 b'',
5042 _(b'show parents of the specified revision'),
5049 _(b'show parents of the specified revision'),
5043 _(b'REV'),
5050 _(b'REV'),
5044 ),
5051 ),
5045 ]
5052 ]
5046 + templateopts,
5053 + templateopts,
5047 _(b'[-r REV] [FILE]'),
5054 _(b'[-r REV] [FILE]'),
5048 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5055 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5049 inferrepo=True,
5056 inferrepo=True,
5050 )
5057 )
5051 def parents(ui, repo, file_=None, **opts):
5058 def parents(ui, repo, file_=None, **opts):
5052 """show the parents of the working directory or revision (DEPRECATED)
5059 """show the parents of the working directory or revision (DEPRECATED)
5053
5060
5054 Print the working directory's parent revisions. If a revision is
5061 Print the working directory's parent revisions. If a revision is
5055 given via -r/--rev, the parent of that revision will be printed.
5062 given via -r/--rev, the parent of that revision will be printed.
5056 If a file argument is given, the revision in which the file was
5063 If a file argument is given, the revision in which the file was
5057 last changed (before the working directory revision or the
5064 last changed (before the working directory revision or the
5058 argument to --rev if given) is printed.
5065 argument to --rev if given) is printed.
5059
5066
5060 This command is equivalent to::
5067 This command is equivalent to::
5061
5068
5062 hg log -r "p1()+p2()" or
5069 hg log -r "p1()+p2()" or
5063 hg log -r "p1(REV)+p2(REV)" or
5070 hg log -r "p1(REV)+p2(REV)" or
5064 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5071 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5065 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5072 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5066
5073
5067 See :hg:`summary` and :hg:`help revsets` for related information.
5074 See :hg:`summary` and :hg:`help revsets` for related information.
5068
5075
5069 Returns 0 on success.
5076 Returns 0 on success.
5070 """
5077 """
5071
5078
5072 opts = pycompat.byteskwargs(opts)
5079 opts = pycompat.byteskwargs(opts)
5073 rev = opts.get(b'rev')
5080 rev = opts.get(b'rev')
5074 if rev:
5081 if rev:
5075 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5082 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5076 ctx = scmutil.revsingle(repo, rev, None)
5083 ctx = scmutil.revsingle(repo, rev, None)
5077
5084
5078 if file_:
5085 if file_:
5079 m = scmutil.match(ctx, (file_,), opts)
5086 m = scmutil.match(ctx, (file_,), opts)
5080 if m.anypats() or len(m.files()) != 1:
5087 if m.anypats() or len(m.files()) != 1:
5081 raise error.Abort(_(b'can only specify an explicit filename'))
5088 raise error.Abort(_(b'can only specify an explicit filename'))
5082 file_ = m.files()[0]
5089 file_ = m.files()[0]
5083 filenodes = []
5090 filenodes = []
5084 for cp in ctx.parents():
5091 for cp in ctx.parents():
5085 if not cp:
5092 if not cp:
5086 continue
5093 continue
5087 try:
5094 try:
5088 filenodes.append(cp.filenode(file_))
5095 filenodes.append(cp.filenode(file_))
5089 except error.LookupError:
5096 except error.LookupError:
5090 pass
5097 pass
5091 if not filenodes:
5098 if not filenodes:
5092 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5099 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5093 p = []
5100 p = []
5094 for fn in filenodes:
5101 for fn in filenodes:
5095 fctx = repo.filectx(file_, fileid=fn)
5102 fctx = repo.filectx(file_, fileid=fn)
5096 p.append(fctx.node())
5103 p.append(fctx.node())
5097 else:
5104 else:
5098 p = [cp.node() for cp in ctx.parents()]
5105 p = [cp.node() for cp in ctx.parents()]
5099
5106
5100 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5107 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5101 for n in p:
5108 for n in p:
5102 if n != nullid:
5109 if n != nullid:
5103 displayer.show(repo[n])
5110 displayer.show(repo[n])
5104 displayer.close()
5111 displayer.close()
5105
5112
5106
5113
5107 @command(
5114 @command(
5108 b'paths',
5115 b'paths',
5109 formatteropts,
5116 formatteropts,
5110 _(b'[NAME]'),
5117 _(b'[NAME]'),
5111 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5118 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5112 optionalrepo=True,
5119 optionalrepo=True,
5113 intents={INTENT_READONLY},
5120 intents={INTENT_READONLY},
5114 )
5121 )
5115 def paths(ui, repo, search=None, **opts):
5122 def paths(ui, repo, search=None, **opts):
5116 """show aliases for remote repositories
5123 """show aliases for remote repositories
5117
5124
5118 Show definition of symbolic path name NAME. If no name is given,
5125 Show definition of symbolic path name NAME. If no name is given,
5119 show definition of all available names.
5126 show definition of all available names.
5120
5127
5121 Option -q/--quiet suppresses all output when searching for NAME
5128 Option -q/--quiet suppresses all output when searching for NAME
5122 and shows only the path names when listing all definitions.
5129 and shows only the path names when listing all definitions.
5123
5130
5124 Path names are defined in the [paths] section of your
5131 Path names are defined in the [paths] section of your
5125 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5132 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5126 repository, ``.hg/hgrc`` is used, too.
5133 repository, ``.hg/hgrc`` is used, too.
5127
5134
5128 The path names ``default`` and ``default-push`` have a special
5135 The path names ``default`` and ``default-push`` have a special
5129 meaning. When performing a push or pull operation, they are used
5136 meaning. When performing a push or pull operation, they are used
5130 as fallbacks if no location is specified on the command-line.
5137 as fallbacks if no location is specified on the command-line.
5131 When ``default-push`` is set, it will be used for push and
5138 When ``default-push`` is set, it will be used for push and
5132 ``default`` will be used for pull; otherwise ``default`` is used
5139 ``default`` will be used for pull; otherwise ``default`` is used
5133 as the fallback for both. When cloning a repository, the clone
5140 as the fallback for both. When cloning a repository, the clone
5134 source is written as ``default`` in ``.hg/hgrc``.
5141 source is written as ``default`` in ``.hg/hgrc``.
5135
5142
5136 .. note::
5143 .. note::
5137
5144
5138 ``default`` and ``default-push`` apply to all inbound (e.g.
5145 ``default`` and ``default-push`` apply to all inbound (e.g.
5139 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5146 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5140 and :hg:`bundle`) operations.
5147 and :hg:`bundle`) operations.
5141
5148
5142 See :hg:`help urls` for more information.
5149 See :hg:`help urls` for more information.
5143
5150
5144 .. container:: verbose
5151 .. container:: verbose
5145
5152
5146 Template:
5153 Template:
5147
5154
5148 The following keywords are supported. See also :hg:`help templates`.
5155 The following keywords are supported. See also :hg:`help templates`.
5149
5156
5150 :name: String. Symbolic name of the path alias.
5157 :name: String. Symbolic name of the path alias.
5151 :pushurl: String. URL for push operations.
5158 :pushurl: String. URL for push operations.
5152 :url: String. URL or directory path for the other operations.
5159 :url: String. URL or directory path for the other operations.
5153
5160
5154 Returns 0 on success.
5161 Returns 0 on success.
5155 """
5162 """
5156
5163
5157 opts = pycompat.byteskwargs(opts)
5164 opts = pycompat.byteskwargs(opts)
5158 ui.pager(b'paths')
5165 ui.pager(b'paths')
5159 if search:
5166 if search:
5160 pathitems = [
5167 pathitems = [
5161 (name, path)
5168 (name, path)
5162 for name, path in pycompat.iteritems(ui.paths)
5169 for name, path in pycompat.iteritems(ui.paths)
5163 if name == search
5170 if name == search
5164 ]
5171 ]
5165 else:
5172 else:
5166 pathitems = sorted(pycompat.iteritems(ui.paths))
5173 pathitems = sorted(pycompat.iteritems(ui.paths))
5167
5174
5168 fm = ui.formatter(b'paths', opts)
5175 fm = ui.formatter(b'paths', opts)
5169 if fm.isplain():
5176 if fm.isplain():
5170 hidepassword = util.hidepassword
5177 hidepassword = util.hidepassword
5171 else:
5178 else:
5172 hidepassword = bytes
5179 hidepassword = bytes
5173 if ui.quiet:
5180 if ui.quiet:
5174 namefmt = b'%s\n'
5181 namefmt = b'%s\n'
5175 else:
5182 else:
5176 namefmt = b'%s = '
5183 namefmt = b'%s = '
5177 showsubopts = not search and not ui.quiet
5184 showsubopts = not search and not ui.quiet
5178
5185
5179 for name, path in pathitems:
5186 for name, path in pathitems:
5180 fm.startitem()
5187 fm.startitem()
5181 fm.condwrite(not search, b'name', namefmt, name)
5188 fm.condwrite(not search, b'name', namefmt, name)
5182 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5189 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5183 for subopt, value in sorted(path.suboptions.items()):
5190 for subopt, value in sorted(path.suboptions.items()):
5184 assert subopt not in (b'name', b'url')
5191 assert subopt not in (b'name', b'url')
5185 if showsubopts:
5192 if showsubopts:
5186 fm.plain(b'%s:%s = ' % (name, subopt))
5193 fm.plain(b'%s:%s = ' % (name, subopt))
5187 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5194 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5188
5195
5189 fm.end()
5196 fm.end()
5190
5197
5191 if search and not pathitems:
5198 if search and not pathitems:
5192 if not ui.quiet:
5199 if not ui.quiet:
5193 ui.warn(_(b"not found!\n"))
5200 ui.warn(_(b"not found!\n"))
5194 return 1
5201 return 1
5195 else:
5202 else:
5196 return 0
5203 return 0
5197
5204
5198
5205
5199 @command(
5206 @command(
5200 b'phase',
5207 b'phase',
5201 [
5208 [
5202 (b'p', b'public', False, _(b'set changeset phase to public')),
5209 (b'p', b'public', False, _(b'set changeset phase to public')),
5203 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5210 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5204 (b's', b'secret', False, _(b'set changeset phase to secret')),
5211 (b's', b'secret', False, _(b'set changeset phase to secret')),
5205 (b'f', b'force', False, _(b'allow to move boundary backward')),
5212 (b'f', b'force', False, _(b'allow to move boundary backward')),
5206 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5213 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5207 ],
5214 ],
5208 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5215 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5209 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5216 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5210 )
5217 )
5211 def phase(ui, repo, *revs, **opts):
5218 def phase(ui, repo, *revs, **opts):
5212 """set or show the current phase name
5219 """set or show the current phase name
5213
5220
5214 With no argument, show the phase name of the current revision(s).
5221 With no argument, show the phase name of the current revision(s).
5215
5222
5216 With one of -p/--public, -d/--draft or -s/--secret, change the
5223 With one of -p/--public, -d/--draft or -s/--secret, change the
5217 phase value of the specified revisions.
5224 phase value of the specified revisions.
5218
5225
5219 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5226 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5220 lower phase to a higher phase. Phases are ordered as follows::
5227 lower phase to a higher phase. Phases are ordered as follows::
5221
5228
5222 public < draft < secret
5229 public < draft < secret
5223
5230
5224 Returns 0 on success, 1 if some phases could not be changed.
5231 Returns 0 on success, 1 if some phases could not be changed.
5225
5232
5226 (For more information about the phases concept, see :hg:`help phases`.)
5233 (For more information about the phases concept, see :hg:`help phases`.)
5227 """
5234 """
5228 opts = pycompat.byteskwargs(opts)
5235 opts = pycompat.byteskwargs(opts)
5229 # search for a unique phase argument
5236 # search for a unique phase argument
5230 targetphase = None
5237 targetphase = None
5231 for idx, name in enumerate(phases.cmdphasenames):
5238 for idx, name in enumerate(phases.cmdphasenames):
5232 if opts[name]:
5239 if opts[name]:
5233 if targetphase is not None:
5240 if targetphase is not None:
5234 raise error.Abort(_(b'only one phase can be specified'))
5241 raise error.Abort(_(b'only one phase can be specified'))
5235 targetphase = idx
5242 targetphase = idx
5236
5243
5237 # look for specified revision
5244 # look for specified revision
5238 revs = list(revs)
5245 revs = list(revs)
5239 revs.extend(opts[b'rev'])
5246 revs.extend(opts[b'rev'])
5240 if not revs:
5247 if not revs:
5241 # display both parents as the second parent phase can influence
5248 # display both parents as the second parent phase can influence
5242 # the phase of a merge commit
5249 # the phase of a merge commit
5243 revs = [c.rev() for c in repo[None].parents()]
5250 revs = [c.rev() for c in repo[None].parents()]
5244
5251
5245 revs = scmutil.revrange(repo, revs)
5252 revs = scmutil.revrange(repo, revs)
5246
5253
5247 ret = 0
5254 ret = 0
5248 if targetphase is None:
5255 if targetphase is None:
5249 # display
5256 # display
5250 for r in revs:
5257 for r in revs:
5251 ctx = repo[r]
5258 ctx = repo[r]
5252 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5259 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5253 else:
5260 else:
5254 with repo.lock(), repo.transaction(b"phase") as tr:
5261 with repo.lock(), repo.transaction(b"phase") as tr:
5255 # set phase
5262 # set phase
5256 if not revs:
5263 if not revs:
5257 raise error.Abort(_(b'empty revision set'))
5264 raise error.Abort(_(b'empty revision set'))
5258 nodes = [repo[r].node() for r in revs]
5265 nodes = [repo[r].node() for r in revs]
5259 # moving revision from public to draft may hide them
5266 # moving revision from public to draft may hide them
5260 # We have to check result on an unfiltered repository
5267 # We have to check result on an unfiltered repository
5261 unfi = repo.unfiltered()
5268 unfi = repo.unfiltered()
5262 getphase = unfi._phasecache.phase
5269 getphase = unfi._phasecache.phase
5263 olddata = [getphase(unfi, r) for r in unfi]
5270 olddata = [getphase(unfi, r) for r in unfi]
5264 phases.advanceboundary(repo, tr, targetphase, nodes)
5271 phases.advanceboundary(repo, tr, targetphase, nodes)
5265 if opts[b'force']:
5272 if opts[b'force']:
5266 phases.retractboundary(repo, tr, targetphase, nodes)
5273 phases.retractboundary(repo, tr, targetphase, nodes)
5267 getphase = unfi._phasecache.phase
5274 getphase = unfi._phasecache.phase
5268 newdata = [getphase(unfi, r) for r in unfi]
5275 newdata = [getphase(unfi, r) for r in unfi]
5269 changes = sum(newdata[r] != olddata[r] for r in unfi)
5276 changes = sum(newdata[r] != olddata[r] for r in unfi)
5270 cl = unfi.changelog
5277 cl = unfi.changelog
5271 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5278 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5272 if rejected:
5279 if rejected:
5273 ui.warn(
5280 ui.warn(
5274 _(
5281 _(
5275 b'cannot move %i changesets to a higher '
5282 b'cannot move %i changesets to a higher '
5276 b'phase, use --force\n'
5283 b'phase, use --force\n'
5277 )
5284 )
5278 % len(rejected)
5285 % len(rejected)
5279 )
5286 )
5280 ret = 1
5287 ret = 1
5281 if changes:
5288 if changes:
5282 msg = _(b'phase changed for %i changesets\n') % changes
5289 msg = _(b'phase changed for %i changesets\n') % changes
5283 if ret:
5290 if ret:
5284 ui.status(msg)
5291 ui.status(msg)
5285 else:
5292 else:
5286 ui.note(msg)
5293 ui.note(msg)
5287 else:
5294 else:
5288 ui.warn(_(b'no phases changed\n'))
5295 ui.warn(_(b'no phases changed\n'))
5289 return ret
5296 return ret
5290
5297
5291
5298
5292 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5299 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5293 """Run after a changegroup has been added via pull/unbundle
5300 """Run after a changegroup has been added via pull/unbundle
5294
5301
5295 This takes arguments below:
5302 This takes arguments below:
5296
5303
5297 :modheads: change of heads by pull/unbundle
5304 :modheads: change of heads by pull/unbundle
5298 :optupdate: updating working directory is needed or not
5305 :optupdate: updating working directory is needed or not
5299 :checkout: update destination revision (or None to default destination)
5306 :checkout: update destination revision (or None to default destination)
5300 :brev: a name, which might be a bookmark to be activated after updating
5307 :brev: a name, which might be a bookmark to be activated after updating
5301 """
5308 """
5302 if modheads == 0:
5309 if modheads == 0:
5303 return
5310 return
5304 if optupdate:
5311 if optupdate:
5305 try:
5312 try:
5306 return hg.updatetotally(ui, repo, checkout, brev)
5313 return hg.updatetotally(ui, repo, checkout, brev)
5307 except error.UpdateAbort as inst:
5314 except error.UpdateAbort as inst:
5308 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5315 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5309 hint = inst.hint
5316 hint = inst.hint
5310 raise error.UpdateAbort(msg, hint=hint)
5317 raise error.UpdateAbort(msg, hint=hint)
5311 if modheads is not None and modheads > 1:
5318 if modheads is not None and modheads > 1:
5312 currentbranchheads = len(repo.branchheads())
5319 currentbranchheads = len(repo.branchheads())
5313 if currentbranchheads == modheads:
5320 if currentbranchheads == modheads:
5314 ui.status(
5321 ui.status(
5315 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5322 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5316 )
5323 )
5317 elif currentbranchheads > 1:
5324 elif currentbranchheads > 1:
5318 ui.status(
5325 ui.status(
5319 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5326 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5320 )
5327 )
5321 else:
5328 else:
5322 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5329 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5323 elif not ui.configbool(b'commands', b'update.requiredest'):
5330 elif not ui.configbool(b'commands', b'update.requiredest'):
5324 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5331 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5325
5332
5326
5333
5327 @command(
5334 @command(
5328 b'pull',
5335 b'pull',
5329 [
5336 [
5330 (
5337 (
5331 b'u',
5338 b'u',
5332 b'update',
5339 b'update',
5333 None,
5340 None,
5334 _(b'update to new branch head if new descendants were pulled'),
5341 _(b'update to new branch head if new descendants were pulled'),
5335 ),
5342 ),
5336 (
5343 (
5337 b'f',
5344 b'f',
5338 b'force',
5345 b'force',
5339 None,
5346 None,
5340 _(b'run even when remote repository is unrelated'),
5347 _(b'run even when remote repository is unrelated'),
5341 ),
5348 ),
5342 (
5349 (
5343 b'r',
5350 b'r',
5344 b'rev',
5351 b'rev',
5345 [],
5352 [],
5346 _(b'a remote changeset intended to be added'),
5353 _(b'a remote changeset intended to be added'),
5347 _(b'REV'),
5354 _(b'REV'),
5348 ),
5355 ),
5349 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5356 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5350 (
5357 (
5351 b'b',
5358 b'b',
5352 b'branch',
5359 b'branch',
5353 [],
5360 [],
5354 _(b'a specific branch you would like to pull'),
5361 _(b'a specific branch you would like to pull'),
5355 _(b'BRANCH'),
5362 _(b'BRANCH'),
5356 ),
5363 ),
5357 ]
5364 ]
5358 + remoteopts,
5365 + remoteopts,
5359 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5366 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5360 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5367 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5361 helpbasic=True,
5368 helpbasic=True,
5362 )
5369 )
5363 def pull(ui, repo, source=b"default", **opts):
5370 def pull(ui, repo, source=b"default", **opts):
5364 """pull changes from the specified source
5371 """pull changes from the specified source
5365
5372
5366 Pull changes from a remote repository to a local one.
5373 Pull changes from a remote repository to a local one.
5367
5374
5368 This finds all changes from the repository at the specified path
5375 This finds all changes from the repository at the specified path
5369 or URL and adds them to a local repository (the current one unless
5376 or URL and adds them to a local repository (the current one unless
5370 -R is specified). By default, this does not update the copy of the
5377 -R is specified). By default, this does not update the copy of the
5371 project in the working directory.
5378 project in the working directory.
5372
5379
5373 When cloning from servers that support it, Mercurial may fetch
5380 When cloning from servers that support it, Mercurial may fetch
5374 pre-generated data. When this is done, hooks operating on incoming
5381 pre-generated data. When this is done, hooks operating on incoming
5375 changesets and changegroups may fire more than once, once for each
5382 changesets and changegroups may fire more than once, once for each
5376 pre-generated bundle and as well as for any additional remaining
5383 pre-generated bundle and as well as for any additional remaining
5377 data. See :hg:`help -e clonebundles` for more.
5384 data. See :hg:`help -e clonebundles` for more.
5378
5385
5379 Use :hg:`incoming` if you want to see what would have been added
5386 Use :hg:`incoming` if you want to see what would have been added
5380 by a pull at the time you issued this command. If you then decide
5387 by a pull at the time you issued this command. If you then decide
5381 to add those changes to the repository, you should use :hg:`pull
5388 to add those changes to the repository, you should use :hg:`pull
5382 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5389 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5383
5390
5384 If SOURCE is omitted, the 'default' path will be used.
5391 If SOURCE is omitted, the 'default' path will be used.
5385 See :hg:`help urls` for more information.
5392 See :hg:`help urls` for more information.
5386
5393
5387 Specifying bookmark as ``.`` is equivalent to specifying the active
5394 Specifying bookmark as ``.`` is equivalent to specifying the active
5388 bookmark's name.
5395 bookmark's name.
5389
5396
5390 Returns 0 on success, 1 if an update had unresolved files.
5397 Returns 0 on success, 1 if an update had unresolved files.
5391 """
5398 """
5392
5399
5393 opts = pycompat.byteskwargs(opts)
5400 opts = pycompat.byteskwargs(opts)
5394 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5401 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5395 b'update'
5402 b'update'
5396 ):
5403 ):
5397 msg = _(b'update destination required by configuration')
5404 msg = _(b'update destination required by configuration')
5398 hint = _(b'use hg pull followed by hg update DEST')
5405 hint = _(b'use hg pull followed by hg update DEST')
5399 raise error.Abort(msg, hint=hint)
5406 raise error.Abort(msg, hint=hint)
5400
5407
5401 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5408 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5402 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5409 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5403 other = hg.peer(repo, opts, source)
5410 other = hg.peer(repo, opts, source)
5404 try:
5411 try:
5405 revs, checkout = hg.addbranchrevs(
5412 revs, checkout = hg.addbranchrevs(
5406 repo, other, branches, opts.get(b'rev')
5413 repo, other, branches, opts.get(b'rev')
5407 )
5414 )
5408
5415
5409 pullopargs = {}
5416 pullopargs = {}
5410
5417
5411 nodes = None
5418 nodes = None
5412 if opts.get(b'bookmark') or revs:
5419 if opts.get(b'bookmark') or revs:
5413 # The list of bookmark used here is the same used to actually update
5420 # The list of bookmark used here is the same used to actually update
5414 # the bookmark names, to avoid the race from issue 4689 and we do
5421 # the bookmark names, to avoid the race from issue 4689 and we do
5415 # all lookup and bookmark queries in one go so they see the same
5422 # all lookup and bookmark queries in one go so they see the same
5416 # version of the server state (issue 4700).
5423 # version of the server state (issue 4700).
5417 nodes = []
5424 nodes = []
5418 fnodes = []
5425 fnodes = []
5419 revs = revs or []
5426 revs = revs or []
5420 if revs and not other.capable(b'lookup'):
5427 if revs and not other.capable(b'lookup'):
5421 err = _(
5428 err = _(
5422 b"other repository doesn't support revision lookup, "
5429 b"other repository doesn't support revision lookup, "
5423 b"so a rev cannot be specified."
5430 b"so a rev cannot be specified."
5424 )
5431 )
5425 raise error.Abort(err)
5432 raise error.Abort(err)
5426 with other.commandexecutor() as e:
5433 with other.commandexecutor() as e:
5427 fremotebookmarks = e.callcommand(
5434 fremotebookmarks = e.callcommand(
5428 b'listkeys', {b'namespace': b'bookmarks'}
5435 b'listkeys', {b'namespace': b'bookmarks'}
5429 )
5436 )
5430 for r in revs:
5437 for r in revs:
5431 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5438 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5432 remotebookmarks = fremotebookmarks.result()
5439 remotebookmarks = fremotebookmarks.result()
5433 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5440 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5434 pullopargs[b'remotebookmarks'] = remotebookmarks
5441 pullopargs[b'remotebookmarks'] = remotebookmarks
5435 for b in opts.get(b'bookmark', []):
5442 for b in opts.get(b'bookmark', []):
5436 b = repo._bookmarks.expandname(b)
5443 b = repo._bookmarks.expandname(b)
5437 if b not in remotebookmarks:
5444 if b not in remotebookmarks:
5438 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5445 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5439 nodes.append(remotebookmarks[b])
5446 nodes.append(remotebookmarks[b])
5440 for i, rev in enumerate(revs):
5447 for i, rev in enumerate(revs):
5441 node = fnodes[i].result()
5448 node = fnodes[i].result()
5442 nodes.append(node)
5449 nodes.append(node)
5443 if rev == checkout:
5450 if rev == checkout:
5444 checkout = node
5451 checkout = node
5445
5452
5446 wlock = util.nullcontextmanager()
5453 wlock = util.nullcontextmanager()
5447 if opts.get(b'update'):
5454 if opts.get(b'update'):
5448 wlock = repo.wlock()
5455 wlock = repo.wlock()
5449 with wlock:
5456 with wlock:
5450 pullopargs.update(opts.get(b'opargs', {}))
5457 pullopargs.update(opts.get(b'opargs', {}))
5451 modheads = exchange.pull(
5458 modheads = exchange.pull(
5452 repo,
5459 repo,
5453 other,
5460 other,
5454 heads=nodes,
5461 heads=nodes,
5455 force=opts.get(b'force'),
5462 force=opts.get(b'force'),
5456 bookmarks=opts.get(b'bookmark', ()),
5463 bookmarks=opts.get(b'bookmark', ()),
5457 opargs=pullopargs,
5464 opargs=pullopargs,
5458 ).cgresult
5465 ).cgresult
5459
5466
5460 # brev is a name, which might be a bookmark to be activated at
5467 # brev is a name, which might be a bookmark to be activated at
5461 # the end of the update. In other words, it is an explicit
5468 # the end of the update. In other words, it is an explicit
5462 # destination of the update
5469 # destination of the update
5463 brev = None
5470 brev = None
5464
5471
5465 if checkout:
5472 if checkout:
5466 checkout = repo.unfiltered().changelog.rev(checkout)
5473 checkout = repo.unfiltered().changelog.rev(checkout)
5467
5474
5468 # order below depends on implementation of
5475 # order below depends on implementation of
5469 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5476 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5470 # because 'checkout' is determined without it.
5477 # because 'checkout' is determined without it.
5471 if opts.get(b'rev'):
5478 if opts.get(b'rev'):
5472 brev = opts[b'rev'][0]
5479 brev = opts[b'rev'][0]
5473 elif opts.get(b'branch'):
5480 elif opts.get(b'branch'):
5474 brev = opts[b'branch'][0]
5481 brev = opts[b'branch'][0]
5475 else:
5482 else:
5476 brev = branches[0]
5483 brev = branches[0]
5477 repo._subtoppath = source
5484 repo._subtoppath = source
5478 try:
5485 try:
5479 ret = postincoming(
5486 ret = postincoming(
5480 ui, repo, modheads, opts.get(b'update'), checkout, brev
5487 ui, repo, modheads, opts.get(b'update'), checkout, brev
5481 )
5488 )
5482 except error.FilteredRepoLookupError as exc:
5489 except error.FilteredRepoLookupError as exc:
5483 msg = _(b'cannot update to target: %s') % exc.args[0]
5490 msg = _(b'cannot update to target: %s') % exc.args[0]
5484 exc.args = (msg,) + exc.args[1:]
5491 exc.args = (msg,) + exc.args[1:]
5485 raise
5492 raise
5486 finally:
5493 finally:
5487 del repo._subtoppath
5494 del repo._subtoppath
5488
5495
5489 finally:
5496 finally:
5490 other.close()
5497 other.close()
5491 return ret
5498 return ret
5492
5499
5493
5500
5494 @command(
5501 @command(
5495 b'push',
5502 b'push',
5496 [
5503 [
5497 (b'f', b'force', None, _(b'force push')),
5504 (b'f', b'force', None, _(b'force push')),
5498 (
5505 (
5499 b'r',
5506 b'r',
5500 b'rev',
5507 b'rev',
5501 [],
5508 [],
5502 _(b'a changeset intended to be included in the destination'),
5509 _(b'a changeset intended to be included in the destination'),
5503 _(b'REV'),
5510 _(b'REV'),
5504 ),
5511 ),
5505 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5512 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5506 (
5513 (
5507 b'b',
5514 b'b',
5508 b'branch',
5515 b'branch',
5509 [],
5516 [],
5510 _(b'a specific branch you would like to push'),
5517 _(b'a specific branch you would like to push'),
5511 _(b'BRANCH'),
5518 _(b'BRANCH'),
5512 ),
5519 ),
5513 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5520 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5514 (
5521 (
5515 b'',
5522 b'',
5516 b'pushvars',
5523 b'pushvars',
5517 [],
5524 [],
5518 _(b'variables that can be sent to server (ADVANCED)'),
5525 _(b'variables that can be sent to server (ADVANCED)'),
5519 ),
5526 ),
5520 (
5527 (
5521 b'',
5528 b'',
5522 b'publish',
5529 b'publish',
5523 False,
5530 False,
5524 _(b'push the changeset as public (EXPERIMENTAL)'),
5531 _(b'push the changeset as public (EXPERIMENTAL)'),
5525 ),
5532 ),
5526 ]
5533 ]
5527 + remoteopts,
5534 + remoteopts,
5528 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5535 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5529 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5536 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5530 helpbasic=True,
5537 helpbasic=True,
5531 )
5538 )
5532 def push(ui, repo, dest=None, **opts):
5539 def push(ui, repo, dest=None, **opts):
5533 """push changes to the specified destination
5540 """push changes to the specified destination
5534
5541
5535 Push changesets from the local repository to the specified
5542 Push changesets from the local repository to the specified
5536 destination.
5543 destination.
5537
5544
5538 This operation is symmetrical to pull: it is identical to a pull
5545 This operation is symmetrical to pull: it is identical to a pull
5539 in the destination repository from the current one.
5546 in the destination repository from the current one.
5540
5547
5541 By default, push will not allow creation of new heads at the
5548 By default, push will not allow creation of new heads at the
5542 destination, since multiple heads would make it unclear which head
5549 destination, since multiple heads would make it unclear which head
5543 to use. In this situation, it is recommended to pull and merge
5550 to use. In this situation, it is recommended to pull and merge
5544 before pushing.
5551 before pushing.
5545
5552
5546 Use --new-branch if you want to allow push to create a new named
5553 Use --new-branch if you want to allow push to create a new named
5547 branch that is not present at the destination. This allows you to
5554 branch that is not present at the destination. This allows you to
5548 only create a new branch without forcing other changes.
5555 only create a new branch without forcing other changes.
5549
5556
5550 .. note::
5557 .. note::
5551
5558
5552 Extra care should be taken with the -f/--force option,
5559 Extra care should be taken with the -f/--force option,
5553 which will push all new heads on all branches, an action which will
5560 which will push all new heads on all branches, an action which will
5554 almost always cause confusion for collaborators.
5561 almost always cause confusion for collaborators.
5555
5562
5556 If -r/--rev is used, the specified revision and all its ancestors
5563 If -r/--rev is used, the specified revision and all its ancestors
5557 will be pushed to the remote repository.
5564 will be pushed to the remote repository.
5558
5565
5559 If -B/--bookmark is used, the specified bookmarked revision, its
5566 If -B/--bookmark is used, the specified bookmarked revision, its
5560 ancestors, and the bookmark will be pushed to the remote
5567 ancestors, and the bookmark will be pushed to the remote
5561 repository. Specifying ``.`` is equivalent to specifying the active
5568 repository. Specifying ``.`` is equivalent to specifying the active
5562 bookmark's name.
5569 bookmark's name.
5563
5570
5564 Please see :hg:`help urls` for important details about ``ssh://``
5571 Please see :hg:`help urls` for important details about ``ssh://``
5565 URLs. If DESTINATION is omitted, a default path will be used.
5572 URLs. If DESTINATION is omitted, a default path will be used.
5566
5573
5567 .. container:: verbose
5574 .. container:: verbose
5568
5575
5569 The --pushvars option sends strings to the server that become
5576 The --pushvars option sends strings to the server that become
5570 environment variables prepended with ``HG_USERVAR_``. For example,
5577 environment variables prepended with ``HG_USERVAR_``. For example,
5571 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5578 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5572 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5579 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5573
5580
5574 pushvars can provide for user-overridable hooks as well as set debug
5581 pushvars can provide for user-overridable hooks as well as set debug
5575 levels. One example is having a hook that blocks commits containing
5582 levels. One example is having a hook that blocks commits containing
5576 conflict markers, but enables the user to override the hook if the file
5583 conflict markers, but enables the user to override the hook if the file
5577 is using conflict markers for testing purposes or the file format has
5584 is using conflict markers for testing purposes or the file format has
5578 strings that look like conflict markers.
5585 strings that look like conflict markers.
5579
5586
5580 By default, servers will ignore `--pushvars`. To enable it add the
5587 By default, servers will ignore `--pushvars`. To enable it add the
5581 following to your configuration file::
5588 following to your configuration file::
5582
5589
5583 [push]
5590 [push]
5584 pushvars.server = true
5591 pushvars.server = true
5585
5592
5586 Returns 0 if push was successful, 1 if nothing to push.
5593 Returns 0 if push was successful, 1 if nothing to push.
5587 """
5594 """
5588
5595
5589 opts = pycompat.byteskwargs(opts)
5596 opts = pycompat.byteskwargs(opts)
5590 if opts.get(b'bookmark'):
5597 if opts.get(b'bookmark'):
5591 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5598 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5592 for b in opts[b'bookmark']:
5599 for b in opts[b'bookmark']:
5593 # translate -B options to -r so changesets get pushed
5600 # translate -B options to -r so changesets get pushed
5594 b = repo._bookmarks.expandname(b)
5601 b = repo._bookmarks.expandname(b)
5595 if b in repo._bookmarks:
5602 if b in repo._bookmarks:
5596 opts.setdefault(b'rev', []).append(b)
5603 opts.setdefault(b'rev', []).append(b)
5597 else:
5604 else:
5598 # if we try to push a deleted bookmark, translate it to null
5605 # if we try to push a deleted bookmark, translate it to null
5599 # this lets simultaneous -r, -b options continue working
5606 # this lets simultaneous -r, -b options continue working
5600 opts.setdefault(b'rev', []).append(b"null")
5607 opts.setdefault(b'rev', []).append(b"null")
5601
5608
5602 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5609 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5603 if not path:
5610 if not path:
5604 raise error.Abort(
5611 raise error.Abort(
5605 _(b'default repository not configured!'),
5612 _(b'default repository not configured!'),
5606 hint=_(b"see 'hg help config.paths'"),
5613 hint=_(b"see 'hg help config.paths'"),
5607 )
5614 )
5608 dest = path.pushloc or path.loc
5615 dest = path.pushloc or path.loc
5609 branches = (path.branch, opts.get(b'branch') or [])
5616 branches = (path.branch, opts.get(b'branch') or [])
5610 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5617 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5611 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5618 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5612 other = hg.peer(repo, opts, dest)
5619 other = hg.peer(repo, opts, dest)
5613
5620
5614 if revs:
5621 if revs:
5615 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5622 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5616 if not revs:
5623 if not revs:
5617 raise error.Abort(
5624 raise error.Abort(
5618 _(b"specified revisions evaluate to an empty set"),
5625 _(b"specified revisions evaluate to an empty set"),
5619 hint=_(b"use different revision arguments"),
5626 hint=_(b"use different revision arguments"),
5620 )
5627 )
5621 elif path.pushrev:
5628 elif path.pushrev:
5622 # It doesn't make any sense to specify ancestor revisions. So limit
5629 # It doesn't make any sense to specify ancestor revisions. So limit
5623 # to DAG heads to make discovery simpler.
5630 # to DAG heads to make discovery simpler.
5624 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5631 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5625 revs = scmutil.revrange(repo, [expr])
5632 revs = scmutil.revrange(repo, [expr])
5626 revs = [repo[rev].node() for rev in revs]
5633 revs = [repo[rev].node() for rev in revs]
5627 if not revs:
5634 if not revs:
5628 raise error.Abort(
5635 raise error.Abort(
5629 _(b'default push revset for path evaluates to an empty set')
5636 _(b'default push revset for path evaluates to an empty set')
5630 )
5637 )
5631 elif ui.configbool(b'commands', b'push.require-revs'):
5638 elif ui.configbool(b'commands', b'push.require-revs'):
5632 raise error.Abort(
5639 raise error.Abort(
5633 _(b'no revisions specified to push'),
5640 _(b'no revisions specified to push'),
5634 hint=_(b'did you mean "hg push -r ."?'),
5641 hint=_(b'did you mean "hg push -r ."?'),
5635 )
5642 )
5636
5643
5637 repo._subtoppath = dest
5644 repo._subtoppath = dest
5638 try:
5645 try:
5639 # push subrepos depth-first for coherent ordering
5646 # push subrepos depth-first for coherent ordering
5640 c = repo[b'.']
5647 c = repo[b'.']
5641 subs = c.substate # only repos that are committed
5648 subs = c.substate # only repos that are committed
5642 for s in sorted(subs):
5649 for s in sorted(subs):
5643 result = c.sub(s).push(opts)
5650 result = c.sub(s).push(opts)
5644 if result == 0:
5651 if result == 0:
5645 return not result
5652 return not result
5646 finally:
5653 finally:
5647 del repo._subtoppath
5654 del repo._subtoppath
5648
5655
5649 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5656 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5650 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5657 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5651
5658
5652 pushop = exchange.push(
5659 pushop = exchange.push(
5653 repo,
5660 repo,
5654 other,
5661 other,
5655 opts.get(b'force'),
5662 opts.get(b'force'),
5656 revs=revs,
5663 revs=revs,
5657 newbranch=opts.get(b'new_branch'),
5664 newbranch=opts.get(b'new_branch'),
5658 bookmarks=opts.get(b'bookmark', ()),
5665 bookmarks=opts.get(b'bookmark', ()),
5659 publish=opts.get(b'publish'),
5666 publish=opts.get(b'publish'),
5660 opargs=opargs,
5667 opargs=opargs,
5661 )
5668 )
5662
5669
5663 result = not pushop.cgresult
5670 result = not pushop.cgresult
5664
5671
5665 if pushop.bkresult is not None:
5672 if pushop.bkresult is not None:
5666 if pushop.bkresult == 2:
5673 if pushop.bkresult == 2:
5667 result = 2
5674 result = 2
5668 elif not result and pushop.bkresult:
5675 elif not result and pushop.bkresult:
5669 result = 2
5676 result = 2
5670
5677
5671 return result
5678 return result
5672
5679
5673
5680
5674 @command(
5681 @command(
5675 b'recover',
5682 b'recover',
5676 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5683 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5677 helpcategory=command.CATEGORY_MAINTENANCE,
5684 helpcategory=command.CATEGORY_MAINTENANCE,
5678 )
5685 )
5679 def recover(ui, repo, **opts):
5686 def recover(ui, repo, **opts):
5680 """roll back an interrupted transaction
5687 """roll back an interrupted transaction
5681
5688
5682 Recover from an interrupted commit or pull.
5689 Recover from an interrupted commit or pull.
5683
5690
5684 This command tries to fix the repository status after an
5691 This command tries to fix the repository status after an
5685 interrupted operation. It should only be necessary when Mercurial
5692 interrupted operation. It should only be necessary when Mercurial
5686 suggests it.
5693 suggests it.
5687
5694
5688 Returns 0 if successful, 1 if nothing to recover or verify fails.
5695 Returns 0 if successful, 1 if nothing to recover or verify fails.
5689 """
5696 """
5690 ret = repo.recover()
5697 ret = repo.recover()
5691 if ret:
5698 if ret:
5692 if opts['verify']:
5699 if opts['verify']:
5693 return hg.verify(repo)
5700 return hg.verify(repo)
5694 else:
5701 else:
5695 msg = _(
5702 msg = _(
5696 b"(verify step skipped, run `hg verify` to check your "
5703 b"(verify step skipped, run `hg verify` to check your "
5697 b"repository content)\n"
5704 b"repository content)\n"
5698 )
5705 )
5699 ui.warn(msg)
5706 ui.warn(msg)
5700 return 0
5707 return 0
5701 return 1
5708 return 1
5702
5709
5703
5710
5704 @command(
5711 @command(
5705 b'remove|rm',
5712 b'remove|rm',
5706 [
5713 [
5707 (b'A', b'after', None, _(b'record delete for missing files')),
5714 (b'A', b'after', None, _(b'record delete for missing files')),
5708 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5715 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5709 ]
5716 ]
5710 + subrepoopts
5717 + subrepoopts
5711 + walkopts
5718 + walkopts
5712 + dryrunopts,
5719 + dryrunopts,
5713 _(b'[OPTION]... FILE...'),
5720 _(b'[OPTION]... FILE...'),
5714 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5721 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5715 helpbasic=True,
5722 helpbasic=True,
5716 inferrepo=True,
5723 inferrepo=True,
5717 )
5724 )
5718 def remove(ui, repo, *pats, **opts):
5725 def remove(ui, repo, *pats, **opts):
5719 """remove the specified files on the next commit
5726 """remove the specified files on the next commit
5720
5727
5721 Schedule the indicated files for removal from the current branch.
5728 Schedule the indicated files for removal from the current branch.
5722
5729
5723 This command schedules the files to be removed at the next commit.
5730 This command schedules the files to be removed at the next commit.
5724 To undo a remove before that, see :hg:`revert`. To undo added
5731 To undo a remove before that, see :hg:`revert`. To undo added
5725 files, see :hg:`forget`.
5732 files, see :hg:`forget`.
5726
5733
5727 .. container:: verbose
5734 .. container:: verbose
5728
5735
5729 -A/--after can be used to remove only files that have already
5736 -A/--after can be used to remove only files that have already
5730 been deleted, -f/--force can be used to force deletion, and -Af
5737 been deleted, -f/--force can be used to force deletion, and -Af
5731 can be used to remove files from the next revision without
5738 can be used to remove files from the next revision without
5732 deleting them from the working directory.
5739 deleting them from the working directory.
5733
5740
5734 The following table details the behavior of remove for different
5741 The following table details the behavior of remove for different
5735 file states (columns) and option combinations (rows). The file
5742 file states (columns) and option combinations (rows). The file
5736 states are Added [A], Clean [C], Modified [M] and Missing [!]
5743 states are Added [A], Clean [C], Modified [M] and Missing [!]
5737 (as reported by :hg:`status`). The actions are Warn, Remove
5744 (as reported by :hg:`status`). The actions are Warn, Remove
5738 (from branch) and Delete (from disk):
5745 (from branch) and Delete (from disk):
5739
5746
5740 ========= == == == ==
5747 ========= == == == ==
5741 opt/state A C M !
5748 opt/state A C M !
5742 ========= == == == ==
5749 ========= == == == ==
5743 none W RD W R
5750 none W RD W R
5744 -f R RD RD R
5751 -f R RD RD R
5745 -A W W W R
5752 -A W W W R
5746 -Af R R R R
5753 -Af R R R R
5747 ========= == == == ==
5754 ========= == == == ==
5748
5755
5749 .. note::
5756 .. note::
5750
5757
5751 :hg:`remove` never deletes files in Added [A] state from the
5758 :hg:`remove` never deletes files in Added [A] state from the
5752 working directory, not even if ``--force`` is specified.
5759 working directory, not even if ``--force`` is specified.
5753
5760
5754 Returns 0 on success, 1 if any warnings encountered.
5761 Returns 0 on success, 1 if any warnings encountered.
5755 """
5762 """
5756
5763
5757 opts = pycompat.byteskwargs(opts)
5764 opts = pycompat.byteskwargs(opts)
5758 after, force = opts.get(b'after'), opts.get(b'force')
5765 after, force = opts.get(b'after'), opts.get(b'force')
5759 dryrun = opts.get(b'dry_run')
5766 dryrun = opts.get(b'dry_run')
5760 if not pats and not after:
5767 if not pats and not after:
5761 raise error.Abort(_(b'no files specified'))
5768 raise error.Abort(_(b'no files specified'))
5762
5769
5763 m = scmutil.match(repo[None], pats, opts)
5770 m = scmutil.match(repo[None], pats, opts)
5764 subrepos = opts.get(b'subrepos')
5771 subrepos = opts.get(b'subrepos')
5765 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5772 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5766 return cmdutil.remove(
5773 return cmdutil.remove(
5767 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5774 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5768 )
5775 )
5769
5776
5770
5777
5771 @command(
5778 @command(
5772 b'rename|move|mv',
5779 b'rename|move|mv',
5773 [
5780 [
5774 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5781 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5775 (
5782 (
5776 b'f',
5783 b'f',
5777 b'force',
5784 b'force',
5778 None,
5785 None,
5779 _(b'forcibly move over an existing managed file'),
5786 _(b'forcibly move over an existing managed file'),
5780 ),
5787 ),
5781 ]
5788 ]
5782 + walkopts
5789 + walkopts
5783 + dryrunopts,
5790 + dryrunopts,
5784 _(b'[OPTION]... SOURCE... DEST'),
5791 _(b'[OPTION]... SOURCE... DEST'),
5785 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5792 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5786 )
5793 )
5787 def rename(ui, repo, *pats, **opts):
5794 def rename(ui, repo, *pats, **opts):
5788 """rename files; equivalent of copy + remove
5795 """rename files; equivalent of copy + remove
5789
5796
5790 Mark dest as copies of sources; mark sources for deletion. If dest
5797 Mark dest as copies of sources; mark sources for deletion. If dest
5791 is a directory, copies are put in that directory. If dest is a
5798 is a directory, copies are put in that directory. If dest is a
5792 file, there can only be one source.
5799 file, there can only be one source.
5793
5800
5794 By default, this command copies the contents of files as they
5801 By default, this command copies the contents of files as they
5795 exist in the working directory. If invoked with -A/--after, the
5802 exist in the working directory. If invoked with -A/--after, the
5796 operation is recorded, but no copying is performed.
5803 operation is recorded, but no copying is performed.
5797
5804
5798 This command takes effect at the next commit. To undo a rename
5805 This command takes effect at the next commit. To undo a rename
5799 before that, see :hg:`revert`.
5806 before that, see :hg:`revert`.
5800
5807
5801 Returns 0 on success, 1 if errors are encountered.
5808 Returns 0 on success, 1 if errors are encountered.
5802 """
5809 """
5803 opts = pycompat.byteskwargs(opts)
5810 opts = pycompat.byteskwargs(opts)
5804 with repo.wlock(False):
5811 with repo.wlock(False):
5805 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5812 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5806
5813
5807
5814
5808 @command(
5815 @command(
5809 b'resolve',
5816 b'resolve',
5810 [
5817 [
5811 (b'a', b'all', None, _(b'select all unresolved files')),
5818 (b'a', b'all', None, _(b'select all unresolved files')),
5812 (b'l', b'list', None, _(b'list state of files needing merge')),
5819 (b'l', b'list', None, _(b'list state of files needing merge')),
5813 (b'm', b'mark', None, _(b'mark files as resolved')),
5820 (b'm', b'mark', None, _(b'mark files as resolved')),
5814 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5821 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5815 (b'n', b'no-status', None, _(b'hide status prefix')),
5822 (b'n', b'no-status', None, _(b'hide status prefix')),
5816 (b'', b're-merge', None, _(b're-merge files')),
5823 (b'', b're-merge', None, _(b're-merge files')),
5817 ]
5824 ]
5818 + mergetoolopts
5825 + mergetoolopts
5819 + walkopts
5826 + walkopts
5820 + formatteropts,
5827 + formatteropts,
5821 _(b'[OPTION]... [FILE]...'),
5828 _(b'[OPTION]... [FILE]...'),
5822 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5829 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5823 inferrepo=True,
5830 inferrepo=True,
5824 )
5831 )
5825 def resolve(ui, repo, *pats, **opts):
5832 def resolve(ui, repo, *pats, **opts):
5826 """redo merges or set/view the merge status of files
5833 """redo merges or set/view the merge status of files
5827
5834
5828 Merges with unresolved conflicts are often the result of
5835 Merges with unresolved conflicts are often the result of
5829 non-interactive merging using the ``internal:merge`` configuration
5836 non-interactive merging using the ``internal:merge`` configuration
5830 setting, or a command-line merge tool like ``diff3``. The resolve
5837 setting, or a command-line merge tool like ``diff3``. The resolve
5831 command is used to manage the files involved in a merge, after
5838 command is used to manage the files involved in a merge, after
5832 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5839 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5833 working directory must have two parents). See :hg:`help
5840 working directory must have two parents). See :hg:`help
5834 merge-tools` for information on configuring merge tools.
5841 merge-tools` for information on configuring merge tools.
5835
5842
5836 The resolve command can be used in the following ways:
5843 The resolve command can be used in the following ways:
5837
5844
5838 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5845 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5839 the specified files, discarding any previous merge attempts. Re-merging
5846 the specified files, discarding any previous merge attempts. Re-merging
5840 is not performed for files already marked as resolved. Use ``--all/-a``
5847 is not performed for files already marked as resolved. Use ``--all/-a``
5841 to select all unresolved files. ``--tool`` can be used to specify
5848 to select all unresolved files. ``--tool`` can be used to specify
5842 the merge tool used for the given files. It overrides the HGMERGE
5849 the merge tool used for the given files. It overrides the HGMERGE
5843 environment variable and your configuration files. Previous file
5850 environment variable and your configuration files. Previous file
5844 contents are saved with a ``.orig`` suffix.
5851 contents are saved with a ``.orig`` suffix.
5845
5852
5846 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5853 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5847 (e.g. after having manually fixed-up the files). The default is
5854 (e.g. after having manually fixed-up the files). The default is
5848 to mark all unresolved files.
5855 to mark all unresolved files.
5849
5856
5850 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5857 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5851 default is to mark all resolved files.
5858 default is to mark all resolved files.
5852
5859
5853 - :hg:`resolve -l`: list files which had or still have conflicts.
5860 - :hg:`resolve -l`: list files which had or still have conflicts.
5854 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5861 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5855 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5862 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5856 the list. See :hg:`help filesets` for details.
5863 the list. See :hg:`help filesets` for details.
5857
5864
5858 .. note::
5865 .. note::
5859
5866
5860 Mercurial will not let you commit files with unresolved merge
5867 Mercurial will not let you commit files with unresolved merge
5861 conflicts. You must use :hg:`resolve -m ...` before you can
5868 conflicts. You must use :hg:`resolve -m ...` before you can
5862 commit after a conflicting merge.
5869 commit after a conflicting merge.
5863
5870
5864 .. container:: verbose
5871 .. container:: verbose
5865
5872
5866 Template:
5873 Template:
5867
5874
5868 The following keywords are supported in addition to the common template
5875 The following keywords are supported in addition to the common template
5869 keywords and functions. See also :hg:`help templates`.
5876 keywords and functions. See also :hg:`help templates`.
5870
5877
5871 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5878 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5872 :path: String. Repository-absolute path of the file.
5879 :path: String. Repository-absolute path of the file.
5873
5880
5874 Returns 0 on success, 1 if any files fail a resolve attempt.
5881 Returns 0 on success, 1 if any files fail a resolve attempt.
5875 """
5882 """
5876
5883
5877 opts = pycompat.byteskwargs(opts)
5884 opts = pycompat.byteskwargs(opts)
5878 confirm = ui.configbool(b'commands', b'resolve.confirm')
5885 confirm = ui.configbool(b'commands', b'resolve.confirm')
5879 flaglist = b'all mark unmark list no_status re_merge'.split()
5886 flaglist = b'all mark unmark list no_status re_merge'.split()
5880 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5887 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5881
5888
5882 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5889 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5883 if actioncount > 1:
5890 if actioncount > 1:
5884 raise error.Abort(_(b"too many actions specified"))
5891 raise error.Abort(_(b"too many actions specified"))
5885 elif actioncount == 0 and ui.configbool(
5892 elif actioncount == 0 and ui.configbool(
5886 b'commands', b'resolve.explicit-re-merge'
5893 b'commands', b'resolve.explicit-re-merge'
5887 ):
5894 ):
5888 hint = _(b'use --mark, --unmark, --list or --re-merge')
5895 hint = _(b'use --mark, --unmark, --list or --re-merge')
5889 raise error.Abort(_(b'no action specified'), hint=hint)
5896 raise error.Abort(_(b'no action specified'), hint=hint)
5890 if pats and all:
5897 if pats and all:
5891 raise error.Abort(_(b"can't specify --all and patterns"))
5898 raise error.Abort(_(b"can't specify --all and patterns"))
5892 if not (all or pats or show or mark or unmark):
5899 if not (all or pats or show or mark or unmark):
5893 raise error.Abort(
5900 raise error.Abort(
5894 _(b'no files or directories specified'),
5901 _(b'no files or directories specified'),
5895 hint=b'use --all to re-merge all unresolved files',
5902 hint=b'use --all to re-merge all unresolved files',
5896 )
5903 )
5897
5904
5898 if confirm:
5905 if confirm:
5899 if all:
5906 if all:
5900 if ui.promptchoice(
5907 if ui.promptchoice(
5901 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5908 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5902 ):
5909 ):
5903 raise error.Abort(_(b'user quit'))
5910 raise error.Abort(_(b'user quit'))
5904 if mark and not pats:
5911 if mark and not pats:
5905 if ui.promptchoice(
5912 if ui.promptchoice(
5906 _(
5913 _(
5907 b'mark all unresolved files as resolved (yn)?'
5914 b'mark all unresolved files as resolved (yn)?'
5908 b'$$ &Yes $$ &No'
5915 b'$$ &Yes $$ &No'
5909 )
5916 )
5910 ):
5917 ):
5911 raise error.Abort(_(b'user quit'))
5918 raise error.Abort(_(b'user quit'))
5912 if unmark and not pats:
5919 if unmark and not pats:
5913 if ui.promptchoice(
5920 if ui.promptchoice(
5914 _(
5921 _(
5915 b'mark all resolved files as unresolved (yn)?'
5922 b'mark all resolved files as unresolved (yn)?'
5916 b'$$ &Yes $$ &No'
5923 b'$$ &Yes $$ &No'
5917 )
5924 )
5918 ):
5925 ):
5919 raise error.Abort(_(b'user quit'))
5926 raise error.Abort(_(b'user quit'))
5920
5927
5921 uipathfn = scmutil.getuipathfn(repo)
5928 uipathfn = scmutil.getuipathfn(repo)
5922
5929
5923 if show:
5930 if show:
5924 ui.pager(b'resolve')
5931 ui.pager(b'resolve')
5925 fm = ui.formatter(b'resolve', opts)
5932 fm = ui.formatter(b'resolve', opts)
5926 ms = mergemod.mergestate.read(repo)
5933 ms = mergemod.mergestate.read(repo)
5927 wctx = repo[None]
5934 wctx = repo[None]
5928 m = scmutil.match(wctx, pats, opts)
5935 m = scmutil.match(wctx, pats, opts)
5929
5936
5930 # Labels and keys based on merge state. Unresolved path conflicts show
5937 # Labels and keys based on merge state. Unresolved path conflicts show
5931 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5938 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5932 # resolved conflicts.
5939 # resolved conflicts.
5933 mergestateinfo = {
5940 mergestateinfo = {
5934 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5941 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5935 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5942 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5936 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5943 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5937 b'resolve.unresolved',
5944 b'resolve.unresolved',
5938 b'P',
5945 b'P',
5939 ),
5946 ),
5940 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5947 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5941 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5948 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5942 b'resolve.driverresolved',
5949 b'resolve.driverresolved',
5943 b'D',
5950 b'D',
5944 ),
5951 ),
5945 }
5952 }
5946
5953
5947 for f in ms:
5954 for f in ms:
5948 if not m(f):
5955 if not m(f):
5949 continue
5956 continue
5950
5957
5951 label, key = mergestateinfo[ms[f]]
5958 label, key = mergestateinfo[ms[f]]
5952 fm.startitem()
5959 fm.startitem()
5953 fm.context(ctx=wctx)
5960 fm.context(ctx=wctx)
5954 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5961 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5955 fm.data(path=f)
5962 fm.data(path=f)
5956 fm.plain(b'%s\n' % uipathfn(f), label=label)
5963 fm.plain(b'%s\n' % uipathfn(f), label=label)
5957 fm.end()
5964 fm.end()
5958 return 0
5965 return 0
5959
5966
5960 with repo.wlock():
5967 with repo.wlock():
5961 ms = mergemod.mergestate.read(repo)
5968 ms = mergemod.mergestate.read(repo)
5962
5969
5963 if not (ms.active() or repo.dirstate.p2() != nullid):
5970 if not (ms.active() or repo.dirstate.p2() != nullid):
5964 raise error.Abort(
5971 raise error.Abort(
5965 _(b'resolve command not applicable when not merging')
5972 _(b'resolve command not applicable when not merging')
5966 )
5973 )
5967
5974
5968 wctx = repo[None]
5975 wctx = repo[None]
5969
5976
5970 if (
5977 if (
5971 ms.mergedriver
5978 ms.mergedriver
5972 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5979 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5973 ):
5980 ):
5974 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5981 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5975 ms.commit()
5982 ms.commit()
5976 # allow mark and unmark to go through
5983 # allow mark and unmark to go through
5977 if not mark and not unmark and not proceed:
5984 if not mark and not unmark and not proceed:
5978 return 1
5985 return 1
5979
5986
5980 m = scmutil.match(wctx, pats, opts)
5987 m = scmutil.match(wctx, pats, opts)
5981 ret = 0
5988 ret = 0
5982 didwork = False
5989 didwork = False
5983 runconclude = False
5990 runconclude = False
5984
5991
5985 tocomplete = []
5992 tocomplete = []
5986 hasconflictmarkers = []
5993 hasconflictmarkers = []
5987 if mark:
5994 if mark:
5988 markcheck = ui.config(b'commands', b'resolve.mark-check')
5995 markcheck = ui.config(b'commands', b'resolve.mark-check')
5989 if markcheck not in [b'warn', b'abort']:
5996 if markcheck not in [b'warn', b'abort']:
5990 # Treat all invalid / unrecognized values as 'none'.
5997 # Treat all invalid / unrecognized values as 'none'.
5991 markcheck = False
5998 markcheck = False
5992 for f in ms:
5999 for f in ms:
5993 if not m(f):
6000 if not m(f):
5994 continue
6001 continue
5995
6002
5996 didwork = True
6003 didwork = True
5997
6004
5998 # don't let driver-resolved files be marked, and run the conclude
6005 # don't let driver-resolved files be marked, and run the conclude
5999 # step if asked to resolve
6006 # step if asked to resolve
6000 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
6007 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
6001 exact = m.exact(f)
6008 exact = m.exact(f)
6002 if mark:
6009 if mark:
6003 if exact:
6010 if exact:
6004 ui.warn(
6011 ui.warn(
6005 _(b'not marking %s as it is driver-resolved\n')
6012 _(b'not marking %s as it is driver-resolved\n')
6006 % uipathfn(f)
6013 % uipathfn(f)
6007 )
6014 )
6008 elif unmark:
6015 elif unmark:
6009 if exact:
6016 if exact:
6010 ui.warn(
6017 ui.warn(
6011 _(b'not unmarking %s as it is driver-resolved\n')
6018 _(b'not unmarking %s as it is driver-resolved\n')
6012 % uipathfn(f)
6019 % uipathfn(f)
6013 )
6020 )
6014 else:
6021 else:
6015 runconclude = True
6022 runconclude = True
6016 continue
6023 continue
6017
6024
6018 # path conflicts must be resolved manually
6025 # path conflicts must be resolved manually
6019 if ms[f] in (
6026 if ms[f] in (
6020 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6027 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6021 mergemod.MERGE_RECORD_RESOLVED_PATH,
6028 mergemod.MERGE_RECORD_RESOLVED_PATH,
6022 ):
6029 ):
6023 if mark:
6030 if mark:
6024 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6031 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6025 elif unmark:
6032 elif unmark:
6026 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6033 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6027 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6034 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6028 ui.warn(
6035 ui.warn(
6029 _(b'%s: path conflict must be resolved manually\n')
6036 _(b'%s: path conflict must be resolved manually\n')
6030 % uipathfn(f)
6037 % uipathfn(f)
6031 )
6038 )
6032 continue
6039 continue
6033
6040
6034 if mark:
6041 if mark:
6035 if markcheck:
6042 if markcheck:
6036 fdata = repo.wvfs.tryread(f)
6043 fdata = repo.wvfs.tryread(f)
6037 if (
6044 if (
6038 filemerge.hasconflictmarkers(fdata)
6045 filemerge.hasconflictmarkers(fdata)
6039 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6046 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6040 ):
6047 ):
6041 hasconflictmarkers.append(f)
6048 hasconflictmarkers.append(f)
6042 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6049 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6043 elif unmark:
6050 elif unmark:
6044 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6051 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6045 else:
6052 else:
6046 # backup pre-resolve (merge uses .orig for its own purposes)
6053 # backup pre-resolve (merge uses .orig for its own purposes)
6047 a = repo.wjoin(f)
6054 a = repo.wjoin(f)
6048 try:
6055 try:
6049 util.copyfile(a, a + b".resolve")
6056 util.copyfile(a, a + b".resolve")
6050 except (IOError, OSError) as inst:
6057 except (IOError, OSError) as inst:
6051 if inst.errno != errno.ENOENT:
6058 if inst.errno != errno.ENOENT:
6052 raise
6059 raise
6053
6060
6054 try:
6061 try:
6055 # preresolve file
6062 # preresolve file
6056 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6063 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6057 with ui.configoverride(overrides, b'resolve'):
6064 with ui.configoverride(overrides, b'resolve'):
6058 complete, r = ms.preresolve(f, wctx)
6065 complete, r = ms.preresolve(f, wctx)
6059 if not complete:
6066 if not complete:
6060 tocomplete.append(f)
6067 tocomplete.append(f)
6061 elif r:
6068 elif r:
6062 ret = 1
6069 ret = 1
6063 finally:
6070 finally:
6064 ms.commit()
6071 ms.commit()
6065
6072
6066 # replace filemerge's .orig file with our resolve file, but only
6073 # replace filemerge's .orig file with our resolve file, but only
6067 # for merges that are complete
6074 # for merges that are complete
6068 if complete:
6075 if complete:
6069 try:
6076 try:
6070 util.rename(
6077 util.rename(
6071 a + b".resolve", scmutil.backuppath(ui, repo, f)
6078 a + b".resolve", scmutil.backuppath(ui, repo, f)
6072 )
6079 )
6073 except OSError as inst:
6080 except OSError as inst:
6074 if inst.errno != errno.ENOENT:
6081 if inst.errno != errno.ENOENT:
6075 raise
6082 raise
6076
6083
6077 if hasconflictmarkers:
6084 if hasconflictmarkers:
6078 ui.warn(
6085 ui.warn(
6079 _(
6086 _(
6080 b'warning: the following files still have conflict '
6087 b'warning: the following files still have conflict '
6081 b'markers:\n'
6088 b'markers:\n'
6082 )
6089 )
6083 + b''.join(
6090 + b''.join(
6084 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6091 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6085 )
6092 )
6086 )
6093 )
6087 if markcheck == b'abort' and not all and not pats:
6094 if markcheck == b'abort' and not all and not pats:
6088 raise error.Abort(
6095 raise error.Abort(
6089 _(b'conflict markers detected'),
6096 _(b'conflict markers detected'),
6090 hint=_(b'use --all to mark anyway'),
6097 hint=_(b'use --all to mark anyway'),
6091 )
6098 )
6092
6099
6093 for f in tocomplete:
6100 for f in tocomplete:
6094 try:
6101 try:
6095 # resolve file
6102 # resolve file
6096 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6103 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6097 with ui.configoverride(overrides, b'resolve'):
6104 with ui.configoverride(overrides, b'resolve'):
6098 r = ms.resolve(f, wctx)
6105 r = ms.resolve(f, wctx)
6099 if r:
6106 if r:
6100 ret = 1
6107 ret = 1
6101 finally:
6108 finally:
6102 ms.commit()
6109 ms.commit()
6103
6110
6104 # replace filemerge's .orig file with our resolve file
6111 # replace filemerge's .orig file with our resolve file
6105 a = repo.wjoin(f)
6112 a = repo.wjoin(f)
6106 try:
6113 try:
6107 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6114 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6108 except OSError as inst:
6115 except OSError as inst:
6109 if inst.errno != errno.ENOENT:
6116 if inst.errno != errno.ENOENT:
6110 raise
6117 raise
6111
6118
6112 ms.commit()
6119 ms.commit()
6113 ms.recordactions()
6120 ms.recordactions()
6114
6121
6115 if not didwork and pats:
6122 if not didwork and pats:
6116 hint = None
6123 hint = None
6117 if not any([p for p in pats if p.find(b':') >= 0]):
6124 if not any([p for p in pats if p.find(b':') >= 0]):
6118 pats = [b'path:%s' % p for p in pats]
6125 pats = [b'path:%s' % p for p in pats]
6119 m = scmutil.match(wctx, pats, opts)
6126 m = scmutil.match(wctx, pats, opts)
6120 for f in ms:
6127 for f in ms:
6121 if not m(f):
6128 if not m(f):
6122 continue
6129 continue
6123
6130
6124 def flag(o):
6131 def flag(o):
6125 if o == b're_merge':
6132 if o == b're_merge':
6126 return b'--re-merge '
6133 return b'--re-merge '
6127 return b'-%s ' % o[0:1]
6134 return b'-%s ' % o[0:1]
6128
6135
6129 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6136 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6130 hint = _(b"(try: hg resolve %s%s)\n") % (
6137 hint = _(b"(try: hg resolve %s%s)\n") % (
6131 flags,
6138 flags,
6132 b' '.join(pats),
6139 b' '.join(pats),
6133 )
6140 )
6134 break
6141 break
6135 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6142 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6136 if hint:
6143 if hint:
6137 ui.warn(hint)
6144 ui.warn(hint)
6138 elif ms.mergedriver and ms.mdstate() != b's':
6145 elif ms.mergedriver and ms.mdstate() != b's':
6139 # run conclude step when either a driver-resolved file is requested
6146 # run conclude step when either a driver-resolved file is requested
6140 # or there are no driver-resolved files
6147 # or there are no driver-resolved files
6141 # we can't use 'ret' to determine whether any files are unresolved
6148 # we can't use 'ret' to determine whether any files are unresolved
6142 # because we might not have tried to resolve some
6149 # because we might not have tried to resolve some
6143 if (runconclude or not list(ms.driverresolved())) and not list(
6150 if (runconclude or not list(ms.driverresolved())) and not list(
6144 ms.unresolved()
6151 ms.unresolved()
6145 ):
6152 ):
6146 proceed = mergemod.driverconclude(repo, ms, wctx)
6153 proceed = mergemod.driverconclude(repo, ms, wctx)
6147 ms.commit()
6154 ms.commit()
6148 if not proceed:
6155 if not proceed:
6149 return 1
6156 return 1
6150
6157
6151 # Nudge users into finishing an unfinished operation
6158 # Nudge users into finishing an unfinished operation
6152 unresolvedf = list(ms.unresolved())
6159 unresolvedf = list(ms.unresolved())
6153 driverresolvedf = list(ms.driverresolved())
6160 driverresolvedf = list(ms.driverresolved())
6154 if not unresolvedf and not driverresolvedf:
6161 if not unresolvedf and not driverresolvedf:
6155 ui.status(_(b'(no more unresolved files)\n'))
6162 ui.status(_(b'(no more unresolved files)\n'))
6156 cmdutil.checkafterresolved(repo)
6163 cmdutil.checkafterresolved(repo)
6157 elif not unresolvedf:
6164 elif not unresolvedf:
6158 ui.status(
6165 ui.status(
6159 _(
6166 _(
6160 b'(no more unresolved files -- '
6167 b'(no more unresolved files -- '
6161 b'run "hg resolve --all" to conclude)\n'
6168 b'run "hg resolve --all" to conclude)\n'
6162 )
6169 )
6163 )
6170 )
6164
6171
6165 return ret
6172 return ret
6166
6173
6167
6174
6168 @command(
6175 @command(
6169 b'revert',
6176 b'revert',
6170 [
6177 [
6171 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6178 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6172 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6179 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6173 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6180 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6174 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6181 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6175 (b'i', b'interactive', None, _(b'interactively select the changes')),
6182 (b'i', b'interactive', None, _(b'interactively select the changes')),
6176 ]
6183 ]
6177 + walkopts
6184 + walkopts
6178 + dryrunopts,
6185 + dryrunopts,
6179 _(b'[OPTION]... [-r REV] [NAME]...'),
6186 _(b'[OPTION]... [-r REV] [NAME]...'),
6180 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6187 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6181 )
6188 )
6182 def revert(ui, repo, *pats, **opts):
6189 def revert(ui, repo, *pats, **opts):
6183 """restore files to their checkout state
6190 """restore files to their checkout state
6184
6191
6185 .. note::
6192 .. note::
6186
6193
6187 To check out earlier revisions, you should use :hg:`update REV`.
6194 To check out earlier revisions, you should use :hg:`update REV`.
6188 To cancel an uncommitted merge (and lose your changes),
6195 To cancel an uncommitted merge (and lose your changes),
6189 use :hg:`merge --abort`.
6196 use :hg:`merge --abort`.
6190
6197
6191 With no revision specified, revert the specified files or directories
6198 With no revision specified, revert the specified files or directories
6192 to the contents they had in the parent of the working directory.
6199 to the contents they had in the parent of the working directory.
6193 This restores the contents of files to an unmodified
6200 This restores the contents of files to an unmodified
6194 state and unschedules adds, removes, copies, and renames. If the
6201 state and unschedules adds, removes, copies, and renames. If the
6195 working directory has two parents, you must explicitly specify a
6202 working directory has two parents, you must explicitly specify a
6196 revision.
6203 revision.
6197
6204
6198 Using the -r/--rev or -d/--date options, revert the given files or
6205 Using the -r/--rev or -d/--date options, revert the given files or
6199 directories to their states as of a specific revision. Because
6206 directories to their states as of a specific revision. Because
6200 revert does not change the working directory parents, this will
6207 revert does not change the working directory parents, this will
6201 cause these files to appear modified. This can be helpful to "back
6208 cause these files to appear modified. This can be helpful to "back
6202 out" some or all of an earlier change. See :hg:`backout` for a
6209 out" some or all of an earlier change. See :hg:`backout` for a
6203 related method.
6210 related method.
6204
6211
6205 Modified files are saved with a .orig suffix before reverting.
6212 Modified files are saved with a .orig suffix before reverting.
6206 To disable these backups, use --no-backup. It is possible to store
6213 To disable these backups, use --no-backup. It is possible to store
6207 the backup files in a custom directory relative to the root of the
6214 the backup files in a custom directory relative to the root of the
6208 repository by setting the ``ui.origbackuppath`` configuration
6215 repository by setting the ``ui.origbackuppath`` configuration
6209 option.
6216 option.
6210
6217
6211 See :hg:`help dates` for a list of formats valid for -d/--date.
6218 See :hg:`help dates` for a list of formats valid for -d/--date.
6212
6219
6213 See :hg:`help backout` for a way to reverse the effect of an
6220 See :hg:`help backout` for a way to reverse the effect of an
6214 earlier changeset.
6221 earlier changeset.
6215
6222
6216 Returns 0 on success.
6223 Returns 0 on success.
6217 """
6224 """
6218
6225
6219 opts = pycompat.byteskwargs(opts)
6226 opts = pycompat.byteskwargs(opts)
6220 if opts.get(b"date"):
6227 if opts.get(b"date"):
6221 if opts.get(b"rev"):
6228 if opts.get(b"rev"):
6222 raise error.Abort(_(b"you can't specify a revision and a date"))
6229 raise error.Abort(_(b"you can't specify a revision and a date"))
6223 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6230 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6224
6231
6225 parent, p2 = repo.dirstate.parents()
6232 parent, p2 = repo.dirstate.parents()
6226 if not opts.get(b'rev') and p2 != nullid:
6233 if not opts.get(b'rev') and p2 != nullid:
6227 # revert after merge is a trap for new users (issue2915)
6234 # revert after merge is a trap for new users (issue2915)
6228 raise error.Abort(
6235 raise error.Abort(
6229 _(b'uncommitted merge with no revision specified'),
6236 _(b'uncommitted merge with no revision specified'),
6230 hint=_(b"use 'hg update' or see 'hg help revert'"),
6237 hint=_(b"use 'hg update' or see 'hg help revert'"),
6231 )
6238 )
6232
6239
6233 rev = opts.get(b'rev')
6240 rev = opts.get(b'rev')
6234 if rev:
6241 if rev:
6235 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6242 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6236 ctx = scmutil.revsingle(repo, rev)
6243 ctx = scmutil.revsingle(repo, rev)
6237
6244
6238 if not (
6245 if not (
6239 pats
6246 pats
6240 or opts.get(b'include')
6247 or opts.get(b'include')
6241 or opts.get(b'exclude')
6248 or opts.get(b'exclude')
6242 or opts.get(b'all')
6249 or opts.get(b'all')
6243 or opts.get(b'interactive')
6250 or opts.get(b'interactive')
6244 ):
6251 ):
6245 msg = _(b"no files or directories specified")
6252 msg = _(b"no files or directories specified")
6246 if p2 != nullid:
6253 if p2 != nullid:
6247 hint = _(
6254 hint = _(
6248 b"uncommitted merge, use --all to discard all changes,"
6255 b"uncommitted merge, use --all to discard all changes,"
6249 b" or 'hg update -C .' to abort the merge"
6256 b" or 'hg update -C .' to abort the merge"
6250 )
6257 )
6251 raise error.Abort(msg, hint=hint)
6258 raise error.Abort(msg, hint=hint)
6252 dirty = any(repo.status())
6259 dirty = any(repo.status())
6253 node = ctx.node()
6260 node = ctx.node()
6254 if node != parent:
6261 if node != parent:
6255 if dirty:
6262 if dirty:
6256 hint = (
6263 hint = (
6257 _(
6264 _(
6258 b"uncommitted changes, use --all to discard all"
6265 b"uncommitted changes, use --all to discard all"
6259 b" changes, or 'hg update %d' to update"
6266 b" changes, or 'hg update %d' to update"
6260 )
6267 )
6261 % ctx.rev()
6268 % ctx.rev()
6262 )
6269 )
6263 else:
6270 else:
6264 hint = (
6271 hint = (
6265 _(
6272 _(
6266 b"use --all to revert all files,"
6273 b"use --all to revert all files,"
6267 b" or 'hg update %d' to update"
6274 b" or 'hg update %d' to update"
6268 )
6275 )
6269 % ctx.rev()
6276 % ctx.rev()
6270 )
6277 )
6271 elif dirty:
6278 elif dirty:
6272 hint = _(b"uncommitted changes, use --all to discard all changes")
6279 hint = _(b"uncommitted changes, use --all to discard all changes")
6273 else:
6280 else:
6274 hint = _(b"use --all to revert all files")
6281 hint = _(b"use --all to revert all files")
6275 raise error.Abort(msg, hint=hint)
6282 raise error.Abort(msg, hint=hint)
6276
6283
6277 return cmdutil.revert(
6284 return cmdutil.revert(
6278 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6285 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6279 )
6286 )
6280
6287
6281
6288
6282 @command(
6289 @command(
6283 b'rollback',
6290 b'rollback',
6284 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6291 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6285 helpcategory=command.CATEGORY_MAINTENANCE,
6292 helpcategory=command.CATEGORY_MAINTENANCE,
6286 )
6293 )
6287 def rollback(ui, repo, **opts):
6294 def rollback(ui, repo, **opts):
6288 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6295 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6289
6296
6290 Please use :hg:`commit --amend` instead of rollback to correct
6297 Please use :hg:`commit --amend` instead of rollback to correct
6291 mistakes in the last commit.
6298 mistakes in the last commit.
6292
6299
6293 This command should be used with care. There is only one level of
6300 This command should be used with care. There is only one level of
6294 rollback, and there is no way to undo a rollback. It will also
6301 rollback, and there is no way to undo a rollback. It will also
6295 restore the dirstate at the time of the last transaction, losing
6302 restore the dirstate at the time of the last transaction, losing
6296 any dirstate changes since that time. This command does not alter
6303 any dirstate changes since that time. This command does not alter
6297 the working directory.
6304 the working directory.
6298
6305
6299 Transactions are used to encapsulate the effects of all commands
6306 Transactions are used to encapsulate the effects of all commands
6300 that create new changesets or propagate existing changesets into a
6307 that create new changesets or propagate existing changesets into a
6301 repository.
6308 repository.
6302
6309
6303 .. container:: verbose
6310 .. container:: verbose
6304
6311
6305 For example, the following commands are transactional, and their
6312 For example, the following commands are transactional, and their
6306 effects can be rolled back:
6313 effects can be rolled back:
6307
6314
6308 - commit
6315 - commit
6309 - import
6316 - import
6310 - pull
6317 - pull
6311 - push (with this repository as the destination)
6318 - push (with this repository as the destination)
6312 - unbundle
6319 - unbundle
6313
6320
6314 To avoid permanent data loss, rollback will refuse to rollback a
6321 To avoid permanent data loss, rollback will refuse to rollback a
6315 commit transaction if it isn't checked out. Use --force to
6322 commit transaction if it isn't checked out. Use --force to
6316 override this protection.
6323 override this protection.
6317
6324
6318 The rollback command can be entirely disabled by setting the
6325 The rollback command can be entirely disabled by setting the
6319 ``ui.rollback`` configuration setting to false. If you're here
6326 ``ui.rollback`` configuration setting to false. If you're here
6320 because you want to use rollback and it's disabled, you can
6327 because you want to use rollback and it's disabled, you can
6321 re-enable the command by setting ``ui.rollback`` to true.
6328 re-enable the command by setting ``ui.rollback`` to true.
6322
6329
6323 This command is not intended for use on public repositories. Once
6330 This command is not intended for use on public repositories. Once
6324 changes are visible for pull by other users, rolling a transaction
6331 changes are visible for pull by other users, rolling a transaction
6325 back locally is ineffective (someone else may already have pulled
6332 back locally is ineffective (someone else may already have pulled
6326 the changes). Furthermore, a race is possible with readers of the
6333 the changes). Furthermore, a race is possible with readers of the
6327 repository; for example an in-progress pull from the repository
6334 repository; for example an in-progress pull from the repository
6328 may fail if a rollback is performed.
6335 may fail if a rollback is performed.
6329
6336
6330 Returns 0 on success, 1 if no rollback data is available.
6337 Returns 0 on success, 1 if no rollback data is available.
6331 """
6338 """
6332 if not ui.configbool(b'ui', b'rollback'):
6339 if not ui.configbool(b'ui', b'rollback'):
6333 raise error.Abort(
6340 raise error.Abort(
6334 _(b'rollback is disabled because it is unsafe'),
6341 _(b'rollback is disabled because it is unsafe'),
6335 hint=b'see `hg help -v rollback` for information',
6342 hint=b'see `hg help -v rollback` for information',
6336 )
6343 )
6337 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6344 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6338
6345
6339
6346
6340 @command(
6347 @command(
6341 b'root',
6348 b'root',
6342 [] + formatteropts,
6349 [] + formatteropts,
6343 intents={INTENT_READONLY},
6350 intents={INTENT_READONLY},
6344 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6351 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6345 )
6352 )
6346 def root(ui, repo, **opts):
6353 def root(ui, repo, **opts):
6347 """print the root (top) of the current working directory
6354 """print the root (top) of the current working directory
6348
6355
6349 Print the root directory of the current repository.
6356 Print the root directory of the current repository.
6350
6357
6351 .. container:: verbose
6358 .. container:: verbose
6352
6359
6353 Template:
6360 Template:
6354
6361
6355 The following keywords are supported in addition to the common template
6362 The following keywords are supported in addition to the common template
6356 keywords and functions. See also :hg:`help templates`.
6363 keywords and functions. See also :hg:`help templates`.
6357
6364
6358 :hgpath: String. Path to the .hg directory.
6365 :hgpath: String. Path to the .hg directory.
6359 :storepath: String. Path to the directory holding versioned data.
6366 :storepath: String. Path to the directory holding versioned data.
6360
6367
6361 Returns 0 on success.
6368 Returns 0 on success.
6362 """
6369 """
6363 opts = pycompat.byteskwargs(opts)
6370 opts = pycompat.byteskwargs(opts)
6364 with ui.formatter(b'root', opts) as fm:
6371 with ui.formatter(b'root', opts) as fm:
6365 fm.startitem()
6372 fm.startitem()
6366 fm.write(b'reporoot', b'%s\n', repo.root)
6373 fm.write(b'reporoot', b'%s\n', repo.root)
6367 fm.data(hgpath=repo.path, storepath=repo.spath)
6374 fm.data(hgpath=repo.path, storepath=repo.spath)
6368
6375
6369
6376
6370 @command(
6377 @command(
6371 b'serve',
6378 b'serve',
6372 [
6379 [
6373 (
6380 (
6374 b'A',
6381 b'A',
6375 b'accesslog',
6382 b'accesslog',
6376 b'',
6383 b'',
6377 _(b'name of access log file to write to'),
6384 _(b'name of access log file to write to'),
6378 _(b'FILE'),
6385 _(b'FILE'),
6379 ),
6386 ),
6380 (b'd', b'daemon', None, _(b'run server in background')),
6387 (b'd', b'daemon', None, _(b'run server in background')),
6381 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6388 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6382 (
6389 (
6383 b'E',
6390 b'E',
6384 b'errorlog',
6391 b'errorlog',
6385 b'',
6392 b'',
6386 _(b'name of error log file to write to'),
6393 _(b'name of error log file to write to'),
6387 _(b'FILE'),
6394 _(b'FILE'),
6388 ),
6395 ),
6389 # use string type, then we can check if something was passed
6396 # use string type, then we can check if something was passed
6390 (
6397 (
6391 b'p',
6398 b'p',
6392 b'port',
6399 b'port',
6393 b'',
6400 b'',
6394 _(b'port to listen on (default: 8000)'),
6401 _(b'port to listen on (default: 8000)'),
6395 _(b'PORT'),
6402 _(b'PORT'),
6396 ),
6403 ),
6397 (
6404 (
6398 b'a',
6405 b'a',
6399 b'address',
6406 b'address',
6400 b'',
6407 b'',
6401 _(b'address to listen on (default: all interfaces)'),
6408 _(b'address to listen on (default: all interfaces)'),
6402 _(b'ADDR'),
6409 _(b'ADDR'),
6403 ),
6410 ),
6404 (
6411 (
6405 b'',
6412 b'',
6406 b'prefix',
6413 b'prefix',
6407 b'',
6414 b'',
6408 _(b'prefix path to serve from (default: server root)'),
6415 _(b'prefix path to serve from (default: server root)'),
6409 _(b'PREFIX'),
6416 _(b'PREFIX'),
6410 ),
6417 ),
6411 (
6418 (
6412 b'n',
6419 b'n',
6413 b'name',
6420 b'name',
6414 b'',
6421 b'',
6415 _(b'name to show in web pages (default: working directory)'),
6422 _(b'name to show in web pages (default: working directory)'),
6416 _(b'NAME'),
6423 _(b'NAME'),
6417 ),
6424 ),
6418 (
6425 (
6419 b'',
6426 b'',
6420 b'web-conf',
6427 b'web-conf',
6421 b'',
6428 b'',
6422 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6429 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6423 _(b'FILE'),
6430 _(b'FILE'),
6424 ),
6431 ),
6425 (
6432 (
6426 b'',
6433 b'',
6427 b'webdir-conf',
6434 b'webdir-conf',
6428 b'',
6435 b'',
6429 _(b'name of the hgweb config file (DEPRECATED)'),
6436 _(b'name of the hgweb config file (DEPRECATED)'),
6430 _(b'FILE'),
6437 _(b'FILE'),
6431 ),
6438 ),
6432 (
6439 (
6433 b'',
6440 b'',
6434 b'pid-file',
6441 b'pid-file',
6435 b'',
6442 b'',
6436 _(b'name of file to write process ID to'),
6443 _(b'name of file to write process ID to'),
6437 _(b'FILE'),
6444 _(b'FILE'),
6438 ),
6445 ),
6439 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6446 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6440 (
6447 (
6441 b'',
6448 b'',
6442 b'cmdserver',
6449 b'cmdserver',
6443 b'',
6450 b'',
6444 _(b'for remote clients (ADVANCED)'),
6451 _(b'for remote clients (ADVANCED)'),
6445 _(b'MODE'),
6452 _(b'MODE'),
6446 ),
6453 ),
6447 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6454 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6448 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6455 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6449 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6456 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6450 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6457 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6451 (b'', b'print-url', None, _(b'start and print only the URL')),
6458 (b'', b'print-url', None, _(b'start and print only the URL')),
6452 ]
6459 ]
6453 + subrepoopts,
6460 + subrepoopts,
6454 _(b'[OPTION]...'),
6461 _(b'[OPTION]...'),
6455 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6462 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6456 helpbasic=True,
6463 helpbasic=True,
6457 optionalrepo=True,
6464 optionalrepo=True,
6458 )
6465 )
6459 def serve(ui, repo, **opts):
6466 def serve(ui, repo, **opts):
6460 """start stand-alone webserver
6467 """start stand-alone webserver
6461
6468
6462 Start a local HTTP repository browser and pull server. You can use
6469 Start a local HTTP repository browser and pull server. You can use
6463 this for ad-hoc sharing and browsing of repositories. It is
6470 this for ad-hoc sharing and browsing of repositories. It is
6464 recommended to use a real web server to serve a repository for
6471 recommended to use a real web server to serve a repository for
6465 longer periods of time.
6472 longer periods of time.
6466
6473
6467 Please note that the server does not implement access control.
6474 Please note that the server does not implement access control.
6468 This means that, by default, anybody can read from the server and
6475 This means that, by default, anybody can read from the server and
6469 nobody can write to it by default. Set the ``web.allow-push``
6476 nobody can write to it by default. Set the ``web.allow-push``
6470 option to ``*`` to allow everybody to push to the server. You
6477 option to ``*`` to allow everybody to push to the server. You
6471 should use a real web server if you need to authenticate users.
6478 should use a real web server if you need to authenticate users.
6472
6479
6473 By default, the server logs accesses to stdout and errors to
6480 By default, the server logs accesses to stdout and errors to
6474 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6481 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6475 files.
6482 files.
6476
6483
6477 To have the server choose a free port number to listen on, specify
6484 To have the server choose a free port number to listen on, specify
6478 a port number of 0; in this case, the server will print the port
6485 a port number of 0; in this case, the server will print the port
6479 number it uses.
6486 number it uses.
6480
6487
6481 Returns 0 on success.
6488 Returns 0 on success.
6482 """
6489 """
6483
6490
6484 opts = pycompat.byteskwargs(opts)
6491 opts = pycompat.byteskwargs(opts)
6485 if opts[b"stdio"] and opts[b"cmdserver"]:
6492 if opts[b"stdio"] and opts[b"cmdserver"]:
6486 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6493 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6487 if opts[b"print_url"] and ui.verbose:
6494 if opts[b"print_url"] and ui.verbose:
6488 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6495 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6489
6496
6490 if opts[b"stdio"]:
6497 if opts[b"stdio"]:
6491 if repo is None:
6498 if repo is None:
6492 raise error.RepoError(
6499 raise error.RepoError(
6493 _(b"there is no Mercurial repository here (.hg not found)")
6500 _(b"there is no Mercurial repository here (.hg not found)")
6494 )
6501 )
6495 s = wireprotoserver.sshserver(ui, repo)
6502 s = wireprotoserver.sshserver(ui, repo)
6496 s.serve_forever()
6503 s.serve_forever()
6497
6504
6498 service = server.createservice(ui, repo, opts)
6505 service = server.createservice(ui, repo, opts)
6499 return server.runservice(opts, initfn=service.init, runfn=service.run)
6506 return server.runservice(opts, initfn=service.init, runfn=service.run)
6500
6507
6501
6508
6502 @command(
6509 @command(
6503 b'shelve',
6510 b'shelve',
6504 [
6511 [
6505 (
6512 (
6506 b'A',
6513 b'A',
6507 b'addremove',
6514 b'addremove',
6508 None,
6515 None,
6509 _(b'mark new/missing files as added/removed before shelving'),
6516 _(b'mark new/missing files as added/removed before shelving'),
6510 ),
6517 ),
6511 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6518 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6512 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6519 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6513 (
6520 (
6514 b'',
6521 b'',
6515 b'date',
6522 b'date',
6516 b'',
6523 b'',
6517 _(b'shelve with the specified commit date'),
6524 _(b'shelve with the specified commit date'),
6518 _(b'DATE'),
6525 _(b'DATE'),
6519 ),
6526 ),
6520 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6527 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6521 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6528 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6522 (
6529 (
6523 b'k',
6530 b'k',
6524 b'keep',
6531 b'keep',
6525 False,
6532 False,
6526 _(b'shelve, but keep changes in the working directory'),
6533 _(b'shelve, but keep changes in the working directory'),
6527 ),
6534 ),
6528 (b'l', b'list', None, _(b'list current shelves')),
6535 (b'l', b'list', None, _(b'list current shelves')),
6529 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6536 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6530 (
6537 (
6531 b'n',
6538 b'n',
6532 b'name',
6539 b'name',
6533 b'',
6540 b'',
6534 _(b'use the given name for the shelved commit'),
6541 _(b'use the given name for the shelved commit'),
6535 _(b'NAME'),
6542 _(b'NAME'),
6536 ),
6543 ),
6537 (
6544 (
6538 b'p',
6545 b'p',
6539 b'patch',
6546 b'patch',
6540 None,
6547 None,
6541 _(
6548 _(
6542 b'output patches for changes (provide the names of the shelved '
6549 b'output patches for changes (provide the names of the shelved '
6543 b'changes as positional arguments)'
6550 b'changes as positional arguments)'
6544 ),
6551 ),
6545 ),
6552 ),
6546 (b'i', b'interactive', None, _(b'interactive mode')),
6553 (b'i', b'interactive', None, _(b'interactive mode')),
6547 (
6554 (
6548 b'',
6555 b'',
6549 b'stat',
6556 b'stat',
6550 None,
6557 None,
6551 _(
6558 _(
6552 b'output diffstat-style summary of changes (provide the names of '
6559 b'output diffstat-style summary of changes (provide the names of '
6553 b'the shelved changes as positional arguments)'
6560 b'the shelved changes as positional arguments)'
6554 ),
6561 ),
6555 ),
6562 ),
6556 ]
6563 ]
6557 + cmdutil.walkopts,
6564 + cmdutil.walkopts,
6558 _(b'hg shelve [OPTION]... [FILE]...'),
6565 _(b'hg shelve [OPTION]... [FILE]...'),
6559 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6566 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6560 )
6567 )
6561 def shelve(ui, repo, *pats, **opts):
6568 def shelve(ui, repo, *pats, **opts):
6562 '''save and set aside changes from the working directory
6569 '''save and set aside changes from the working directory
6563
6570
6564 Shelving takes files that "hg status" reports as not clean, saves
6571 Shelving takes files that "hg status" reports as not clean, saves
6565 the modifications to a bundle (a shelved change), and reverts the
6572 the modifications to a bundle (a shelved change), and reverts the
6566 files so that their state in the working directory becomes clean.
6573 files so that their state in the working directory becomes clean.
6567
6574
6568 To restore these changes to the working directory, using "hg
6575 To restore these changes to the working directory, using "hg
6569 unshelve"; this will work even if you switch to a different
6576 unshelve"; this will work even if you switch to a different
6570 commit.
6577 commit.
6571
6578
6572 When no files are specified, "hg shelve" saves all not-clean
6579 When no files are specified, "hg shelve" saves all not-clean
6573 files. If specific files or directories are named, only changes to
6580 files. If specific files or directories are named, only changes to
6574 those files are shelved.
6581 those files are shelved.
6575
6582
6576 In bare shelve (when no files are specified, without interactive,
6583 In bare shelve (when no files are specified, without interactive,
6577 include and exclude option), shelving remembers information if the
6584 include and exclude option), shelving remembers information if the
6578 working directory was on newly created branch, in other words working
6585 working directory was on newly created branch, in other words working
6579 directory was on different branch than its first parent. In this
6586 directory was on different branch than its first parent. In this
6580 situation unshelving restores branch information to the working directory.
6587 situation unshelving restores branch information to the working directory.
6581
6588
6582 Each shelved change has a name that makes it easier to find later.
6589 Each shelved change has a name that makes it easier to find later.
6583 The name of a shelved change defaults to being based on the active
6590 The name of a shelved change defaults to being based on the active
6584 bookmark, or if there is no active bookmark, the current named
6591 bookmark, or if there is no active bookmark, the current named
6585 branch. To specify a different name, use ``--name``.
6592 branch. To specify a different name, use ``--name``.
6586
6593
6587 To see a list of existing shelved changes, use the ``--list``
6594 To see a list of existing shelved changes, use the ``--list``
6588 option. For each shelved change, this will print its name, age,
6595 option. For each shelved change, this will print its name, age,
6589 and description; use ``--patch`` or ``--stat`` for more details.
6596 and description; use ``--patch`` or ``--stat`` for more details.
6590
6597
6591 To delete specific shelved changes, use ``--delete``. To delete
6598 To delete specific shelved changes, use ``--delete``. To delete
6592 all shelved changes, use ``--cleanup``.
6599 all shelved changes, use ``--cleanup``.
6593 '''
6600 '''
6594 opts = pycompat.byteskwargs(opts)
6601 opts = pycompat.byteskwargs(opts)
6595 allowables = [
6602 allowables = [
6596 (b'addremove', {b'create'}), # 'create' is pseudo action
6603 (b'addremove', {b'create'}), # 'create' is pseudo action
6597 (b'unknown', {b'create'}),
6604 (b'unknown', {b'create'}),
6598 (b'cleanup', {b'cleanup'}),
6605 (b'cleanup', {b'cleanup'}),
6599 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6606 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6600 (b'delete', {b'delete'}),
6607 (b'delete', {b'delete'}),
6601 (b'edit', {b'create'}),
6608 (b'edit', {b'create'}),
6602 (b'keep', {b'create'}),
6609 (b'keep', {b'create'}),
6603 (b'list', {b'list'}),
6610 (b'list', {b'list'}),
6604 (b'message', {b'create'}),
6611 (b'message', {b'create'}),
6605 (b'name', {b'create'}),
6612 (b'name', {b'create'}),
6606 (b'patch', {b'patch', b'list'}),
6613 (b'patch', {b'patch', b'list'}),
6607 (b'stat', {b'stat', b'list'}),
6614 (b'stat', {b'stat', b'list'}),
6608 ]
6615 ]
6609
6616
6610 def checkopt(opt):
6617 def checkopt(opt):
6611 if opts.get(opt):
6618 if opts.get(opt):
6612 for i, allowable in allowables:
6619 for i, allowable in allowables:
6613 if opts[i] and opt not in allowable:
6620 if opts[i] and opt not in allowable:
6614 raise error.Abort(
6621 raise error.Abort(
6615 _(
6622 _(
6616 b"options '--%s' and '--%s' may not be "
6623 b"options '--%s' and '--%s' may not be "
6617 b"used together"
6624 b"used together"
6618 )
6625 )
6619 % (opt, i)
6626 % (opt, i)
6620 )
6627 )
6621 return True
6628 return True
6622
6629
6623 if checkopt(b'cleanup'):
6630 if checkopt(b'cleanup'):
6624 if pats:
6631 if pats:
6625 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6632 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6626 return shelvemod.cleanupcmd(ui, repo)
6633 return shelvemod.cleanupcmd(ui, repo)
6627 elif checkopt(b'delete'):
6634 elif checkopt(b'delete'):
6628 return shelvemod.deletecmd(ui, repo, pats)
6635 return shelvemod.deletecmd(ui, repo, pats)
6629 elif checkopt(b'list'):
6636 elif checkopt(b'list'):
6630 return shelvemod.listcmd(ui, repo, pats, opts)
6637 return shelvemod.listcmd(ui, repo, pats, opts)
6631 elif checkopt(b'patch') or checkopt(b'stat'):
6638 elif checkopt(b'patch') or checkopt(b'stat'):
6632 return shelvemod.patchcmds(ui, repo, pats, opts)
6639 return shelvemod.patchcmds(ui, repo, pats, opts)
6633 else:
6640 else:
6634 return shelvemod.createcmd(ui, repo, pats, opts)
6641 return shelvemod.createcmd(ui, repo, pats, opts)
6635
6642
6636
6643
6637 _NOTTERSE = b'nothing'
6644 _NOTTERSE = b'nothing'
6638
6645
6639
6646
6640 @command(
6647 @command(
6641 b'status|st',
6648 b'status|st',
6642 [
6649 [
6643 (b'A', b'all', None, _(b'show status of all files')),
6650 (b'A', b'all', None, _(b'show status of all files')),
6644 (b'm', b'modified', None, _(b'show only modified files')),
6651 (b'm', b'modified', None, _(b'show only modified files')),
6645 (b'a', b'added', None, _(b'show only added files')),
6652 (b'a', b'added', None, _(b'show only added files')),
6646 (b'r', b'removed', None, _(b'show only removed files')),
6653 (b'r', b'removed', None, _(b'show only removed files')),
6647 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6654 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6648 (b'c', b'clean', None, _(b'show only files without changes')),
6655 (b'c', b'clean', None, _(b'show only files without changes')),
6649 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6656 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6650 (b'i', b'ignored', None, _(b'show only ignored files')),
6657 (b'i', b'ignored', None, _(b'show only ignored files')),
6651 (b'n', b'no-status', None, _(b'hide status prefix')),
6658 (b'n', b'no-status', None, _(b'hide status prefix')),
6652 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6659 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6653 (
6660 (
6654 b'C',
6661 b'C',
6655 b'copies',
6662 b'copies',
6656 None,
6663 None,
6657 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6664 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6658 ),
6665 ),
6659 (
6666 (
6660 b'0',
6667 b'0',
6661 b'print0',
6668 b'print0',
6662 None,
6669 None,
6663 _(b'end filenames with NUL, for use with xargs'),
6670 _(b'end filenames with NUL, for use with xargs'),
6664 ),
6671 ),
6665 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6672 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6666 (
6673 (
6667 b'',
6674 b'',
6668 b'change',
6675 b'change',
6669 b'',
6676 b'',
6670 _(b'list the changed files of a revision'),
6677 _(b'list the changed files of a revision'),
6671 _(b'REV'),
6678 _(b'REV'),
6672 ),
6679 ),
6673 ]
6680 ]
6674 + walkopts
6681 + walkopts
6675 + subrepoopts
6682 + subrepoopts
6676 + formatteropts,
6683 + formatteropts,
6677 _(b'[OPTION]... [FILE]...'),
6684 _(b'[OPTION]... [FILE]...'),
6678 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6685 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6679 helpbasic=True,
6686 helpbasic=True,
6680 inferrepo=True,
6687 inferrepo=True,
6681 intents={INTENT_READONLY},
6688 intents={INTENT_READONLY},
6682 )
6689 )
6683 def status(ui, repo, *pats, **opts):
6690 def status(ui, repo, *pats, **opts):
6684 """show changed files in the working directory
6691 """show changed files in the working directory
6685
6692
6686 Show status of files in the repository. If names are given, only
6693 Show status of files in the repository. If names are given, only
6687 files that match are shown. Files that are clean or ignored or
6694 files that match are shown. Files that are clean or ignored or
6688 the source of a copy/move operation, are not listed unless
6695 the source of a copy/move operation, are not listed unless
6689 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6696 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6690 Unless options described with "show only ..." are given, the
6697 Unless options described with "show only ..." are given, the
6691 options -mardu are used.
6698 options -mardu are used.
6692
6699
6693 Option -q/--quiet hides untracked (unknown and ignored) files
6700 Option -q/--quiet hides untracked (unknown and ignored) files
6694 unless explicitly requested with -u/--unknown or -i/--ignored.
6701 unless explicitly requested with -u/--unknown or -i/--ignored.
6695
6702
6696 .. note::
6703 .. note::
6697
6704
6698 :hg:`status` may appear to disagree with diff if permissions have
6705 :hg:`status` may appear to disagree with diff if permissions have
6699 changed or a merge has occurred. The standard diff format does
6706 changed or a merge has occurred. The standard diff format does
6700 not report permission changes and diff only reports changes
6707 not report permission changes and diff only reports changes
6701 relative to one merge parent.
6708 relative to one merge parent.
6702
6709
6703 If one revision is given, it is used as the base revision.
6710 If one revision is given, it is used as the base revision.
6704 If two revisions are given, the differences between them are
6711 If two revisions are given, the differences between them are
6705 shown. The --change option can also be used as a shortcut to list
6712 shown. The --change option can also be used as a shortcut to list
6706 the changed files of a revision from its first parent.
6713 the changed files of a revision from its first parent.
6707
6714
6708 The codes used to show the status of files are::
6715 The codes used to show the status of files are::
6709
6716
6710 M = modified
6717 M = modified
6711 A = added
6718 A = added
6712 R = removed
6719 R = removed
6713 C = clean
6720 C = clean
6714 ! = missing (deleted by non-hg command, but still tracked)
6721 ! = missing (deleted by non-hg command, but still tracked)
6715 ? = not tracked
6722 ? = not tracked
6716 I = ignored
6723 I = ignored
6717 = origin of the previous file (with --copies)
6724 = origin of the previous file (with --copies)
6718
6725
6719 .. container:: verbose
6726 .. container:: verbose
6720
6727
6721 The -t/--terse option abbreviates the output by showing only the directory
6728 The -t/--terse option abbreviates the output by showing only the directory
6722 name if all the files in it share the same status. The option takes an
6729 name if all the files in it share the same status. The option takes an
6723 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6730 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6724 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6731 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6725 for 'ignored' and 'c' for clean.
6732 for 'ignored' and 'c' for clean.
6726
6733
6727 It abbreviates only those statuses which are passed. Note that clean and
6734 It abbreviates only those statuses which are passed. Note that clean and
6728 ignored files are not displayed with '--terse ic' unless the -c/--clean
6735 ignored files are not displayed with '--terse ic' unless the -c/--clean
6729 and -i/--ignored options are also used.
6736 and -i/--ignored options are also used.
6730
6737
6731 The -v/--verbose option shows information when the repository is in an
6738 The -v/--verbose option shows information when the repository is in an
6732 unfinished merge, shelve, rebase state etc. You can have this behavior
6739 unfinished merge, shelve, rebase state etc. You can have this behavior
6733 turned on by default by enabling the ``commands.status.verbose`` option.
6740 turned on by default by enabling the ``commands.status.verbose`` option.
6734
6741
6735 You can skip displaying some of these states by setting
6742 You can skip displaying some of these states by setting
6736 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6743 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6737 'histedit', 'merge', 'rebase', or 'unshelve'.
6744 'histedit', 'merge', 'rebase', or 'unshelve'.
6738
6745
6739 Template:
6746 Template:
6740
6747
6741 The following keywords are supported in addition to the common template
6748 The following keywords are supported in addition to the common template
6742 keywords and functions. See also :hg:`help templates`.
6749 keywords and functions. See also :hg:`help templates`.
6743
6750
6744 :path: String. Repository-absolute path of the file.
6751 :path: String. Repository-absolute path of the file.
6745 :source: String. Repository-absolute path of the file originated from.
6752 :source: String. Repository-absolute path of the file originated from.
6746 Available if ``--copies`` is specified.
6753 Available if ``--copies`` is specified.
6747 :status: String. Character denoting file's status.
6754 :status: String. Character denoting file's status.
6748
6755
6749 Examples:
6756 Examples:
6750
6757
6751 - show changes in the working directory relative to a
6758 - show changes in the working directory relative to a
6752 changeset::
6759 changeset::
6753
6760
6754 hg status --rev 9353
6761 hg status --rev 9353
6755
6762
6756 - show changes in the working directory relative to the
6763 - show changes in the working directory relative to the
6757 current directory (see :hg:`help patterns` for more information)::
6764 current directory (see :hg:`help patterns` for more information)::
6758
6765
6759 hg status re:
6766 hg status re:
6760
6767
6761 - show all changes including copies in an existing changeset::
6768 - show all changes including copies in an existing changeset::
6762
6769
6763 hg status --copies --change 9353
6770 hg status --copies --change 9353
6764
6771
6765 - get a NUL separated list of added files, suitable for xargs::
6772 - get a NUL separated list of added files, suitable for xargs::
6766
6773
6767 hg status -an0
6774 hg status -an0
6768
6775
6769 - show more information about the repository status, abbreviating
6776 - show more information about the repository status, abbreviating
6770 added, removed, modified, deleted, and untracked paths::
6777 added, removed, modified, deleted, and untracked paths::
6771
6778
6772 hg status -v -t mardu
6779 hg status -v -t mardu
6773
6780
6774 Returns 0 on success.
6781 Returns 0 on success.
6775
6782
6776 """
6783 """
6777
6784
6778 opts = pycompat.byteskwargs(opts)
6785 opts = pycompat.byteskwargs(opts)
6779 revs = opts.get(b'rev')
6786 revs = opts.get(b'rev')
6780 change = opts.get(b'change')
6787 change = opts.get(b'change')
6781 terse = opts.get(b'terse')
6788 terse = opts.get(b'terse')
6782 if terse is _NOTTERSE:
6789 if terse is _NOTTERSE:
6783 if revs:
6790 if revs:
6784 terse = b''
6791 terse = b''
6785 else:
6792 else:
6786 terse = ui.config(b'commands', b'status.terse')
6793 terse = ui.config(b'commands', b'status.terse')
6787
6794
6788 if revs and change:
6795 if revs and change:
6789 msg = _(b'cannot specify --rev and --change at the same time')
6796 msg = _(b'cannot specify --rev and --change at the same time')
6790 raise error.Abort(msg)
6797 raise error.Abort(msg)
6791 elif revs and terse:
6798 elif revs and terse:
6792 msg = _(b'cannot use --terse with --rev')
6799 msg = _(b'cannot use --terse with --rev')
6793 raise error.Abort(msg)
6800 raise error.Abort(msg)
6794 elif change:
6801 elif change:
6795 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6802 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6796 ctx2 = scmutil.revsingle(repo, change, None)
6803 ctx2 = scmutil.revsingle(repo, change, None)
6797 ctx1 = ctx2.p1()
6804 ctx1 = ctx2.p1()
6798 else:
6805 else:
6799 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6806 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6800 ctx1, ctx2 = scmutil.revpair(repo, revs)
6807 ctx1, ctx2 = scmutil.revpair(repo, revs)
6801
6808
6802 forcerelativevalue = None
6809 forcerelativevalue = None
6803 if ui.hasconfig(b'commands', b'status.relative'):
6810 if ui.hasconfig(b'commands', b'status.relative'):
6804 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6811 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6805 uipathfn = scmutil.getuipathfn(
6812 uipathfn = scmutil.getuipathfn(
6806 repo,
6813 repo,
6807 legacyrelativevalue=bool(pats),
6814 legacyrelativevalue=bool(pats),
6808 forcerelativevalue=forcerelativevalue,
6815 forcerelativevalue=forcerelativevalue,
6809 )
6816 )
6810
6817
6811 if opts.get(b'print0'):
6818 if opts.get(b'print0'):
6812 end = b'\0'
6819 end = b'\0'
6813 else:
6820 else:
6814 end = b'\n'
6821 end = b'\n'
6815 states = b'modified added removed deleted unknown ignored clean'.split()
6822 states = b'modified added removed deleted unknown ignored clean'.split()
6816 show = [k for k in states if opts.get(k)]
6823 show = [k for k in states if opts.get(k)]
6817 if opts.get(b'all'):
6824 if opts.get(b'all'):
6818 show += ui.quiet and (states[:4] + [b'clean']) or states
6825 show += ui.quiet and (states[:4] + [b'clean']) or states
6819
6826
6820 if not show:
6827 if not show:
6821 if ui.quiet:
6828 if ui.quiet:
6822 show = states[:4]
6829 show = states[:4]
6823 else:
6830 else:
6824 show = states[:5]
6831 show = states[:5]
6825
6832
6826 m = scmutil.match(ctx2, pats, opts)
6833 m = scmutil.match(ctx2, pats, opts)
6827 if terse:
6834 if terse:
6828 # we need to compute clean and unknown to terse
6835 # we need to compute clean and unknown to terse
6829 stat = repo.status(
6836 stat = repo.status(
6830 ctx1.node(),
6837 ctx1.node(),
6831 ctx2.node(),
6838 ctx2.node(),
6832 m,
6839 m,
6833 b'ignored' in show or b'i' in terse,
6840 b'ignored' in show or b'i' in terse,
6834 clean=True,
6841 clean=True,
6835 unknown=True,
6842 unknown=True,
6836 listsubrepos=opts.get(b'subrepos'),
6843 listsubrepos=opts.get(b'subrepos'),
6837 )
6844 )
6838
6845
6839 stat = cmdutil.tersedir(stat, terse)
6846 stat = cmdutil.tersedir(stat, terse)
6840 else:
6847 else:
6841 stat = repo.status(
6848 stat = repo.status(
6842 ctx1.node(),
6849 ctx1.node(),
6843 ctx2.node(),
6850 ctx2.node(),
6844 m,
6851 m,
6845 b'ignored' in show,
6852 b'ignored' in show,
6846 b'clean' in show,
6853 b'clean' in show,
6847 b'unknown' in show,
6854 b'unknown' in show,
6848 opts.get(b'subrepos'),
6855 opts.get(b'subrepos'),
6849 )
6856 )
6850
6857
6851 changestates = zip(
6858 changestates = zip(
6852 states,
6859 states,
6853 pycompat.iterbytestr(b'MAR!?IC'),
6860 pycompat.iterbytestr(b'MAR!?IC'),
6854 [getattr(stat, s.decode('utf8')) for s in states],
6861 [getattr(stat, s.decode('utf8')) for s in states],
6855 )
6862 )
6856
6863
6857 copy = {}
6864 copy = {}
6858 if (
6865 if (
6859 opts.get(b'all')
6866 opts.get(b'all')
6860 or opts.get(b'copies')
6867 or opts.get(b'copies')
6861 or ui.configbool(b'ui', b'statuscopies')
6868 or ui.configbool(b'ui', b'statuscopies')
6862 ) and not opts.get(b'no_status'):
6869 ) and not opts.get(b'no_status'):
6863 copy = copies.pathcopies(ctx1, ctx2, m)
6870 copy = copies.pathcopies(ctx1, ctx2, m)
6864
6871
6865 morestatus = None
6872 morestatus = None
6866 if (
6873 if (
6867 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6874 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6868 ) and not ui.plain():
6875 ) and not ui.plain():
6869 morestatus = cmdutil.readmorestatus(repo)
6876 morestatus = cmdutil.readmorestatus(repo)
6870
6877
6871 ui.pager(b'status')
6878 ui.pager(b'status')
6872 fm = ui.formatter(b'status', opts)
6879 fm = ui.formatter(b'status', opts)
6873 fmt = b'%s' + end
6880 fmt = b'%s' + end
6874 showchar = not opts.get(b'no_status')
6881 showchar = not opts.get(b'no_status')
6875
6882
6876 for state, char, files in changestates:
6883 for state, char, files in changestates:
6877 if state in show:
6884 if state in show:
6878 label = b'status.' + state
6885 label = b'status.' + state
6879 for f in files:
6886 for f in files:
6880 fm.startitem()
6887 fm.startitem()
6881 fm.context(ctx=ctx2)
6888 fm.context(ctx=ctx2)
6882 fm.data(itemtype=b'file', path=f)
6889 fm.data(itemtype=b'file', path=f)
6883 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6890 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6884 fm.plain(fmt % uipathfn(f), label=label)
6891 fm.plain(fmt % uipathfn(f), label=label)
6885 if f in copy:
6892 if f in copy:
6886 fm.data(source=copy[f])
6893 fm.data(source=copy[f])
6887 fm.plain(
6894 fm.plain(
6888 (b' %s' + end) % uipathfn(copy[f]),
6895 (b' %s' + end) % uipathfn(copy[f]),
6889 label=b'status.copied',
6896 label=b'status.copied',
6890 )
6897 )
6891 if morestatus:
6898 if morestatus:
6892 morestatus.formatfile(f, fm)
6899 morestatus.formatfile(f, fm)
6893
6900
6894 if morestatus:
6901 if morestatus:
6895 morestatus.formatfooter(fm)
6902 morestatus.formatfooter(fm)
6896 fm.end()
6903 fm.end()
6897
6904
6898
6905
6899 @command(
6906 @command(
6900 b'summary|sum',
6907 b'summary|sum',
6901 [(b'', b'remote', None, _(b'check for push and pull'))],
6908 [(b'', b'remote', None, _(b'check for push and pull'))],
6902 b'[--remote]',
6909 b'[--remote]',
6903 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6910 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6904 helpbasic=True,
6911 helpbasic=True,
6905 intents={INTENT_READONLY},
6912 intents={INTENT_READONLY},
6906 )
6913 )
6907 def summary(ui, repo, **opts):
6914 def summary(ui, repo, **opts):
6908 """summarize working directory state
6915 """summarize working directory state
6909
6916
6910 This generates a brief summary of the working directory state,
6917 This generates a brief summary of the working directory state,
6911 including parents, branch, commit status, phase and available updates.
6918 including parents, branch, commit status, phase and available updates.
6912
6919
6913 With the --remote option, this will check the default paths for
6920 With the --remote option, this will check the default paths for
6914 incoming and outgoing changes. This can be time-consuming.
6921 incoming and outgoing changes. This can be time-consuming.
6915
6922
6916 Returns 0 on success.
6923 Returns 0 on success.
6917 """
6924 """
6918
6925
6919 opts = pycompat.byteskwargs(opts)
6926 opts = pycompat.byteskwargs(opts)
6920 ui.pager(b'summary')
6927 ui.pager(b'summary')
6921 ctx = repo[None]
6928 ctx = repo[None]
6922 parents = ctx.parents()
6929 parents = ctx.parents()
6923 pnode = parents[0].node()
6930 pnode = parents[0].node()
6924 marks = []
6931 marks = []
6925
6932
6926 try:
6933 try:
6927 ms = mergemod.mergestate.read(repo)
6934 ms = mergemod.mergestate.read(repo)
6928 except error.UnsupportedMergeRecords as e:
6935 except error.UnsupportedMergeRecords as e:
6929 s = b' '.join(e.recordtypes)
6936 s = b' '.join(e.recordtypes)
6930 ui.warn(
6937 ui.warn(
6931 _(b'warning: merge state has unsupported record types: %s\n') % s
6938 _(b'warning: merge state has unsupported record types: %s\n') % s
6932 )
6939 )
6933 unresolved = []
6940 unresolved = []
6934 else:
6941 else:
6935 unresolved = list(ms.unresolved())
6942 unresolved = list(ms.unresolved())
6936
6943
6937 for p in parents:
6944 for p in parents:
6938 # label with log.changeset (instead of log.parent) since this
6945 # label with log.changeset (instead of log.parent) since this
6939 # shows a working directory parent *changeset*:
6946 # shows a working directory parent *changeset*:
6940 # i18n: column positioning for "hg summary"
6947 # i18n: column positioning for "hg summary"
6941 ui.write(
6948 ui.write(
6942 _(b'parent: %d:%s ') % (p.rev(), p),
6949 _(b'parent: %d:%s ') % (p.rev(), p),
6943 label=logcmdutil.changesetlabels(p),
6950 label=logcmdutil.changesetlabels(p),
6944 )
6951 )
6945 ui.write(b' '.join(p.tags()), label=b'log.tag')
6952 ui.write(b' '.join(p.tags()), label=b'log.tag')
6946 if p.bookmarks():
6953 if p.bookmarks():
6947 marks.extend(p.bookmarks())
6954 marks.extend(p.bookmarks())
6948 if p.rev() == -1:
6955 if p.rev() == -1:
6949 if not len(repo):
6956 if not len(repo):
6950 ui.write(_(b' (empty repository)'))
6957 ui.write(_(b' (empty repository)'))
6951 else:
6958 else:
6952 ui.write(_(b' (no revision checked out)'))
6959 ui.write(_(b' (no revision checked out)'))
6953 if p.obsolete():
6960 if p.obsolete():
6954 ui.write(_(b' (obsolete)'))
6961 ui.write(_(b' (obsolete)'))
6955 if p.isunstable():
6962 if p.isunstable():
6956 instabilities = (
6963 instabilities = (
6957 ui.label(instability, b'trouble.%s' % instability)
6964 ui.label(instability, b'trouble.%s' % instability)
6958 for instability in p.instabilities()
6965 for instability in p.instabilities()
6959 )
6966 )
6960 ui.write(b' (' + b', '.join(instabilities) + b')')
6967 ui.write(b' (' + b', '.join(instabilities) + b')')
6961 ui.write(b'\n')
6968 ui.write(b'\n')
6962 if p.description():
6969 if p.description():
6963 ui.status(
6970 ui.status(
6964 b' ' + p.description().splitlines()[0].strip() + b'\n',
6971 b' ' + p.description().splitlines()[0].strip() + b'\n',
6965 label=b'log.summary',
6972 label=b'log.summary',
6966 )
6973 )
6967
6974
6968 branch = ctx.branch()
6975 branch = ctx.branch()
6969 bheads = repo.branchheads(branch)
6976 bheads = repo.branchheads(branch)
6970 # i18n: column positioning for "hg summary"
6977 # i18n: column positioning for "hg summary"
6971 m = _(b'branch: %s\n') % branch
6978 m = _(b'branch: %s\n') % branch
6972 if branch != b'default':
6979 if branch != b'default':
6973 ui.write(m, label=b'log.branch')
6980 ui.write(m, label=b'log.branch')
6974 else:
6981 else:
6975 ui.status(m, label=b'log.branch')
6982 ui.status(m, label=b'log.branch')
6976
6983
6977 if marks:
6984 if marks:
6978 active = repo._activebookmark
6985 active = repo._activebookmark
6979 # i18n: column positioning for "hg summary"
6986 # i18n: column positioning for "hg summary"
6980 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6987 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6981 if active is not None:
6988 if active is not None:
6982 if active in marks:
6989 if active in marks:
6983 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6990 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6984 marks.remove(active)
6991 marks.remove(active)
6985 else:
6992 else:
6986 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6993 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6987 for m in marks:
6994 for m in marks:
6988 ui.write(b' ' + m, label=b'log.bookmark')
6995 ui.write(b' ' + m, label=b'log.bookmark')
6989 ui.write(b'\n', label=b'log.bookmark')
6996 ui.write(b'\n', label=b'log.bookmark')
6990
6997
6991 status = repo.status(unknown=True)
6998 status = repo.status(unknown=True)
6992
6999
6993 c = repo.dirstate.copies()
7000 c = repo.dirstate.copies()
6994 copied, renamed = [], []
7001 copied, renamed = [], []
6995 for d, s in pycompat.iteritems(c):
7002 for d, s in pycompat.iteritems(c):
6996 if s in status.removed:
7003 if s in status.removed:
6997 status.removed.remove(s)
7004 status.removed.remove(s)
6998 renamed.append(d)
7005 renamed.append(d)
6999 else:
7006 else:
7000 copied.append(d)
7007 copied.append(d)
7001 if d in status.added:
7008 if d in status.added:
7002 status.added.remove(d)
7009 status.added.remove(d)
7003
7010
7004 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7011 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7005
7012
7006 labels = [
7013 labels = [
7007 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7014 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7008 (ui.label(_(b'%d added'), b'status.added'), status.added),
7015 (ui.label(_(b'%d added'), b'status.added'), status.added),
7009 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7016 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7010 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7017 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7011 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7018 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7012 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7019 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7013 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7020 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7014 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7021 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7015 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7022 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7016 ]
7023 ]
7017 t = []
7024 t = []
7018 for l, s in labels:
7025 for l, s in labels:
7019 if s:
7026 if s:
7020 t.append(l % len(s))
7027 t.append(l % len(s))
7021
7028
7022 t = b', '.join(t)
7029 t = b', '.join(t)
7023 cleanworkdir = False
7030 cleanworkdir = False
7024
7031
7025 if repo.vfs.exists(b'graftstate'):
7032 if repo.vfs.exists(b'graftstate'):
7026 t += _(b' (graft in progress)')
7033 t += _(b' (graft in progress)')
7027 if repo.vfs.exists(b'updatestate'):
7034 if repo.vfs.exists(b'updatestate'):
7028 t += _(b' (interrupted update)')
7035 t += _(b' (interrupted update)')
7029 elif len(parents) > 1:
7036 elif len(parents) > 1:
7030 t += _(b' (merge)')
7037 t += _(b' (merge)')
7031 elif branch != parents[0].branch():
7038 elif branch != parents[0].branch():
7032 t += _(b' (new branch)')
7039 t += _(b' (new branch)')
7033 elif parents[0].closesbranch() and pnode in repo.branchheads(
7040 elif parents[0].closesbranch() and pnode in repo.branchheads(
7034 branch, closed=True
7041 branch, closed=True
7035 ):
7042 ):
7036 t += _(b' (head closed)')
7043 t += _(b' (head closed)')
7037 elif not (
7044 elif not (
7038 status.modified
7045 status.modified
7039 or status.added
7046 or status.added
7040 or status.removed
7047 or status.removed
7041 or renamed
7048 or renamed
7042 or copied
7049 or copied
7043 or subs
7050 or subs
7044 ):
7051 ):
7045 t += _(b' (clean)')
7052 t += _(b' (clean)')
7046 cleanworkdir = True
7053 cleanworkdir = True
7047 elif pnode not in bheads:
7054 elif pnode not in bheads:
7048 t += _(b' (new branch head)')
7055 t += _(b' (new branch head)')
7049
7056
7050 if parents:
7057 if parents:
7051 pendingphase = max(p.phase() for p in parents)
7058 pendingphase = max(p.phase() for p in parents)
7052 else:
7059 else:
7053 pendingphase = phases.public
7060 pendingphase = phases.public
7054
7061
7055 if pendingphase > phases.newcommitphase(ui):
7062 if pendingphase > phases.newcommitphase(ui):
7056 t += b' (%s)' % phases.phasenames[pendingphase]
7063 t += b' (%s)' % phases.phasenames[pendingphase]
7057
7064
7058 if cleanworkdir:
7065 if cleanworkdir:
7059 # i18n: column positioning for "hg summary"
7066 # i18n: column positioning for "hg summary"
7060 ui.status(_(b'commit: %s\n') % t.strip())
7067 ui.status(_(b'commit: %s\n') % t.strip())
7061 else:
7068 else:
7062 # i18n: column positioning for "hg summary"
7069 # i18n: column positioning for "hg summary"
7063 ui.write(_(b'commit: %s\n') % t.strip())
7070 ui.write(_(b'commit: %s\n') % t.strip())
7064
7071
7065 # all ancestors of branch heads - all ancestors of parent = new csets
7072 # all ancestors of branch heads - all ancestors of parent = new csets
7066 new = len(
7073 new = len(
7067 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7074 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7068 )
7075 )
7069
7076
7070 if new == 0:
7077 if new == 0:
7071 # i18n: column positioning for "hg summary"
7078 # i18n: column positioning for "hg summary"
7072 ui.status(_(b'update: (current)\n'))
7079 ui.status(_(b'update: (current)\n'))
7073 elif pnode not in bheads:
7080 elif pnode not in bheads:
7074 # i18n: column positioning for "hg summary"
7081 # i18n: column positioning for "hg summary"
7075 ui.write(_(b'update: %d new changesets (update)\n') % new)
7082 ui.write(_(b'update: %d new changesets (update)\n') % new)
7076 else:
7083 else:
7077 # i18n: column positioning for "hg summary"
7084 # i18n: column positioning for "hg summary"
7078 ui.write(
7085 ui.write(
7079 _(b'update: %d new changesets, %d branch heads (merge)\n')
7086 _(b'update: %d new changesets, %d branch heads (merge)\n')
7080 % (new, len(bheads))
7087 % (new, len(bheads))
7081 )
7088 )
7082
7089
7083 t = []
7090 t = []
7084 draft = len(repo.revs(b'draft()'))
7091 draft = len(repo.revs(b'draft()'))
7085 if draft:
7092 if draft:
7086 t.append(_(b'%d draft') % draft)
7093 t.append(_(b'%d draft') % draft)
7087 secret = len(repo.revs(b'secret()'))
7094 secret = len(repo.revs(b'secret()'))
7088 if secret:
7095 if secret:
7089 t.append(_(b'%d secret') % secret)
7096 t.append(_(b'%d secret') % secret)
7090
7097
7091 if draft or secret:
7098 if draft or secret:
7092 ui.status(_(b'phases: %s\n') % b', '.join(t))
7099 ui.status(_(b'phases: %s\n') % b', '.join(t))
7093
7100
7094 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7101 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7095 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7102 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7096 numtrouble = len(repo.revs(trouble + b"()"))
7103 numtrouble = len(repo.revs(trouble + b"()"))
7097 # We write all the possibilities to ease translation
7104 # We write all the possibilities to ease translation
7098 troublemsg = {
7105 troublemsg = {
7099 b"orphan": _(b"orphan: %d changesets"),
7106 b"orphan": _(b"orphan: %d changesets"),
7100 b"contentdivergent": _(b"content-divergent: %d changesets"),
7107 b"contentdivergent": _(b"content-divergent: %d changesets"),
7101 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7108 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7102 }
7109 }
7103 if numtrouble > 0:
7110 if numtrouble > 0:
7104 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7111 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7105
7112
7106 cmdutil.summaryhooks(ui, repo)
7113 cmdutil.summaryhooks(ui, repo)
7107
7114
7108 if opts.get(b'remote'):
7115 if opts.get(b'remote'):
7109 needsincoming, needsoutgoing = True, True
7116 needsincoming, needsoutgoing = True, True
7110 else:
7117 else:
7111 needsincoming, needsoutgoing = False, False
7118 needsincoming, needsoutgoing = False, False
7112 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7119 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7113 if i:
7120 if i:
7114 needsincoming = True
7121 needsincoming = True
7115 if o:
7122 if o:
7116 needsoutgoing = True
7123 needsoutgoing = True
7117 if not needsincoming and not needsoutgoing:
7124 if not needsincoming and not needsoutgoing:
7118 return
7125 return
7119
7126
7120 def getincoming():
7127 def getincoming():
7121 source, branches = hg.parseurl(ui.expandpath(b'default'))
7128 source, branches = hg.parseurl(ui.expandpath(b'default'))
7122 sbranch = branches[0]
7129 sbranch = branches[0]
7123 try:
7130 try:
7124 other = hg.peer(repo, {}, source)
7131 other = hg.peer(repo, {}, source)
7125 except error.RepoError:
7132 except error.RepoError:
7126 if opts.get(b'remote'):
7133 if opts.get(b'remote'):
7127 raise
7134 raise
7128 return source, sbranch, None, None, None
7135 return source, sbranch, None, None, None
7129 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7136 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7130 if revs:
7137 if revs:
7131 revs = [other.lookup(rev) for rev in revs]
7138 revs = [other.lookup(rev) for rev in revs]
7132 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7139 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7133 repo.ui.pushbuffer()
7140 repo.ui.pushbuffer()
7134 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7141 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7135 repo.ui.popbuffer()
7142 repo.ui.popbuffer()
7136 return source, sbranch, other, commoninc, commoninc[1]
7143 return source, sbranch, other, commoninc, commoninc[1]
7137
7144
7138 if needsincoming:
7145 if needsincoming:
7139 source, sbranch, sother, commoninc, incoming = getincoming()
7146 source, sbranch, sother, commoninc, incoming = getincoming()
7140 else:
7147 else:
7141 source = sbranch = sother = commoninc = incoming = None
7148 source = sbranch = sother = commoninc = incoming = None
7142
7149
7143 def getoutgoing():
7150 def getoutgoing():
7144 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7151 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7145 dbranch = branches[0]
7152 dbranch = branches[0]
7146 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7153 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7147 if source != dest:
7154 if source != dest:
7148 try:
7155 try:
7149 dother = hg.peer(repo, {}, dest)
7156 dother = hg.peer(repo, {}, dest)
7150 except error.RepoError:
7157 except error.RepoError:
7151 if opts.get(b'remote'):
7158 if opts.get(b'remote'):
7152 raise
7159 raise
7153 return dest, dbranch, None, None
7160 return dest, dbranch, None, None
7154 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7161 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7155 elif sother is None:
7162 elif sother is None:
7156 # there is no explicit destination peer, but source one is invalid
7163 # there is no explicit destination peer, but source one is invalid
7157 return dest, dbranch, None, None
7164 return dest, dbranch, None, None
7158 else:
7165 else:
7159 dother = sother
7166 dother = sother
7160 if source != dest or (sbranch is not None and sbranch != dbranch):
7167 if source != dest or (sbranch is not None and sbranch != dbranch):
7161 common = None
7168 common = None
7162 else:
7169 else:
7163 common = commoninc
7170 common = commoninc
7164 if revs:
7171 if revs:
7165 revs = [repo.lookup(rev) for rev in revs]
7172 revs = [repo.lookup(rev) for rev in revs]
7166 repo.ui.pushbuffer()
7173 repo.ui.pushbuffer()
7167 outgoing = discovery.findcommonoutgoing(
7174 outgoing = discovery.findcommonoutgoing(
7168 repo, dother, onlyheads=revs, commoninc=common
7175 repo, dother, onlyheads=revs, commoninc=common
7169 )
7176 )
7170 repo.ui.popbuffer()
7177 repo.ui.popbuffer()
7171 return dest, dbranch, dother, outgoing
7178 return dest, dbranch, dother, outgoing
7172
7179
7173 if needsoutgoing:
7180 if needsoutgoing:
7174 dest, dbranch, dother, outgoing = getoutgoing()
7181 dest, dbranch, dother, outgoing = getoutgoing()
7175 else:
7182 else:
7176 dest = dbranch = dother = outgoing = None
7183 dest = dbranch = dother = outgoing = None
7177
7184
7178 if opts.get(b'remote'):
7185 if opts.get(b'remote'):
7179 t = []
7186 t = []
7180 if incoming:
7187 if incoming:
7181 t.append(_(b'1 or more incoming'))
7188 t.append(_(b'1 or more incoming'))
7182 o = outgoing.missing
7189 o = outgoing.missing
7183 if o:
7190 if o:
7184 t.append(_(b'%d outgoing') % len(o))
7191 t.append(_(b'%d outgoing') % len(o))
7185 other = dother or sother
7192 other = dother or sother
7186 if b'bookmarks' in other.listkeys(b'namespaces'):
7193 if b'bookmarks' in other.listkeys(b'namespaces'):
7187 counts = bookmarks.summary(repo, other)
7194 counts = bookmarks.summary(repo, other)
7188 if counts[0] > 0:
7195 if counts[0] > 0:
7189 t.append(_(b'%d incoming bookmarks') % counts[0])
7196 t.append(_(b'%d incoming bookmarks') % counts[0])
7190 if counts[1] > 0:
7197 if counts[1] > 0:
7191 t.append(_(b'%d outgoing bookmarks') % counts[1])
7198 t.append(_(b'%d outgoing bookmarks') % counts[1])
7192
7199
7193 if t:
7200 if t:
7194 # i18n: column positioning for "hg summary"
7201 # i18n: column positioning for "hg summary"
7195 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7202 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7196 else:
7203 else:
7197 # i18n: column positioning for "hg summary"
7204 # i18n: column positioning for "hg summary"
7198 ui.status(_(b'remote: (synced)\n'))
7205 ui.status(_(b'remote: (synced)\n'))
7199
7206
7200 cmdutil.summaryremotehooks(
7207 cmdutil.summaryremotehooks(
7201 ui,
7208 ui,
7202 repo,
7209 repo,
7203 opts,
7210 opts,
7204 (
7211 (
7205 (source, sbranch, sother, commoninc),
7212 (source, sbranch, sother, commoninc),
7206 (dest, dbranch, dother, outgoing),
7213 (dest, dbranch, dother, outgoing),
7207 ),
7214 ),
7208 )
7215 )
7209
7216
7210
7217
7211 @command(
7218 @command(
7212 b'tag',
7219 b'tag',
7213 [
7220 [
7214 (b'f', b'force', None, _(b'force tag')),
7221 (b'f', b'force', None, _(b'force tag')),
7215 (b'l', b'local', None, _(b'make the tag local')),
7222 (b'l', b'local', None, _(b'make the tag local')),
7216 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7223 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7217 (b'', b'remove', None, _(b'remove a tag')),
7224 (b'', b'remove', None, _(b'remove a tag')),
7218 # -l/--local is already there, commitopts cannot be used
7225 # -l/--local is already there, commitopts cannot be used
7219 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7226 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7220 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7227 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7221 ]
7228 ]
7222 + commitopts2,
7229 + commitopts2,
7223 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7230 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7224 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7231 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7225 )
7232 )
7226 def tag(ui, repo, name1, *names, **opts):
7233 def tag(ui, repo, name1, *names, **opts):
7227 """add one or more tags for the current or given revision
7234 """add one or more tags for the current or given revision
7228
7235
7229 Name a particular revision using <name>.
7236 Name a particular revision using <name>.
7230
7237
7231 Tags are used to name particular revisions of the repository and are
7238 Tags are used to name particular revisions of the repository and are
7232 very useful to compare different revisions, to go back to significant
7239 very useful to compare different revisions, to go back to significant
7233 earlier versions or to mark branch points as releases, etc. Changing
7240 earlier versions or to mark branch points as releases, etc. Changing
7234 an existing tag is normally disallowed; use -f/--force to override.
7241 an existing tag is normally disallowed; use -f/--force to override.
7235
7242
7236 If no revision is given, the parent of the working directory is
7243 If no revision is given, the parent of the working directory is
7237 used.
7244 used.
7238
7245
7239 To facilitate version control, distribution, and merging of tags,
7246 To facilitate version control, distribution, and merging of tags,
7240 they are stored as a file named ".hgtags" which is managed similarly
7247 they are stored as a file named ".hgtags" which is managed similarly
7241 to other project files and can be hand-edited if necessary. This
7248 to other project files and can be hand-edited if necessary. This
7242 also means that tagging creates a new commit. The file
7249 also means that tagging creates a new commit. The file
7243 ".hg/localtags" is used for local tags (not shared among
7250 ".hg/localtags" is used for local tags (not shared among
7244 repositories).
7251 repositories).
7245
7252
7246 Tag commits are usually made at the head of a branch. If the parent
7253 Tag commits are usually made at the head of a branch. If the parent
7247 of the working directory is not a branch head, :hg:`tag` aborts; use
7254 of the working directory is not a branch head, :hg:`tag` aborts; use
7248 -f/--force to force the tag commit to be based on a non-head
7255 -f/--force to force the tag commit to be based on a non-head
7249 changeset.
7256 changeset.
7250
7257
7251 See :hg:`help dates` for a list of formats valid for -d/--date.
7258 See :hg:`help dates` for a list of formats valid for -d/--date.
7252
7259
7253 Since tag names have priority over branch names during revision
7260 Since tag names have priority over branch names during revision
7254 lookup, using an existing branch name as a tag name is discouraged.
7261 lookup, using an existing branch name as a tag name is discouraged.
7255
7262
7256 Returns 0 on success.
7263 Returns 0 on success.
7257 """
7264 """
7258 opts = pycompat.byteskwargs(opts)
7265 opts = pycompat.byteskwargs(opts)
7259 with repo.wlock(), repo.lock():
7266 with repo.wlock(), repo.lock():
7260 rev_ = b"."
7267 rev_ = b"."
7261 names = [t.strip() for t in (name1,) + names]
7268 names = [t.strip() for t in (name1,) + names]
7262 if len(names) != len(set(names)):
7269 if len(names) != len(set(names)):
7263 raise error.Abort(_(b'tag names must be unique'))
7270 raise error.Abort(_(b'tag names must be unique'))
7264 for n in names:
7271 for n in names:
7265 scmutil.checknewlabel(repo, n, b'tag')
7272 scmutil.checknewlabel(repo, n, b'tag')
7266 if not n:
7273 if not n:
7267 raise error.Abort(
7274 raise error.Abort(
7268 _(b'tag names cannot consist entirely of whitespace')
7275 _(b'tag names cannot consist entirely of whitespace')
7269 )
7276 )
7270 if opts.get(b'rev') and opts.get(b'remove'):
7277 if opts.get(b'rev') and opts.get(b'remove'):
7271 raise error.Abort(_(b"--rev and --remove are incompatible"))
7278 raise error.Abort(_(b"--rev and --remove are incompatible"))
7272 if opts.get(b'rev'):
7279 if opts.get(b'rev'):
7273 rev_ = opts[b'rev']
7280 rev_ = opts[b'rev']
7274 message = opts.get(b'message')
7281 message = opts.get(b'message')
7275 if opts.get(b'remove'):
7282 if opts.get(b'remove'):
7276 if opts.get(b'local'):
7283 if opts.get(b'local'):
7277 expectedtype = b'local'
7284 expectedtype = b'local'
7278 else:
7285 else:
7279 expectedtype = b'global'
7286 expectedtype = b'global'
7280
7287
7281 for n in names:
7288 for n in names:
7282 if repo.tagtype(n) == b'global':
7289 if repo.tagtype(n) == b'global':
7283 alltags = tagsmod.findglobaltags(ui, repo)
7290 alltags = tagsmod.findglobaltags(ui, repo)
7284 if alltags[n][0] == nullid:
7291 if alltags[n][0] == nullid:
7285 raise error.Abort(_(b"tag '%s' is already removed") % n)
7292 raise error.Abort(_(b"tag '%s' is already removed") % n)
7286 if not repo.tagtype(n):
7293 if not repo.tagtype(n):
7287 raise error.Abort(_(b"tag '%s' does not exist") % n)
7294 raise error.Abort(_(b"tag '%s' does not exist") % n)
7288 if repo.tagtype(n) != expectedtype:
7295 if repo.tagtype(n) != expectedtype:
7289 if expectedtype == b'global':
7296 if expectedtype == b'global':
7290 raise error.Abort(
7297 raise error.Abort(
7291 _(b"tag '%s' is not a global tag") % n
7298 _(b"tag '%s' is not a global tag") % n
7292 )
7299 )
7293 else:
7300 else:
7294 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7301 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7295 rev_ = b'null'
7302 rev_ = b'null'
7296 if not message:
7303 if not message:
7297 # we don't translate commit messages
7304 # we don't translate commit messages
7298 message = b'Removed tag %s' % b', '.join(names)
7305 message = b'Removed tag %s' % b', '.join(names)
7299 elif not opts.get(b'force'):
7306 elif not opts.get(b'force'):
7300 for n in names:
7307 for n in names:
7301 if n in repo.tags():
7308 if n in repo.tags():
7302 raise error.Abort(
7309 raise error.Abort(
7303 _(b"tag '%s' already exists (use -f to force)") % n
7310 _(b"tag '%s' already exists (use -f to force)") % n
7304 )
7311 )
7305 if not opts.get(b'local'):
7312 if not opts.get(b'local'):
7306 p1, p2 = repo.dirstate.parents()
7313 p1, p2 = repo.dirstate.parents()
7307 if p2 != nullid:
7314 if p2 != nullid:
7308 raise error.Abort(_(b'uncommitted merge'))
7315 raise error.Abort(_(b'uncommitted merge'))
7309 bheads = repo.branchheads()
7316 bheads = repo.branchheads()
7310 if not opts.get(b'force') and bheads and p1 not in bheads:
7317 if not opts.get(b'force') and bheads and p1 not in bheads:
7311 raise error.Abort(
7318 raise error.Abort(
7312 _(
7319 _(
7313 b'working directory is not at a branch head '
7320 b'working directory is not at a branch head '
7314 b'(use -f to force)'
7321 b'(use -f to force)'
7315 )
7322 )
7316 )
7323 )
7317 node = scmutil.revsingle(repo, rev_).node()
7324 node = scmutil.revsingle(repo, rev_).node()
7318
7325
7319 if not message:
7326 if not message:
7320 # we don't translate commit messages
7327 # we don't translate commit messages
7321 message = b'Added tag %s for changeset %s' % (
7328 message = b'Added tag %s for changeset %s' % (
7322 b', '.join(names),
7329 b', '.join(names),
7323 short(node),
7330 short(node),
7324 )
7331 )
7325
7332
7326 date = opts.get(b'date')
7333 date = opts.get(b'date')
7327 if date:
7334 if date:
7328 date = dateutil.parsedate(date)
7335 date = dateutil.parsedate(date)
7329
7336
7330 if opts.get(b'remove'):
7337 if opts.get(b'remove'):
7331 editform = b'tag.remove'
7338 editform = b'tag.remove'
7332 else:
7339 else:
7333 editform = b'tag.add'
7340 editform = b'tag.add'
7334 editor = cmdutil.getcommiteditor(
7341 editor = cmdutil.getcommiteditor(
7335 editform=editform, **pycompat.strkwargs(opts)
7342 editform=editform, **pycompat.strkwargs(opts)
7336 )
7343 )
7337
7344
7338 # don't allow tagging the null rev
7345 # don't allow tagging the null rev
7339 if (
7346 if (
7340 not opts.get(b'remove')
7347 not opts.get(b'remove')
7341 and scmutil.revsingle(repo, rev_).rev() == nullrev
7348 and scmutil.revsingle(repo, rev_).rev() == nullrev
7342 ):
7349 ):
7343 raise error.Abort(_(b"cannot tag null revision"))
7350 raise error.Abort(_(b"cannot tag null revision"))
7344
7351
7345 tagsmod.tag(
7352 tagsmod.tag(
7346 repo,
7353 repo,
7347 names,
7354 names,
7348 node,
7355 node,
7349 message,
7356 message,
7350 opts.get(b'local'),
7357 opts.get(b'local'),
7351 opts.get(b'user'),
7358 opts.get(b'user'),
7352 date,
7359 date,
7353 editor=editor,
7360 editor=editor,
7354 )
7361 )
7355
7362
7356
7363
7357 @command(
7364 @command(
7358 b'tags',
7365 b'tags',
7359 formatteropts,
7366 formatteropts,
7360 b'',
7367 b'',
7361 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7368 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7362 intents={INTENT_READONLY},
7369 intents={INTENT_READONLY},
7363 )
7370 )
7364 def tags(ui, repo, **opts):
7371 def tags(ui, repo, **opts):
7365 """list repository tags
7372 """list repository tags
7366
7373
7367 This lists both regular and local tags. When the -v/--verbose
7374 This lists both regular and local tags. When the -v/--verbose
7368 switch is used, a third column "local" is printed for local tags.
7375 switch is used, a third column "local" is printed for local tags.
7369 When the -q/--quiet switch is used, only the tag name is printed.
7376 When the -q/--quiet switch is used, only the tag name is printed.
7370
7377
7371 .. container:: verbose
7378 .. container:: verbose
7372
7379
7373 Template:
7380 Template:
7374
7381
7375 The following keywords are supported in addition to the common template
7382 The following keywords are supported in addition to the common template
7376 keywords and functions such as ``{tag}``. See also
7383 keywords and functions such as ``{tag}``. See also
7377 :hg:`help templates`.
7384 :hg:`help templates`.
7378
7385
7379 :type: String. ``local`` for local tags.
7386 :type: String. ``local`` for local tags.
7380
7387
7381 Returns 0 on success.
7388 Returns 0 on success.
7382 """
7389 """
7383
7390
7384 opts = pycompat.byteskwargs(opts)
7391 opts = pycompat.byteskwargs(opts)
7385 ui.pager(b'tags')
7392 ui.pager(b'tags')
7386 fm = ui.formatter(b'tags', opts)
7393 fm = ui.formatter(b'tags', opts)
7387 hexfunc = fm.hexfunc
7394 hexfunc = fm.hexfunc
7388
7395
7389 for t, n in reversed(repo.tagslist()):
7396 for t, n in reversed(repo.tagslist()):
7390 hn = hexfunc(n)
7397 hn = hexfunc(n)
7391 label = b'tags.normal'
7398 label = b'tags.normal'
7392 tagtype = b''
7399 tagtype = b''
7393 if repo.tagtype(t) == b'local':
7400 if repo.tagtype(t) == b'local':
7394 label = b'tags.local'
7401 label = b'tags.local'
7395 tagtype = b'local'
7402 tagtype = b'local'
7396
7403
7397 fm.startitem()
7404 fm.startitem()
7398 fm.context(repo=repo)
7405 fm.context(repo=repo)
7399 fm.write(b'tag', b'%s', t, label=label)
7406 fm.write(b'tag', b'%s', t, label=label)
7400 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7407 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7401 fm.condwrite(
7408 fm.condwrite(
7402 not ui.quiet,
7409 not ui.quiet,
7403 b'rev node',
7410 b'rev node',
7404 fmt,
7411 fmt,
7405 repo.changelog.rev(n),
7412 repo.changelog.rev(n),
7406 hn,
7413 hn,
7407 label=label,
7414 label=label,
7408 )
7415 )
7409 fm.condwrite(
7416 fm.condwrite(
7410 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7417 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7411 )
7418 )
7412 fm.plain(b'\n')
7419 fm.plain(b'\n')
7413 fm.end()
7420 fm.end()
7414
7421
7415
7422
7416 @command(
7423 @command(
7417 b'tip',
7424 b'tip',
7418 [
7425 [
7419 (b'p', b'patch', None, _(b'show patch')),
7426 (b'p', b'patch', None, _(b'show patch')),
7420 (b'g', b'git', None, _(b'use git extended diff format')),
7427 (b'g', b'git', None, _(b'use git extended diff format')),
7421 ]
7428 ]
7422 + templateopts,
7429 + templateopts,
7423 _(b'[-p] [-g]'),
7430 _(b'[-p] [-g]'),
7424 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7431 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7425 )
7432 )
7426 def tip(ui, repo, **opts):
7433 def tip(ui, repo, **opts):
7427 """show the tip revision (DEPRECATED)
7434 """show the tip revision (DEPRECATED)
7428
7435
7429 The tip revision (usually just called the tip) is the changeset
7436 The tip revision (usually just called the tip) is the changeset
7430 most recently added to the repository (and therefore the most
7437 most recently added to the repository (and therefore the most
7431 recently changed head).
7438 recently changed head).
7432
7439
7433 If you have just made a commit, that commit will be the tip. If
7440 If you have just made a commit, that commit will be the tip. If
7434 you have just pulled changes from another repository, the tip of
7441 you have just pulled changes from another repository, the tip of
7435 that repository becomes the current tip. The "tip" tag is special
7442 that repository becomes the current tip. The "tip" tag is special
7436 and cannot be renamed or assigned to a different changeset.
7443 and cannot be renamed or assigned to a different changeset.
7437
7444
7438 This command is deprecated, please use :hg:`heads` instead.
7445 This command is deprecated, please use :hg:`heads` instead.
7439
7446
7440 Returns 0 on success.
7447 Returns 0 on success.
7441 """
7448 """
7442 opts = pycompat.byteskwargs(opts)
7449 opts = pycompat.byteskwargs(opts)
7443 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7450 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7444 displayer.show(repo[b'tip'])
7451 displayer.show(repo[b'tip'])
7445 displayer.close()
7452 displayer.close()
7446
7453
7447
7454
7448 @command(
7455 @command(
7449 b'unbundle',
7456 b'unbundle',
7450 [
7457 [
7451 (
7458 (
7452 b'u',
7459 b'u',
7453 b'update',
7460 b'update',
7454 None,
7461 None,
7455 _(b'update to new branch head if changesets were unbundled'),
7462 _(b'update to new branch head if changesets were unbundled'),
7456 )
7463 )
7457 ],
7464 ],
7458 _(b'[-u] FILE...'),
7465 _(b'[-u] FILE...'),
7459 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7466 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7460 )
7467 )
7461 def unbundle(ui, repo, fname1, *fnames, **opts):
7468 def unbundle(ui, repo, fname1, *fnames, **opts):
7462 """apply one or more bundle files
7469 """apply one or more bundle files
7463
7470
7464 Apply one or more bundle files generated by :hg:`bundle`.
7471 Apply one or more bundle files generated by :hg:`bundle`.
7465
7472
7466 Returns 0 on success, 1 if an update has unresolved files.
7473 Returns 0 on success, 1 if an update has unresolved files.
7467 """
7474 """
7468 fnames = (fname1,) + fnames
7475 fnames = (fname1,) + fnames
7469
7476
7470 with repo.lock():
7477 with repo.lock():
7471 for fname in fnames:
7478 for fname in fnames:
7472 f = hg.openpath(ui, fname)
7479 f = hg.openpath(ui, fname)
7473 gen = exchange.readbundle(ui, f, fname)
7480 gen = exchange.readbundle(ui, f, fname)
7474 if isinstance(gen, streamclone.streamcloneapplier):
7481 if isinstance(gen, streamclone.streamcloneapplier):
7475 raise error.Abort(
7482 raise error.Abort(
7476 _(
7483 _(
7477 b'packed bundles cannot be applied with '
7484 b'packed bundles cannot be applied with '
7478 b'"hg unbundle"'
7485 b'"hg unbundle"'
7479 ),
7486 ),
7480 hint=_(b'use "hg debugapplystreamclonebundle"'),
7487 hint=_(b'use "hg debugapplystreamclonebundle"'),
7481 )
7488 )
7482 url = b'bundle:' + fname
7489 url = b'bundle:' + fname
7483 try:
7490 try:
7484 txnname = b'unbundle'
7491 txnname = b'unbundle'
7485 if not isinstance(gen, bundle2.unbundle20):
7492 if not isinstance(gen, bundle2.unbundle20):
7486 txnname = b'unbundle\n%s' % util.hidepassword(url)
7493 txnname = b'unbundle\n%s' % util.hidepassword(url)
7487 with repo.transaction(txnname) as tr:
7494 with repo.transaction(txnname) as tr:
7488 op = bundle2.applybundle(
7495 op = bundle2.applybundle(
7489 repo, gen, tr, source=b'unbundle', url=url
7496 repo, gen, tr, source=b'unbundle', url=url
7490 )
7497 )
7491 except error.BundleUnknownFeatureError as exc:
7498 except error.BundleUnknownFeatureError as exc:
7492 raise error.Abort(
7499 raise error.Abort(
7493 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7500 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7494 hint=_(
7501 hint=_(
7495 b"see https://mercurial-scm.org/"
7502 b"see https://mercurial-scm.org/"
7496 b"wiki/BundleFeature for more "
7503 b"wiki/BundleFeature for more "
7497 b"information"
7504 b"information"
7498 ),
7505 ),
7499 )
7506 )
7500 modheads = bundle2.combinechangegroupresults(op)
7507 modheads = bundle2.combinechangegroupresults(op)
7501
7508
7502 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7509 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7503
7510
7504
7511
7505 @command(
7512 @command(
7506 b'unshelve',
7513 b'unshelve',
7507 [
7514 [
7508 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7515 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7509 (
7516 (
7510 b'c',
7517 b'c',
7511 b'continue',
7518 b'continue',
7512 None,
7519 None,
7513 _(b'continue an incomplete unshelve operation'),
7520 _(b'continue an incomplete unshelve operation'),
7514 ),
7521 ),
7515 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7522 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7516 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7523 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7517 (
7524 (
7518 b'n',
7525 b'n',
7519 b'name',
7526 b'name',
7520 b'',
7527 b'',
7521 _(b'restore shelved change with given name'),
7528 _(b'restore shelved change with given name'),
7522 _(b'NAME'),
7529 _(b'NAME'),
7523 ),
7530 ),
7524 (b't', b'tool', b'', _(b'specify merge tool')),
7531 (b't', b'tool', b'', _(b'specify merge tool')),
7525 (
7532 (
7526 b'',
7533 b'',
7527 b'date',
7534 b'date',
7528 b'',
7535 b'',
7529 _(b'set date for temporary commits (DEPRECATED)'),
7536 _(b'set date for temporary commits (DEPRECATED)'),
7530 _(b'DATE'),
7537 _(b'DATE'),
7531 ),
7538 ),
7532 ],
7539 ],
7533 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7540 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7534 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7541 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7535 )
7542 )
7536 def unshelve(ui, repo, *shelved, **opts):
7543 def unshelve(ui, repo, *shelved, **opts):
7537 """restore a shelved change to the working directory
7544 """restore a shelved change to the working directory
7538
7545
7539 This command accepts an optional name of a shelved change to
7546 This command accepts an optional name of a shelved change to
7540 restore. If none is given, the most recent shelved change is used.
7547 restore. If none is given, the most recent shelved change is used.
7541
7548
7542 If a shelved change is applied successfully, the bundle that
7549 If a shelved change is applied successfully, the bundle that
7543 contains the shelved changes is moved to a backup location
7550 contains the shelved changes is moved to a backup location
7544 (.hg/shelve-backup).
7551 (.hg/shelve-backup).
7545
7552
7546 Since you can restore a shelved change on top of an arbitrary
7553 Since you can restore a shelved change on top of an arbitrary
7547 commit, it is possible that unshelving will result in a conflict
7554 commit, it is possible that unshelving will result in a conflict
7548 between your changes and the commits you are unshelving onto. If
7555 between your changes and the commits you are unshelving onto. If
7549 this occurs, you must resolve the conflict, then use
7556 this occurs, you must resolve the conflict, then use
7550 ``--continue`` to complete the unshelve operation. (The bundle
7557 ``--continue`` to complete the unshelve operation. (The bundle
7551 will not be moved until you successfully complete the unshelve.)
7558 will not be moved until you successfully complete the unshelve.)
7552
7559
7553 (Alternatively, you can use ``--abort`` to abandon an unshelve
7560 (Alternatively, you can use ``--abort`` to abandon an unshelve
7554 that causes a conflict. This reverts the unshelved changes, and
7561 that causes a conflict. This reverts the unshelved changes, and
7555 leaves the bundle in place.)
7562 leaves the bundle in place.)
7556
7563
7557 If bare shelved change (without interactive, include and exclude
7564 If bare shelved change (without interactive, include and exclude
7558 option) was done on newly created branch it would restore branch
7565 option) was done on newly created branch it would restore branch
7559 information to the working directory.
7566 information to the working directory.
7560
7567
7561 After a successful unshelve, the shelved changes are stored in a
7568 After a successful unshelve, the shelved changes are stored in a
7562 backup directory. Only the N most recent backups are kept. N
7569 backup directory. Only the N most recent backups are kept. N
7563 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7570 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7564 configuration option.
7571 configuration option.
7565
7572
7566 .. container:: verbose
7573 .. container:: verbose
7567
7574
7568 Timestamp in seconds is used to decide order of backups. More
7575 Timestamp in seconds is used to decide order of backups. More
7569 than ``maxbackups`` backups are kept, if same timestamp
7576 than ``maxbackups`` backups are kept, if same timestamp
7570 prevents from deciding exact order of them, for safety.
7577 prevents from deciding exact order of them, for safety.
7571
7578
7572 Selected changes can be unshelved with ``--interactive`` flag.
7579 Selected changes can be unshelved with ``--interactive`` flag.
7573 The working directory is updated with the selected changes, and
7580 The working directory is updated with the selected changes, and
7574 only the unselected changes remain shelved.
7581 only the unselected changes remain shelved.
7575 Note: The whole shelve is applied to working directory first before
7582 Note: The whole shelve is applied to working directory first before
7576 running interactively. So, this will bring up all the conflicts between
7583 running interactively. So, this will bring up all the conflicts between
7577 working directory and the shelve, irrespective of which changes will be
7584 working directory and the shelve, irrespective of which changes will be
7578 unshelved.
7585 unshelved.
7579 """
7586 """
7580 with repo.wlock():
7587 with repo.wlock():
7581 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
7588 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
7582
7589
7583
7590
7584 statemod.addunfinished(
7591 statemod.addunfinished(
7585 b'unshelve',
7592 b'unshelve',
7586 fname=b'shelvedstate',
7593 fname=b'shelvedstate',
7587 continueflag=True,
7594 continueflag=True,
7588 abortfunc=shelvemod.hgabortunshelve,
7595 abortfunc=shelvemod.hgabortunshelve,
7589 continuefunc=shelvemod.hgcontinueunshelve,
7596 continuefunc=shelvemod.hgcontinueunshelve,
7590 cmdmsg=_(b'unshelve already in progress'),
7597 cmdmsg=_(b'unshelve already in progress'),
7591 )
7598 )
7592
7599
7593
7600
7594 @command(
7601 @command(
7595 b'update|up|checkout|co',
7602 b'update|up|checkout|co',
7596 [
7603 [
7597 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7604 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7598 (b'c', b'check', None, _(b'require clean working directory')),
7605 (b'c', b'check', None, _(b'require clean working directory')),
7599 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7606 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7600 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7607 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7601 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7608 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7602 ]
7609 ]
7603 + mergetoolopts,
7610 + mergetoolopts,
7604 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7611 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7605 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7612 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7606 helpbasic=True,
7613 helpbasic=True,
7607 )
7614 )
7608 def update(ui, repo, node=None, **opts):
7615 def update(ui, repo, node=None, **opts):
7609 """update working directory (or switch revisions)
7616 """update working directory (or switch revisions)
7610
7617
7611 Update the repository's working directory to the specified
7618 Update the repository's working directory to the specified
7612 changeset. If no changeset is specified, update to the tip of the
7619 changeset. If no changeset is specified, update to the tip of the
7613 current named branch and move the active bookmark (see :hg:`help
7620 current named branch and move the active bookmark (see :hg:`help
7614 bookmarks`).
7621 bookmarks`).
7615
7622
7616 Update sets the working directory's parent revision to the specified
7623 Update sets the working directory's parent revision to the specified
7617 changeset (see :hg:`help parents`).
7624 changeset (see :hg:`help parents`).
7618
7625
7619 If the changeset is not a descendant or ancestor of the working
7626 If the changeset is not a descendant or ancestor of the working
7620 directory's parent and there are uncommitted changes, the update is
7627 directory's parent and there are uncommitted changes, the update is
7621 aborted. With the -c/--check option, the working directory is checked
7628 aborted. With the -c/--check option, the working directory is checked
7622 for uncommitted changes; if none are found, the working directory is
7629 for uncommitted changes; if none are found, the working directory is
7623 updated to the specified changeset.
7630 updated to the specified changeset.
7624
7631
7625 .. container:: verbose
7632 .. container:: verbose
7626
7633
7627 The -C/--clean, -c/--check, and -m/--merge options control what
7634 The -C/--clean, -c/--check, and -m/--merge options control what
7628 happens if the working directory contains uncommitted changes.
7635 happens if the working directory contains uncommitted changes.
7629 At most of one of them can be specified.
7636 At most of one of them can be specified.
7630
7637
7631 1. If no option is specified, and if
7638 1. If no option is specified, and if
7632 the requested changeset is an ancestor or descendant of
7639 the requested changeset is an ancestor or descendant of
7633 the working directory's parent, the uncommitted changes
7640 the working directory's parent, the uncommitted changes
7634 are merged into the requested changeset and the merged
7641 are merged into the requested changeset and the merged
7635 result is left uncommitted. If the requested changeset is
7642 result is left uncommitted. If the requested changeset is
7636 not an ancestor or descendant (that is, it is on another
7643 not an ancestor or descendant (that is, it is on another
7637 branch), the update is aborted and the uncommitted changes
7644 branch), the update is aborted and the uncommitted changes
7638 are preserved.
7645 are preserved.
7639
7646
7640 2. With the -m/--merge option, the update is allowed even if the
7647 2. With the -m/--merge option, the update is allowed even if the
7641 requested changeset is not an ancestor or descendant of
7648 requested changeset is not an ancestor or descendant of
7642 the working directory's parent.
7649 the working directory's parent.
7643
7650
7644 3. With the -c/--check option, the update is aborted and the
7651 3. With the -c/--check option, the update is aborted and the
7645 uncommitted changes are preserved.
7652 uncommitted changes are preserved.
7646
7653
7647 4. With the -C/--clean option, uncommitted changes are discarded and
7654 4. With the -C/--clean option, uncommitted changes are discarded and
7648 the working directory is updated to the requested changeset.
7655 the working directory is updated to the requested changeset.
7649
7656
7650 To cancel an uncommitted merge (and lose your changes), use
7657 To cancel an uncommitted merge (and lose your changes), use
7651 :hg:`merge --abort`.
7658 :hg:`merge --abort`.
7652
7659
7653 Use null as the changeset to remove the working directory (like
7660 Use null as the changeset to remove the working directory (like
7654 :hg:`clone -U`).
7661 :hg:`clone -U`).
7655
7662
7656 If you want to revert just one file to an older revision, use
7663 If you want to revert just one file to an older revision, use
7657 :hg:`revert [-r REV] NAME`.
7664 :hg:`revert [-r REV] NAME`.
7658
7665
7659 See :hg:`help dates` for a list of formats valid for -d/--date.
7666 See :hg:`help dates` for a list of formats valid for -d/--date.
7660
7667
7661 Returns 0 on success, 1 if there are unresolved files.
7668 Returns 0 on success, 1 if there are unresolved files.
7662 """
7669 """
7663 rev = opts.get('rev')
7670 rev = opts.get('rev')
7664 date = opts.get('date')
7671 date = opts.get('date')
7665 clean = opts.get('clean')
7672 clean = opts.get('clean')
7666 check = opts.get('check')
7673 check = opts.get('check')
7667 merge = opts.get('merge')
7674 merge = opts.get('merge')
7668 if rev and node:
7675 if rev and node:
7669 raise error.Abort(_(b"please specify just one revision"))
7676 raise error.Abort(_(b"please specify just one revision"))
7670
7677
7671 if ui.configbool(b'commands', b'update.requiredest'):
7678 if ui.configbool(b'commands', b'update.requiredest'):
7672 if not node and not rev and not date:
7679 if not node and not rev and not date:
7673 raise error.Abort(
7680 raise error.Abort(
7674 _(b'you must specify a destination'),
7681 _(b'you must specify a destination'),
7675 hint=_(b'for example: hg update ".::"'),
7682 hint=_(b'for example: hg update ".::"'),
7676 )
7683 )
7677
7684
7678 if rev is None or rev == b'':
7685 if rev is None or rev == b'':
7679 rev = node
7686 rev = node
7680
7687
7681 if date and rev is not None:
7688 if date and rev is not None:
7682 raise error.Abort(_(b"you can't specify a revision and a date"))
7689 raise error.Abort(_(b"you can't specify a revision and a date"))
7683
7690
7684 if len([x for x in (clean, check, merge) if x]) > 1:
7691 if len([x for x in (clean, check, merge) if x]) > 1:
7685 raise error.Abort(
7692 raise error.Abort(
7686 _(
7693 _(
7687 b"can only specify one of -C/--clean, -c/--check, "
7694 b"can only specify one of -C/--clean, -c/--check, "
7688 b"or -m/--merge"
7695 b"or -m/--merge"
7689 )
7696 )
7690 )
7697 )
7691
7698
7692 updatecheck = None
7699 updatecheck = None
7693 if check:
7700 if check:
7694 updatecheck = b'abort'
7701 updatecheck = b'abort'
7695 elif merge:
7702 elif merge:
7696 updatecheck = b'none'
7703 updatecheck = b'none'
7697
7704
7698 with repo.wlock():
7705 with repo.wlock():
7699 cmdutil.clearunfinished(repo)
7706 cmdutil.clearunfinished(repo)
7700 if date:
7707 if date:
7701 rev = cmdutil.finddate(ui, repo, date)
7708 rev = cmdutil.finddate(ui, repo, date)
7702
7709
7703 # if we defined a bookmark, we have to remember the original name
7710 # if we defined a bookmark, we have to remember the original name
7704 brev = rev
7711 brev = rev
7705 if rev:
7712 if rev:
7706 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7713 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7707 ctx = scmutil.revsingle(repo, rev, default=None)
7714 ctx = scmutil.revsingle(repo, rev, default=None)
7708 rev = ctx.rev()
7715 rev = ctx.rev()
7709 hidden = ctx.hidden()
7716 hidden = ctx.hidden()
7710 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7717 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7711 with ui.configoverride(overrides, b'update'):
7718 with ui.configoverride(overrides, b'update'):
7712 ret = hg.updatetotally(
7719 ret = hg.updatetotally(
7713 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7720 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7714 )
7721 )
7715 if hidden:
7722 if hidden:
7716 ctxstr = ctx.hex()[:12]
7723 ctxstr = ctx.hex()[:12]
7717 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7724 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7718
7725
7719 if ctx.obsolete():
7726 if ctx.obsolete():
7720 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7727 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7721 ui.warn(b"(%s)\n" % obsfatemsg)
7728 ui.warn(b"(%s)\n" % obsfatemsg)
7722 return ret
7729 return ret
7723
7730
7724
7731
7725 @command(
7732 @command(
7726 b'verify',
7733 b'verify',
7727 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7734 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7728 helpcategory=command.CATEGORY_MAINTENANCE,
7735 helpcategory=command.CATEGORY_MAINTENANCE,
7729 )
7736 )
7730 def verify(ui, repo, **opts):
7737 def verify(ui, repo, **opts):
7731 """verify the integrity of the repository
7738 """verify the integrity of the repository
7732
7739
7733 Verify the integrity of the current repository.
7740 Verify the integrity of the current repository.
7734
7741
7735 This will perform an extensive check of the repository's
7742 This will perform an extensive check of the repository's
7736 integrity, validating the hashes and checksums of each entry in
7743 integrity, validating the hashes and checksums of each entry in
7737 the changelog, manifest, and tracked files, as well as the
7744 the changelog, manifest, and tracked files, as well as the
7738 integrity of their crosslinks and indices.
7745 integrity of their crosslinks and indices.
7739
7746
7740 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7747 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7741 for more information about recovery from corruption of the
7748 for more information about recovery from corruption of the
7742 repository.
7749 repository.
7743
7750
7744 Returns 0 on success, 1 if errors are encountered.
7751 Returns 0 on success, 1 if errors are encountered.
7745 """
7752 """
7746 opts = pycompat.byteskwargs(opts)
7753 opts = pycompat.byteskwargs(opts)
7747
7754
7748 level = None
7755 level = None
7749 if opts[b'full']:
7756 if opts[b'full']:
7750 level = verifymod.VERIFY_FULL
7757 level = verifymod.VERIFY_FULL
7751 return hg.verify(repo, level)
7758 return hg.verify(repo, level)
7752
7759
7753
7760
7754 @command(
7761 @command(
7755 b'version',
7762 b'version',
7756 [] + formatteropts,
7763 [] + formatteropts,
7757 helpcategory=command.CATEGORY_HELP,
7764 helpcategory=command.CATEGORY_HELP,
7758 norepo=True,
7765 norepo=True,
7759 intents={INTENT_READONLY},
7766 intents={INTENT_READONLY},
7760 )
7767 )
7761 def version_(ui, **opts):
7768 def version_(ui, **opts):
7762 """output version and copyright information
7769 """output version and copyright information
7763
7770
7764 .. container:: verbose
7771 .. container:: verbose
7765
7772
7766 Template:
7773 Template:
7767
7774
7768 The following keywords are supported. See also :hg:`help templates`.
7775 The following keywords are supported. See also :hg:`help templates`.
7769
7776
7770 :extensions: List of extensions.
7777 :extensions: List of extensions.
7771 :ver: String. Version number.
7778 :ver: String. Version number.
7772
7779
7773 And each entry of ``{extensions}`` provides the following sub-keywords
7780 And each entry of ``{extensions}`` provides the following sub-keywords
7774 in addition to ``{ver}``.
7781 in addition to ``{ver}``.
7775
7782
7776 :bundled: Boolean. True if included in the release.
7783 :bundled: Boolean. True if included in the release.
7777 :name: String. Extension name.
7784 :name: String. Extension name.
7778 """
7785 """
7779 opts = pycompat.byteskwargs(opts)
7786 opts = pycompat.byteskwargs(opts)
7780 if ui.verbose:
7787 if ui.verbose:
7781 ui.pager(b'version')
7788 ui.pager(b'version')
7782 fm = ui.formatter(b"version", opts)
7789 fm = ui.formatter(b"version", opts)
7783 fm.startitem()
7790 fm.startitem()
7784 fm.write(
7791 fm.write(
7785 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7792 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7786 )
7793 )
7787 license = _(
7794 license = _(
7788 b"(see https://mercurial-scm.org for more information)\n"
7795 b"(see https://mercurial-scm.org for more information)\n"
7789 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7796 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7790 b"This is free software; see the source for copying conditions. "
7797 b"This is free software; see the source for copying conditions. "
7791 b"There is NO\nwarranty; "
7798 b"There is NO\nwarranty; "
7792 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7799 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7793 )
7800 )
7794 if not ui.quiet:
7801 if not ui.quiet:
7795 fm.plain(license)
7802 fm.plain(license)
7796
7803
7797 if ui.verbose:
7804 if ui.verbose:
7798 fm.plain(_(b"\nEnabled extensions:\n\n"))
7805 fm.plain(_(b"\nEnabled extensions:\n\n"))
7799 # format names and versions into columns
7806 # format names and versions into columns
7800 names = []
7807 names = []
7801 vers = []
7808 vers = []
7802 isinternals = []
7809 isinternals = []
7803 for name, module in extensions.extensions():
7810 for name, module in extensions.extensions():
7804 names.append(name)
7811 names.append(name)
7805 vers.append(extensions.moduleversion(module) or None)
7812 vers.append(extensions.moduleversion(module) or None)
7806 isinternals.append(extensions.ismoduleinternal(module))
7813 isinternals.append(extensions.ismoduleinternal(module))
7807 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7814 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7808 if names:
7815 if names:
7809 namefmt = b" %%-%ds " % max(len(n) for n in names)
7816 namefmt = b" %%-%ds " % max(len(n) for n in names)
7810 places = [_(b"external"), _(b"internal")]
7817 places = [_(b"external"), _(b"internal")]
7811 for n, v, p in zip(names, vers, isinternals):
7818 for n, v, p in zip(names, vers, isinternals):
7812 fn.startitem()
7819 fn.startitem()
7813 fn.condwrite(ui.verbose, b"name", namefmt, n)
7820 fn.condwrite(ui.verbose, b"name", namefmt, n)
7814 if ui.verbose:
7821 if ui.verbose:
7815 fn.plain(b"%s " % places[p])
7822 fn.plain(b"%s " % places[p])
7816 fn.data(bundled=p)
7823 fn.data(bundled=p)
7817 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7824 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7818 if ui.verbose:
7825 if ui.verbose:
7819 fn.plain(b"\n")
7826 fn.plain(b"\n")
7820 fn.end()
7827 fn.end()
7821 fm.end()
7828 fm.end()
7822
7829
7823
7830
7824 def loadcmdtable(ui, name, cmdtable):
7831 def loadcmdtable(ui, name, cmdtable):
7825 """Load command functions from specified cmdtable
7832 """Load command functions from specified cmdtable
7826 """
7833 """
7827 overrides = [cmd for cmd in cmdtable if cmd in table]
7834 overrides = [cmd for cmd in cmdtable if cmd in table]
7828 if overrides:
7835 if overrides:
7829 ui.warn(
7836 ui.warn(
7830 _(b"extension '%s' overrides commands: %s\n")
7837 _(b"extension '%s' overrides commands: %s\n")
7831 % (name, b" ".join(overrides))
7838 % (name, b" ".join(overrides))
7832 )
7839 )
7833 table.update(cmdtable)
7840 table.update(cmdtable)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now