##// END OF EJS Templates
status: outputting structured unfinished-operation information...
Rodrigo Damazio Bovendorp -
r44313:aac921f5 default
parent child Browse files
Show More
@@ -1,4017 +1,4024 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 (b'g', b'git', None, _(b'use git extended diff format')),
173 (b'g', b'git', None, _(b'use git extended diff format')),
174 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
174 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
175 (b'', b'nodates', None, _(b'omit dates from diff headers')),
175 (b'', b'nodates', None, _(b'omit dates from diff headers')),
176 ]
176 ]
177
177
178 diffwsopts = [
178 diffwsopts = [
179 (
179 (
180 b'w',
180 b'w',
181 b'ignore-all-space',
181 b'ignore-all-space',
182 None,
182 None,
183 _(b'ignore white space when comparing lines'),
183 _(b'ignore white space when comparing lines'),
184 ),
184 ),
185 (
185 (
186 b'b',
186 b'b',
187 b'ignore-space-change',
187 b'ignore-space-change',
188 None,
188 None,
189 _(b'ignore changes in the amount of white space'),
189 _(b'ignore changes in the amount of white space'),
190 ),
190 ),
191 (
191 (
192 b'B',
192 b'B',
193 b'ignore-blank-lines',
193 b'ignore-blank-lines',
194 None,
194 None,
195 _(b'ignore changes whose lines are all blank'),
195 _(b'ignore changes whose lines are all blank'),
196 ),
196 ),
197 (
197 (
198 b'Z',
198 b'Z',
199 b'ignore-space-at-eol',
199 b'ignore-space-at-eol',
200 None,
200 None,
201 _(b'ignore changes in whitespace at EOL'),
201 _(b'ignore changes in whitespace at EOL'),
202 ),
202 ),
203 ]
203 ]
204
204
205 diffopts2 = (
205 diffopts2 = (
206 [
206 [
207 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
207 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
208 (
208 (
209 b'p',
209 b'p',
210 b'show-function',
210 b'show-function',
211 None,
211 None,
212 _(b'show which function each change is in'),
212 _(b'show which function each change is in'),
213 ),
213 ),
214 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
214 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
215 ]
215 ]
216 + diffwsopts
216 + diffwsopts
217 + [
217 + [
218 (
218 (
219 b'U',
219 b'U',
220 b'unified',
220 b'unified',
221 b'',
221 b'',
222 _(b'number of lines of context to show'),
222 _(b'number of lines of context to show'),
223 _(b'NUM'),
223 _(b'NUM'),
224 ),
224 ),
225 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
225 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
226 (
226 (
227 b'',
227 b'',
228 b'root',
228 b'root',
229 b'',
229 b'',
230 _(b'produce diffs relative to subdirectory'),
230 _(b'produce diffs relative to subdirectory'),
231 _(b'DIR'),
231 _(b'DIR'),
232 ),
232 ),
233 ]
233 ]
234 )
234 )
235
235
236 mergetoolopts = [
236 mergetoolopts = [
237 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
237 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
238 ]
238 ]
239
239
240 similarityopts = [
240 similarityopts = [
241 (
241 (
242 b's',
242 b's',
243 b'similarity',
243 b'similarity',
244 b'',
244 b'',
245 _(b'guess renamed files by similarity (0<=s<=100)'),
245 _(b'guess renamed files by similarity (0<=s<=100)'),
246 _(b'SIMILARITY'),
246 _(b'SIMILARITY'),
247 )
247 )
248 ]
248 ]
249
249
250 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
250 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
251
251
252 debugrevlogopts = [
252 debugrevlogopts = [
253 (b'c', b'changelog', False, _(b'open changelog')),
253 (b'c', b'changelog', False, _(b'open changelog')),
254 (b'm', b'manifest', False, _(b'open manifest')),
254 (b'm', b'manifest', False, _(b'open manifest')),
255 (b'', b'dir', b'', _(b'open directory manifest')),
255 (b'', b'dir', b'', _(b'open directory manifest')),
256 ]
256 ]
257
257
258 # special string such that everything below this line will be ingored in the
258 # special string such that everything below this line will be ingored in the
259 # editor text
259 # editor text
260 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
260 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
261
261
262
262
263 def resolvecommitoptions(ui, opts):
263 def resolvecommitoptions(ui, opts):
264 """modify commit options dict to handle related options
264 """modify commit options dict to handle related options
265
265
266 The return value indicates that ``rewrite.update-timestamp`` is the reason
266 The return value indicates that ``rewrite.update-timestamp`` is the reason
267 the ``date`` option is set.
267 the ``date`` option is set.
268 """
268 """
269 if opts.get(b'date') and opts.get(b'currentdate'):
269 if opts.get(b'date') and opts.get(b'currentdate'):
270 raise error.Abort(_(b'--date and --currentdate are mutually exclusive'))
270 raise error.Abort(_(b'--date and --currentdate are mutually exclusive'))
271 if opts.get(b'user') and opts.get(b'currentuser'):
271 if opts.get(b'user') and opts.get(b'currentuser'):
272 raise error.Abort(_(b'--user and --currentuser are mutually exclusive'))
272 raise error.Abort(_(b'--user and --currentuser are mutually exclusive'))
273
273
274 datemaydiffer = False # date-only change should be ignored?
274 datemaydiffer = False # date-only change should be ignored?
275
275
276 if opts.get(b'currentdate'):
276 if opts.get(b'currentdate'):
277 opts[b'date'] = b'%d %d' % dateutil.makedate()
277 opts[b'date'] = b'%d %d' % dateutil.makedate()
278 elif (
278 elif (
279 not opts.get(b'date')
279 not opts.get(b'date')
280 and ui.configbool(b'rewrite', b'update-timestamp')
280 and ui.configbool(b'rewrite', b'update-timestamp')
281 and opts.get(b'currentdate') is None
281 and opts.get(b'currentdate') is None
282 ):
282 ):
283 opts[b'date'] = b'%d %d' % dateutil.makedate()
283 opts[b'date'] = b'%d %d' % dateutil.makedate()
284 datemaydiffer = True
284 datemaydiffer = True
285
285
286 if opts.get(b'currentuser'):
286 if opts.get(b'currentuser'):
287 opts[b'user'] = ui.username()
287 opts[b'user'] = ui.username()
288
288
289 return datemaydiffer
289 return datemaydiffer
290
290
291
291
292 def checknotesize(ui, opts):
292 def checknotesize(ui, opts):
293 """ make sure note is of valid format """
293 """ make sure note is of valid format """
294
294
295 note = opts.get(b'note')
295 note = opts.get(b'note')
296 if not note:
296 if not note:
297 return
297 return
298
298
299 if len(note) > 255:
299 if len(note) > 255:
300 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
300 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
301 if b'\n' in note:
301 if b'\n' in note:
302 raise error.Abort(_(b"note cannot contain a newline"))
302 raise error.Abort(_(b"note cannot contain a newline"))
303
303
304
304
305 def ishunk(x):
305 def ishunk(x):
306 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
306 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
307 return isinstance(x, hunkclasses)
307 return isinstance(x, hunkclasses)
308
308
309
309
310 def newandmodified(chunks, originalchunks):
310 def newandmodified(chunks, originalchunks):
311 newlyaddedandmodifiedfiles = set()
311 newlyaddedandmodifiedfiles = set()
312 alsorestore = set()
312 alsorestore = set()
313 for chunk in chunks:
313 for chunk in chunks:
314 if (
314 if (
315 ishunk(chunk)
315 ishunk(chunk)
316 and chunk.header.isnewfile()
316 and chunk.header.isnewfile()
317 and chunk not in originalchunks
317 and chunk not in originalchunks
318 ):
318 ):
319 newlyaddedandmodifiedfiles.add(chunk.header.filename())
319 newlyaddedandmodifiedfiles.add(chunk.header.filename())
320 alsorestore.update(
320 alsorestore.update(
321 set(chunk.header.files()) - {chunk.header.filename()}
321 set(chunk.header.files()) - {chunk.header.filename()}
322 )
322 )
323 return newlyaddedandmodifiedfiles, alsorestore
323 return newlyaddedandmodifiedfiles, alsorestore
324
324
325
325
326 def parsealiases(cmd):
326 def parsealiases(cmd):
327 return cmd.split(b"|")
327 return cmd.split(b"|")
328
328
329
329
330 def setupwrapcolorwrite(ui):
330 def setupwrapcolorwrite(ui):
331 # wrap ui.write so diff output can be labeled/colorized
331 # wrap ui.write so diff output can be labeled/colorized
332 def wrapwrite(orig, *args, **kw):
332 def wrapwrite(orig, *args, **kw):
333 label = kw.pop('label', b'')
333 label = kw.pop('label', b'')
334 for chunk, l in patch.difflabel(lambda: args):
334 for chunk, l in patch.difflabel(lambda: args):
335 orig(chunk, label=label + l)
335 orig(chunk, label=label + l)
336
336
337 oldwrite = ui.write
337 oldwrite = ui.write
338
338
339 def wrap(*args, **kwargs):
339 def wrap(*args, **kwargs):
340 return wrapwrite(oldwrite, *args, **kwargs)
340 return wrapwrite(oldwrite, *args, **kwargs)
341
341
342 setattr(ui, 'write', wrap)
342 setattr(ui, 'write', wrap)
343 return oldwrite
343 return oldwrite
344
344
345
345
346 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
346 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
347 try:
347 try:
348 if usecurses:
348 if usecurses:
349 if testfile:
349 if testfile:
350 recordfn = crecordmod.testdecorator(
350 recordfn = crecordmod.testdecorator(
351 testfile, crecordmod.testchunkselector
351 testfile, crecordmod.testchunkselector
352 )
352 )
353 else:
353 else:
354 recordfn = crecordmod.chunkselector
354 recordfn = crecordmod.chunkselector
355
355
356 return crecordmod.filterpatch(
356 return crecordmod.filterpatch(
357 ui, originalhunks, recordfn, operation
357 ui, originalhunks, recordfn, operation
358 )
358 )
359 except crecordmod.fallbackerror as e:
359 except crecordmod.fallbackerror as e:
360 ui.warn(b'%s\n' % e.message) # pytype: disable=attribute-error
360 ui.warn(b'%s\n' % e.message) # pytype: disable=attribute-error
361 ui.warn(_(b'falling back to text mode\n'))
361 ui.warn(_(b'falling back to text mode\n'))
362
362
363 return patch.filterpatch(ui, originalhunks, match, operation)
363 return patch.filterpatch(ui, originalhunks, match, operation)
364
364
365
365
366 def recordfilter(ui, originalhunks, match, operation=None):
366 def recordfilter(ui, originalhunks, match, operation=None):
367 """ Prompts the user to filter the originalhunks and return a list of
367 """ Prompts the user to filter the originalhunks and return a list of
368 selected hunks.
368 selected hunks.
369 *operation* is used for to build ui messages to indicate the user what
369 *operation* is used for to build ui messages to indicate the user what
370 kind of filtering they are doing: reverting, committing, shelving, etc.
370 kind of filtering they are doing: reverting, committing, shelving, etc.
371 (see patch.filterpatch).
371 (see patch.filterpatch).
372 """
372 """
373 usecurses = crecordmod.checkcurses(ui)
373 usecurses = crecordmod.checkcurses(ui)
374 testfile = ui.config(b'experimental', b'crecordtest')
374 testfile = ui.config(b'experimental', b'crecordtest')
375 oldwrite = setupwrapcolorwrite(ui)
375 oldwrite = setupwrapcolorwrite(ui)
376 try:
376 try:
377 newchunks, newopts = filterchunks(
377 newchunks, newopts = filterchunks(
378 ui, originalhunks, usecurses, testfile, match, operation
378 ui, originalhunks, usecurses, testfile, match, operation
379 )
379 )
380 finally:
380 finally:
381 ui.write = oldwrite
381 ui.write = oldwrite
382 return newchunks, newopts
382 return newchunks, newopts
383
383
384
384
385 def dorecord(
385 def dorecord(
386 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
386 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
387 ):
387 ):
388 opts = pycompat.byteskwargs(opts)
388 opts = pycompat.byteskwargs(opts)
389 if not ui.interactive():
389 if not ui.interactive():
390 if cmdsuggest:
390 if cmdsuggest:
391 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
391 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
392 else:
392 else:
393 msg = _(b'running non-interactively')
393 msg = _(b'running non-interactively')
394 raise error.Abort(msg)
394 raise error.Abort(msg)
395
395
396 # make sure username is set before going interactive
396 # make sure username is set before going interactive
397 if not opts.get(b'user'):
397 if not opts.get(b'user'):
398 ui.username() # raise exception, username not provided
398 ui.username() # raise exception, username not provided
399
399
400 def recordfunc(ui, repo, message, match, opts):
400 def recordfunc(ui, repo, message, match, opts):
401 """This is generic record driver.
401 """This is generic record driver.
402
402
403 Its job is to interactively filter local changes, and
403 Its job is to interactively filter local changes, and
404 accordingly prepare working directory into a state in which the
404 accordingly prepare working directory into a state in which the
405 job can be delegated to a non-interactive commit command such as
405 job can be delegated to a non-interactive commit command such as
406 'commit' or 'qrefresh'.
406 'commit' or 'qrefresh'.
407
407
408 After the actual job is done by non-interactive command, the
408 After the actual job is done by non-interactive command, the
409 working directory is restored to its original state.
409 working directory is restored to its original state.
410
410
411 In the end we'll record interesting changes, and everything else
411 In the end we'll record interesting changes, and everything else
412 will be left in place, so the user can continue working.
412 will be left in place, so the user can continue working.
413 """
413 """
414 if not opts.get(b'interactive-unshelve'):
414 if not opts.get(b'interactive-unshelve'):
415 checkunfinished(repo, commit=True)
415 checkunfinished(repo, commit=True)
416 wctx = repo[None]
416 wctx = repo[None]
417 merge = len(wctx.parents()) > 1
417 merge = len(wctx.parents()) > 1
418 if merge:
418 if merge:
419 raise error.Abort(
419 raise error.Abort(
420 _(
420 _(
421 b'cannot partially commit a merge '
421 b'cannot partially commit a merge '
422 b'(use "hg commit" instead)'
422 b'(use "hg commit" instead)'
423 )
423 )
424 )
424 )
425
425
426 def fail(f, msg):
426 def fail(f, msg):
427 raise error.Abort(b'%s: %s' % (f, msg))
427 raise error.Abort(b'%s: %s' % (f, msg))
428
428
429 force = opts.get(b'force')
429 force = opts.get(b'force')
430 if not force:
430 if not force:
431 match = matchmod.badmatch(match, fail)
431 match = matchmod.badmatch(match, fail)
432
432
433 status = repo.status(match=match)
433 status = repo.status(match=match)
434
434
435 overrides = {(b'ui', b'commitsubrepos'): True}
435 overrides = {(b'ui', b'commitsubrepos'): True}
436
436
437 with repo.ui.configoverride(overrides, b'record'):
437 with repo.ui.configoverride(overrides, b'record'):
438 # subrepoutil.precommit() modifies the status
438 # subrepoutil.precommit() modifies the status
439 tmpstatus = scmutil.status(
439 tmpstatus = scmutil.status(
440 copymod.copy(status.modified),
440 copymod.copy(status.modified),
441 copymod.copy(status.added),
441 copymod.copy(status.added),
442 copymod.copy(status.removed),
442 copymod.copy(status.removed),
443 copymod.copy(status.deleted),
443 copymod.copy(status.deleted),
444 copymod.copy(status.unknown),
444 copymod.copy(status.unknown),
445 copymod.copy(status.ignored),
445 copymod.copy(status.ignored),
446 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
446 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
447 )
447 )
448
448
449 # Force allows -X subrepo to skip the subrepo.
449 # Force allows -X subrepo to skip the subrepo.
450 subs, commitsubs, newstate = subrepoutil.precommit(
450 subs, commitsubs, newstate = subrepoutil.precommit(
451 repo.ui, wctx, tmpstatus, match, force=True
451 repo.ui, wctx, tmpstatus, match, force=True
452 )
452 )
453 for s in subs:
453 for s in subs:
454 if s in commitsubs:
454 if s in commitsubs:
455 dirtyreason = wctx.sub(s).dirtyreason(True)
455 dirtyreason = wctx.sub(s).dirtyreason(True)
456 raise error.Abort(dirtyreason)
456 raise error.Abort(dirtyreason)
457
457
458 if not force:
458 if not force:
459 repo.checkcommitpatterns(wctx, match, status, fail)
459 repo.checkcommitpatterns(wctx, match, status, fail)
460 diffopts = patch.difffeatureopts(
460 diffopts = patch.difffeatureopts(
461 ui,
461 ui,
462 opts=opts,
462 opts=opts,
463 whitespace=True,
463 whitespace=True,
464 section=b'commands',
464 section=b'commands',
465 configprefix=b'commit.interactive.',
465 configprefix=b'commit.interactive.',
466 )
466 )
467 diffopts.nodates = True
467 diffopts.nodates = True
468 diffopts.git = True
468 diffopts.git = True
469 diffopts.showfunc = True
469 diffopts.showfunc = True
470 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
470 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
471 originalchunks = patch.parsepatch(originaldiff)
471 originalchunks = patch.parsepatch(originaldiff)
472 match = scmutil.match(repo[None], pats)
472 match = scmutil.match(repo[None], pats)
473
473
474 # 1. filter patch, since we are intending to apply subset of it
474 # 1. filter patch, since we are intending to apply subset of it
475 try:
475 try:
476 chunks, newopts = filterfn(ui, originalchunks, match)
476 chunks, newopts = filterfn(ui, originalchunks, match)
477 except error.PatchError as err:
477 except error.PatchError as err:
478 raise error.Abort(_(b'error parsing patch: %s') % err)
478 raise error.Abort(_(b'error parsing patch: %s') % err)
479 opts.update(newopts)
479 opts.update(newopts)
480
480
481 # We need to keep a backup of files that have been newly added and
481 # We need to keep a backup of files that have been newly added and
482 # modified during the recording process because there is a previous
482 # modified during the recording process because there is a previous
483 # version without the edit in the workdir. We also will need to restore
483 # version without the edit in the workdir. We also will need to restore
484 # files that were the sources of renames so that the patch application
484 # files that were the sources of renames so that the patch application
485 # works.
485 # works.
486 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
486 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
487 chunks, originalchunks
487 chunks, originalchunks
488 )
488 )
489 contenders = set()
489 contenders = set()
490 for h in chunks:
490 for h in chunks:
491 try:
491 try:
492 contenders.update(set(h.files()))
492 contenders.update(set(h.files()))
493 except AttributeError:
493 except AttributeError:
494 pass
494 pass
495
495
496 changed = status.modified + status.added + status.removed
496 changed = status.modified + status.added + status.removed
497 newfiles = [f for f in changed if f in contenders]
497 newfiles = [f for f in changed if f in contenders]
498 if not newfiles:
498 if not newfiles:
499 ui.status(_(b'no changes to record\n'))
499 ui.status(_(b'no changes to record\n'))
500 return 0
500 return 0
501
501
502 modified = set(status.modified)
502 modified = set(status.modified)
503
503
504 # 2. backup changed files, so we can restore them in the end
504 # 2. backup changed files, so we can restore them in the end
505
505
506 if backupall:
506 if backupall:
507 tobackup = changed
507 tobackup = changed
508 else:
508 else:
509 tobackup = [
509 tobackup = [
510 f
510 f
511 for f in newfiles
511 for f in newfiles
512 if f in modified or f in newlyaddedandmodifiedfiles
512 if f in modified or f in newlyaddedandmodifiedfiles
513 ]
513 ]
514 backups = {}
514 backups = {}
515 if tobackup:
515 if tobackup:
516 backupdir = repo.vfs.join(b'record-backups')
516 backupdir = repo.vfs.join(b'record-backups')
517 try:
517 try:
518 os.mkdir(backupdir)
518 os.mkdir(backupdir)
519 except OSError as err:
519 except OSError as err:
520 if err.errno != errno.EEXIST:
520 if err.errno != errno.EEXIST:
521 raise
521 raise
522 try:
522 try:
523 # backup continues
523 # backup continues
524 for f in tobackup:
524 for f in tobackup:
525 fd, tmpname = pycompat.mkstemp(
525 fd, tmpname = pycompat.mkstemp(
526 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
526 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
527 )
527 )
528 os.close(fd)
528 os.close(fd)
529 ui.debug(b'backup %r as %r\n' % (f, tmpname))
529 ui.debug(b'backup %r as %r\n' % (f, tmpname))
530 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
530 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
531 backups[f] = tmpname
531 backups[f] = tmpname
532
532
533 fp = stringio()
533 fp = stringio()
534 for c in chunks:
534 for c in chunks:
535 fname = c.filename()
535 fname = c.filename()
536 if fname in backups:
536 if fname in backups:
537 c.write(fp)
537 c.write(fp)
538 dopatch = fp.tell()
538 dopatch = fp.tell()
539 fp.seek(0)
539 fp.seek(0)
540
540
541 # 2.5 optionally review / modify patch in text editor
541 # 2.5 optionally review / modify patch in text editor
542 if opts.get(b'review', False):
542 if opts.get(b'review', False):
543 patchtext = (
543 patchtext = (
544 crecordmod.diffhelptext
544 crecordmod.diffhelptext
545 + crecordmod.patchhelptext
545 + crecordmod.patchhelptext
546 + fp.read()
546 + fp.read()
547 )
547 )
548 reviewedpatch = ui.edit(
548 reviewedpatch = ui.edit(
549 patchtext, b"", action=b"diff", repopath=repo.path
549 patchtext, b"", action=b"diff", repopath=repo.path
550 )
550 )
551 fp.truncate(0)
551 fp.truncate(0)
552 fp.write(reviewedpatch)
552 fp.write(reviewedpatch)
553 fp.seek(0)
553 fp.seek(0)
554
554
555 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
555 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
556 # 3a. apply filtered patch to clean repo (clean)
556 # 3a. apply filtered patch to clean repo (clean)
557 if backups:
557 if backups:
558 # Equivalent to hg.revert
558 # Equivalent to hg.revert
559 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
559 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
560 mergemod.update(
560 mergemod.update(
561 repo,
561 repo,
562 repo.dirstate.p1(),
562 repo.dirstate.p1(),
563 branchmerge=False,
563 branchmerge=False,
564 force=True,
564 force=True,
565 matcher=m,
565 matcher=m,
566 )
566 )
567
567
568 # 3b. (apply)
568 # 3b. (apply)
569 if dopatch:
569 if dopatch:
570 try:
570 try:
571 ui.debug(b'applying patch\n')
571 ui.debug(b'applying patch\n')
572 ui.debug(fp.getvalue())
572 ui.debug(fp.getvalue())
573 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
573 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
574 except error.PatchError as err:
574 except error.PatchError as err:
575 raise error.Abort(pycompat.bytestr(err))
575 raise error.Abort(pycompat.bytestr(err))
576 del fp
576 del fp
577
577
578 # 4. We prepared working directory according to filtered
578 # 4. We prepared working directory according to filtered
579 # patch. Now is the time to delegate the job to
579 # patch. Now is the time to delegate the job to
580 # commit/qrefresh or the like!
580 # commit/qrefresh or the like!
581
581
582 # Make all of the pathnames absolute.
582 # Make all of the pathnames absolute.
583 newfiles = [repo.wjoin(nf) for nf in newfiles]
583 newfiles = [repo.wjoin(nf) for nf in newfiles]
584 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
584 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
585 finally:
585 finally:
586 # 5. finally restore backed-up files
586 # 5. finally restore backed-up files
587 try:
587 try:
588 dirstate = repo.dirstate
588 dirstate = repo.dirstate
589 for realname, tmpname in pycompat.iteritems(backups):
589 for realname, tmpname in pycompat.iteritems(backups):
590 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
590 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
591
591
592 if dirstate[realname] == b'n':
592 if dirstate[realname] == b'n':
593 # without normallookup, restoring timestamp
593 # without normallookup, restoring timestamp
594 # may cause partially committed files
594 # may cause partially committed files
595 # to be treated as unmodified
595 # to be treated as unmodified
596 dirstate.normallookup(realname)
596 dirstate.normallookup(realname)
597
597
598 # copystat=True here and above are a hack to trick any
598 # copystat=True here and above are a hack to trick any
599 # editors that have f open that we haven't modified them.
599 # editors that have f open that we haven't modified them.
600 #
600 #
601 # Also note that this racy as an editor could notice the
601 # Also note that this racy as an editor could notice the
602 # file's mtime before we've finished writing it.
602 # file's mtime before we've finished writing it.
603 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
603 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
604 os.unlink(tmpname)
604 os.unlink(tmpname)
605 if tobackup:
605 if tobackup:
606 os.rmdir(backupdir)
606 os.rmdir(backupdir)
607 except OSError:
607 except OSError:
608 pass
608 pass
609
609
610 def recordinwlock(ui, repo, message, match, opts):
610 def recordinwlock(ui, repo, message, match, opts):
611 with repo.wlock():
611 with repo.wlock():
612 return recordfunc(ui, repo, message, match, opts)
612 return recordfunc(ui, repo, message, match, opts)
613
613
614 return commit(ui, repo, recordinwlock, pats, opts)
614 return commit(ui, repo, recordinwlock, pats, opts)
615
615
616
616
617 class dirnode(object):
617 class dirnode(object):
618 """
618 """
619 Represent a directory in user working copy with information required for
619 Represent a directory in user working copy with information required for
620 the purpose of tersing its status.
620 the purpose of tersing its status.
621
621
622 path is the path to the directory, without a trailing '/'
622 path is the path to the directory, without a trailing '/'
623
623
624 statuses is a set of statuses of all files in this directory (this includes
624 statuses is a set of statuses of all files in this directory (this includes
625 all the files in all the subdirectories too)
625 all the files in all the subdirectories too)
626
626
627 files is a list of files which are direct child of this directory
627 files is a list of files which are direct child of this directory
628
628
629 subdirs is a dictionary of sub-directory name as the key and it's own
629 subdirs is a dictionary of sub-directory name as the key and it's own
630 dirnode object as the value
630 dirnode object as the value
631 """
631 """
632
632
633 def __init__(self, dirpath):
633 def __init__(self, dirpath):
634 self.path = dirpath
634 self.path = dirpath
635 self.statuses = set()
635 self.statuses = set()
636 self.files = []
636 self.files = []
637 self.subdirs = {}
637 self.subdirs = {}
638
638
639 def _addfileindir(self, filename, status):
639 def _addfileindir(self, filename, status):
640 """Add a file in this directory as a direct child."""
640 """Add a file in this directory as a direct child."""
641 self.files.append((filename, status))
641 self.files.append((filename, status))
642
642
643 def addfile(self, filename, status):
643 def addfile(self, filename, status):
644 """
644 """
645 Add a file to this directory or to its direct parent directory.
645 Add a file to this directory or to its direct parent directory.
646
646
647 If the file is not direct child of this directory, we traverse to the
647 If the file is not direct child of this directory, we traverse to the
648 directory of which this file is a direct child of and add the file
648 directory of which this file is a direct child of and add the file
649 there.
649 there.
650 """
650 """
651
651
652 # the filename contains a path separator, it means it's not the direct
652 # the filename contains a path separator, it means it's not the direct
653 # child of this directory
653 # child of this directory
654 if b'/' in filename:
654 if b'/' in filename:
655 subdir, filep = filename.split(b'/', 1)
655 subdir, filep = filename.split(b'/', 1)
656
656
657 # does the dirnode object for subdir exists
657 # does the dirnode object for subdir exists
658 if subdir not in self.subdirs:
658 if subdir not in self.subdirs:
659 subdirpath = pathutil.join(self.path, subdir)
659 subdirpath = pathutil.join(self.path, subdir)
660 self.subdirs[subdir] = dirnode(subdirpath)
660 self.subdirs[subdir] = dirnode(subdirpath)
661
661
662 # try adding the file in subdir
662 # try adding the file in subdir
663 self.subdirs[subdir].addfile(filep, status)
663 self.subdirs[subdir].addfile(filep, status)
664
664
665 else:
665 else:
666 self._addfileindir(filename, status)
666 self._addfileindir(filename, status)
667
667
668 if status not in self.statuses:
668 if status not in self.statuses:
669 self.statuses.add(status)
669 self.statuses.add(status)
670
670
671 def iterfilepaths(self):
671 def iterfilepaths(self):
672 """Yield (status, path) for files directly under this directory."""
672 """Yield (status, path) for files directly under this directory."""
673 for f, st in self.files:
673 for f, st in self.files:
674 yield st, pathutil.join(self.path, f)
674 yield st, pathutil.join(self.path, f)
675
675
676 def tersewalk(self, terseargs):
676 def tersewalk(self, terseargs):
677 """
677 """
678 Yield (status, path) obtained by processing the status of this
678 Yield (status, path) obtained by processing the status of this
679 dirnode.
679 dirnode.
680
680
681 terseargs is the string of arguments passed by the user with `--terse`
681 terseargs is the string of arguments passed by the user with `--terse`
682 flag.
682 flag.
683
683
684 Following are the cases which can happen:
684 Following are the cases which can happen:
685
685
686 1) All the files in the directory (including all the files in its
686 1) All the files in the directory (including all the files in its
687 subdirectories) share the same status and the user has asked us to terse
687 subdirectories) share the same status and the user has asked us to terse
688 that status. -> yield (status, dirpath). dirpath will end in '/'.
688 that status. -> yield (status, dirpath). dirpath will end in '/'.
689
689
690 2) Otherwise, we do following:
690 2) Otherwise, we do following:
691
691
692 a) Yield (status, filepath) for all the files which are in this
692 a) Yield (status, filepath) for all the files which are in this
693 directory (only the ones in this directory, not the subdirs)
693 directory (only the ones in this directory, not the subdirs)
694
694
695 b) Recurse the function on all the subdirectories of this
695 b) Recurse the function on all the subdirectories of this
696 directory
696 directory
697 """
697 """
698
698
699 if len(self.statuses) == 1:
699 if len(self.statuses) == 1:
700 onlyst = self.statuses.pop()
700 onlyst = self.statuses.pop()
701
701
702 # Making sure we terse only when the status abbreviation is
702 # Making sure we terse only when the status abbreviation is
703 # passed as terse argument
703 # passed as terse argument
704 if onlyst in terseargs:
704 if onlyst in terseargs:
705 yield onlyst, self.path + b'/'
705 yield onlyst, self.path + b'/'
706 return
706 return
707
707
708 # add the files to status list
708 # add the files to status list
709 for st, fpath in self.iterfilepaths():
709 for st, fpath in self.iterfilepaths():
710 yield st, fpath
710 yield st, fpath
711
711
712 # recurse on the subdirs
712 # recurse on the subdirs
713 for dirobj in self.subdirs.values():
713 for dirobj in self.subdirs.values():
714 for st, fpath in dirobj.tersewalk(terseargs):
714 for st, fpath in dirobj.tersewalk(terseargs):
715 yield st, fpath
715 yield st, fpath
716
716
717
717
718 def tersedir(statuslist, terseargs):
718 def tersedir(statuslist, terseargs):
719 """
719 """
720 Terse the status if all the files in a directory shares the same status.
720 Terse the status if all the files in a directory shares the same status.
721
721
722 statuslist is scmutil.status() object which contains a list of files for
722 statuslist is scmutil.status() object which contains a list of files for
723 each status.
723 each status.
724 terseargs is string which is passed by the user as the argument to `--terse`
724 terseargs is string which is passed by the user as the argument to `--terse`
725 flag.
725 flag.
726
726
727 The function makes a tree of objects of dirnode class, and at each node it
727 The function makes a tree of objects of dirnode class, and at each node it
728 stores the information required to know whether we can terse a certain
728 stores the information required to know whether we can terse a certain
729 directory or not.
729 directory or not.
730 """
730 """
731 # the order matters here as that is used to produce final list
731 # the order matters here as that is used to produce final list
732 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
732 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
733
733
734 # checking the argument validity
734 # checking the argument validity
735 for s in pycompat.bytestr(terseargs):
735 for s in pycompat.bytestr(terseargs):
736 if s not in allst:
736 if s not in allst:
737 raise error.Abort(_(b"'%s' not recognized") % s)
737 raise error.Abort(_(b"'%s' not recognized") % s)
738
738
739 # creating a dirnode object for the root of the repo
739 # creating a dirnode object for the root of the repo
740 rootobj = dirnode(b'')
740 rootobj = dirnode(b'')
741 pstatus = (
741 pstatus = (
742 b'modified',
742 b'modified',
743 b'added',
743 b'added',
744 b'deleted',
744 b'deleted',
745 b'clean',
745 b'clean',
746 b'unknown',
746 b'unknown',
747 b'ignored',
747 b'ignored',
748 b'removed',
748 b'removed',
749 )
749 )
750
750
751 tersedict = {}
751 tersedict = {}
752 for attrname in pstatus:
752 for attrname in pstatus:
753 statuschar = attrname[0:1]
753 statuschar = attrname[0:1]
754 for f in getattr(statuslist, attrname):
754 for f in getattr(statuslist, attrname):
755 rootobj.addfile(f, statuschar)
755 rootobj.addfile(f, statuschar)
756 tersedict[statuschar] = []
756 tersedict[statuschar] = []
757
757
758 # we won't be tersing the root dir, so add files in it
758 # we won't be tersing the root dir, so add files in it
759 for st, fpath in rootobj.iterfilepaths():
759 for st, fpath in rootobj.iterfilepaths():
760 tersedict[st].append(fpath)
760 tersedict[st].append(fpath)
761
761
762 # process each sub-directory and build tersedict
762 # process each sub-directory and build tersedict
763 for subdir in rootobj.subdirs.values():
763 for subdir in rootobj.subdirs.values():
764 for st, f in subdir.tersewalk(terseargs):
764 for st, f in subdir.tersewalk(terseargs):
765 tersedict[st].append(f)
765 tersedict[st].append(f)
766
766
767 tersedlist = []
767 tersedlist = []
768 for st in allst:
768 for st in allst:
769 tersedict[st].sort()
769 tersedict[st].sort()
770 tersedlist.append(tersedict[st])
770 tersedlist.append(tersedict[st])
771
771
772 return scmutil.status(*tersedlist)
772 return scmutil.status(*tersedlist)
773
773
774
774
775 def _commentlines(raw):
775 def _commentlines(raw):
776 '''Surround lineswith a comment char and a new line'''
776 '''Surround lineswith a comment char and a new line'''
777 lines = raw.splitlines()
777 lines = raw.splitlines()
778 commentedlines = [b'# %s' % line for line in lines]
778 commentedlines = [b'# %s' % line for line in lines]
779 return b'\n'.join(commentedlines) + b'\n'
779 return b'\n'.join(commentedlines) + b'\n'
780
780
781
781
782 @attr.s(frozen=True)
782 @attr.s(frozen=True)
783 class morestatus(object):
783 class morestatus(object):
784 reporoot = attr.ib()
784 reporoot = attr.ib()
785 unfinishedop = attr.ib()
785 unfinishedop = attr.ib()
786 unfinishedmsg = attr.ib()
786 unfinishedmsg = attr.ib()
787 inmergestate = attr.ib()
787 inmergestate = attr.ib()
788 unresolvedpaths = attr.ib()
788 unresolvedpaths = attr.ib()
789 _label = b'status.morestatus'
789 _label = b'status.morestatus'
790
790
791 def formatfile(self, path, fm):
791 def formatfile(self, path, fm):
792 if self.inmergestate and path in self.unresolvedpaths:
792 if self.inmergestate and path in self.unresolvedpaths:
793 fm.data(unresolved=True)
793 fm.data(unresolved=True)
794
794
795 def formatfooter(self, fm):
795 def formatfooter(self, fm):
796 fm.startitem()
797 fm.data(
798 itemtype=b'morestatus',
799 unfinished=self.unfinishedop,
800 unfinishedmsg=self.unfinishedmsg,
801 )
802
796 statemsg = (
803 statemsg = (
797 _(b'The repository is in an unfinished *%s* state.')
804 _(b'The repository is in an unfinished *%s* state.')
798 % self.unfinishedop
805 % self.unfinishedop
799 )
806 )
800 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
807 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
801
808
802 self._formatconflicts(fm)
809 self._formatconflicts(fm)
803 if self.unfinishedmsg:
810 if self.unfinishedmsg:
804 fm.plain(
811 fm.plain(
805 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
812 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
806 )
813 )
807
814
808 def _formatconflicts(self, fm):
815 def _formatconflicts(self, fm):
809 if not self.inmergestate:
816 if not self.inmergestate:
810 return
817 return
811
818
812 if self.unresolvedpaths:
819 if self.unresolvedpaths:
813 mergeliststr = b'\n'.join(
820 mergeliststr = b'\n'.join(
814 [
821 [
815 b' %s'
822 b' %s'
816 % util.pathto(self.reporoot, encoding.getcwd(), path)
823 % util.pathto(self.reporoot, encoding.getcwd(), path)
817 for path in self.unresolvedpaths
824 for path in self.unresolvedpaths
818 ]
825 ]
819 )
826 )
820 msg = (
827 msg = (
821 _(
828 _(
822 '''Unresolved merge conflicts:
829 '''Unresolved merge conflicts:
823
830
824 %s
831 %s
825
832
826 To mark files as resolved: hg resolve --mark FILE'''
833 To mark files as resolved: hg resolve --mark FILE'''
827 )
834 )
828 % mergeliststr
835 % mergeliststr
829 )
836 )
830 else:
837 else:
831 msg = _(b'No unresolved merge conflicts.')
838 msg = _(b'No unresolved merge conflicts.')
832
839
833 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
840 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
834
841
835
842
836 def readmorestatus(repo):
843 def readmorestatus(repo):
837 """Returns a morestatus object if the repo has unfinished state."""
844 """Returns a morestatus object if the repo has unfinished state."""
838 statetuple = statemod.getrepostate(repo)
845 statetuple = statemod.getrepostate(repo)
839 if not statetuple:
846 if not statetuple:
840 return None
847 return None
841
848
842 unfinishedop, unfinishedmsg = statetuple
849 unfinishedop, unfinishedmsg = statetuple
843 mergestate = mergemod.mergestate.read(repo)
850 mergestate = mergemod.mergestate.read(repo)
844 unresolved = None
851 unresolved = None
845 if mergestate.active():
852 if mergestate.active():
846 unresolved = sorted(mergestate.unresolved())
853 unresolved = sorted(mergestate.unresolved())
847 return morestatus(
854 return morestatus(
848 repo.root,
855 repo.root,
849 unfinishedop,
856 unfinishedop,
850 unfinishedmsg,
857 unfinishedmsg,
851 unresolved is not None,
858 unresolved is not None,
852 unresolved,
859 unresolved,
853 )
860 )
854
861
855
862
856 def findpossible(cmd, table, strict=False):
863 def findpossible(cmd, table, strict=False):
857 """
864 """
858 Return cmd -> (aliases, command table entry)
865 Return cmd -> (aliases, command table entry)
859 for each matching command.
866 for each matching command.
860 Return debug commands (or their aliases) only if no normal command matches.
867 Return debug commands (or their aliases) only if no normal command matches.
861 """
868 """
862 choice = {}
869 choice = {}
863 debugchoice = {}
870 debugchoice = {}
864
871
865 if cmd in table:
872 if cmd in table:
866 # short-circuit exact matches, "log" alias beats "log|history"
873 # short-circuit exact matches, "log" alias beats "log|history"
867 keys = [cmd]
874 keys = [cmd]
868 else:
875 else:
869 keys = table.keys()
876 keys = table.keys()
870
877
871 allcmds = []
878 allcmds = []
872 for e in keys:
879 for e in keys:
873 aliases = parsealiases(e)
880 aliases = parsealiases(e)
874 allcmds.extend(aliases)
881 allcmds.extend(aliases)
875 found = None
882 found = None
876 if cmd in aliases:
883 if cmd in aliases:
877 found = cmd
884 found = cmd
878 elif not strict:
885 elif not strict:
879 for a in aliases:
886 for a in aliases:
880 if a.startswith(cmd):
887 if a.startswith(cmd):
881 found = a
888 found = a
882 break
889 break
883 if found is not None:
890 if found is not None:
884 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
891 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
885 debugchoice[found] = (aliases, table[e])
892 debugchoice[found] = (aliases, table[e])
886 else:
893 else:
887 choice[found] = (aliases, table[e])
894 choice[found] = (aliases, table[e])
888
895
889 if not choice and debugchoice:
896 if not choice and debugchoice:
890 choice = debugchoice
897 choice = debugchoice
891
898
892 return choice, allcmds
899 return choice, allcmds
893
900
894
901
895 def findcmd(cmd, table, strict=True):
902 def findcmd(cmd, table, strict=True):
896 """Return (aliases, command table entry) for command string."""
903 """Return (aliases, command table entry) for command string."""
897 choice, allcmds = findpossible(cmd, table, strict)
904 choice, allcmds = findpossible(cmd, table, strict)
898
905
899 if cmd in choice:
906 if cmd in choice:
900 return choice[cmd]
907 return choice[cmd]
901
908
902 if len(choice) > 1:
909 if len(choice) > 1:
903 clist = sorted(choice)
910 clist = sorted(choice)
904 raise error.AmbiguousCommand(cmd, clist)
911 raise error.AmbiguousCommand(cmd, clist)
905
912
906 if choice:
913 if choice:
907 return list(choice.values())[0]
914 return list(choice.values())[0]
908
915
909 raise error.UnknownCommand(cmd, allcmds)
916 raise error.UnknownCommand(cmd, allcmds)
910
917
911
918
912 def changebranch(ui, repo, revs, label):
919 def changebranch(ui, repo, revs, label):
913 """ Change the branch name of given revs to label """
920 """ Change the branch name of given revs to label """
914
921
915 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
922 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
916 # abort in case of uncommitted merge or dirty wdir
923 # abort in case of uncommitted merge or dirty wdir
917 bailifchanged(repo)
924 bailifchanged(repo)
918 revs = scmutil.revrange(repo, revs)
925 revs = scmutil.revrange(repo, revs)
919 if not revs:
926 if not revs:
920 raise error.Abort(b"empty revision set")
927 raise error.Abort(b"empty revision set")
921 roots = repo.revs(b'roots(%ld)', revs)
928 roots = repo.revs(b'roots(%ld)', revs)
922 if len(roots) > 1:
929 if len(roots) > 1:
923 raise error.Abort(
930 raise error.Abort(
924 _(b"cannot change branch of non-linear revisions")
931 _(b"cannot change branch of non-linear revisions")
925 )
932 )
926 rewriteutil.precheck(repo, revs, b'change branch of')
933 rewriteutil.precheck(repo, revs, b'change branch of')
927
934
928 root = repo[roots.first()]
935 root = repo[roots.first()]
929 rpb = {parent.branch() for parent in root.parents()}
936 rpb = {parent.branch() for parent in root.parents()}
930 if label not in rpb and label in repo.branchmap():
937 if label not in rpb and label in repo.branchmap():
931 raise error.Abort(_(b"a branch of the same name already exists"))
938 raise error.Abort(_(b"a branch of the same name already exists"))
932
939
933 if repo.revs(b'obsolete() and %ld', revs):
940 if repo.revs(b'obsolete() and %ld', revs):
934 raise error.Abort(
941 raise error.Abort(
935 _(b"cannot change branch of a obsolete changeset")
942 _(b"cannot change branch of a obsolete changeset")
936 )
943 )
937
944
938 # make sure only topological heads
945 # make sure only topological heads
939 if repo.revs(b'heads(%ld) - head()', revs):
946 if repo.revs(b'heads(%ld) - head()', revs):
940 raise error.Abort(_(b"cannot change branch in middle of a stack"))
947 raise error.Abort(_(b"cannot change branch in middle of a stack"))
941
948
942 replacements = {}
949 replacements = {}
943 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
950 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
944 # mercurial.subrepo -> mercurial.cmdutil
951 # mercurial.subrepo -> mercurial.cmdutil
945 from . import context
952 from . import context
946
953
947 for rev in revs:
954 for rev in revs:
948 ctx = repo[rev]
955 ctx = repo[rev]
949 oldbranch = ctx.branch()
956 oldbranch = ctx.branch()
950 # check if ctx has same branch
957 # check if ctx has same branch
951 if oldbranch == label:
958 if oldbranch == label:
952 continue
959 continue
953
960
954 def filectxfn(repo, newctx, path):
961 def filectxfn(repo, newctx, path):
955 try:
962 try:
956 return ctx[path]
963 return ctx[path]
957 except error.ManifestLookupError:
964 except error.ManifestLookupError:
958 return None
965 return None
959
966
960 ui.debug(
967 ui.debug(
961 b"changing branch of '%s' from '%s' to '%s'\n"
968 b"changing branch of '%s' from '%s' to '%s'\n"
962 % (hex(ctx.node()), oldbranch, label)
969 % (hex(ctx.node()), oldbranch, label)
963 )
970 )
964 extra = ctx.extra()
971 extra = ctx.extra()
965 extra[b'branch_change'] = hex(ctx.node())
972 extra[b'branch_change'] = hex(ctx.node())
966 # While changing branch of set of linear commits, make sure that
973 # While changing branch of set of linear commits, make sure that
967 # we base our commits on new parent rather than old parent which
974 # we base our commits on new parent rather than old parent which
968 # was obsoleted while changing the branch
975 # was obsoleted while changing the branch
969 p1 = ctx.p1().node()
976 p1 = ctx.p1().node()
970 p2 = ctx.p2().node()
977 p2 = ctx.p2().node()
971 if p1 in replacements:
978 if p1 in replacements:
972 p1 = replacements[p1][0]
979 p1 = replacements[p1][0]
973 if p2 in replacements:
980 if p2 in replacements:
974 p2 = replacements[p2][0]
981 p2 = replacements[p2][0]
975
982
976 mc = context.memctx(
983 mc = context.memctx(
977 repo,
984 repo,
978 (p1, p2),
985 (p1, p2),
979 ctx.description(),
986 ctx.description(),
980 ctx.files(),
987 ctx.files(),
981 filectxfn,
988 filectxfn,
982 user=ctx.user(),
989 user=ctx.user(),
983 date=ctx.date(),
990 date=ctx.date(),
984 extra=extra,
991 extra=extra,
985 branch=label,
992 branch=label,
986 )
993 )
987
994
988 newnode = repo.commitctx(mc)
995 newnode = repo.commitctx(mc)
989 replacements[ctx.node()] = (newnode,)
996 replacements[ctx.node()] = (newnode,)
990 ui.debug(b'new node id is %s\n' % hex(newnode))
997 ui.debug(b'new node id is %s\n' % hex(newnode))
991
998
992 # create obsmarkers and move bookmarks
999 # create obsmarkers and move bookmarks
993 scmutil.cleanupnodes(
1000 scmutil.cleanupnodes(
994 repo, replacements, b'branch-change', fixphase=True
1001 repo, replacements, b'branch-change', fixphase=True
995 )
1002 )
996
1003
997 # move the working copy too
1004 # move the working copy too
998 wctx = repo[None]
1005 wctx = repo[None]
999 # in-progress merge is a bit too complex for now.
1006 # in-progress merge is a bit too complex for now.
1000 if len(wctx.parents()) == 1:
1007 if len(wctx.parents()) == 1:
1001 newid = replacements.get(wctx.p1().node())
1008 newid = replacements.get(wctx.p1().node())
1002 if newid is not None:
1009 if newid is not None:
1003 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1010 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1004 # mercurial.cmdutil
1011 # mercurial.cmdutil
1005 from . import hg
1012 from . import hg
1006
1013
1007 hg.update(repo, newid[0], quietempty=True)
1014 hg.update(repo, newid[0], quietempty=True)
1008
1015
1009 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1016 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1010
1017
1011
1018
1012 def findrepo(p):
1019 def findrepo(p):
1013 while not os.path.isdir(os.path.join(p, b".hg")):
1020 while not os.path.isdir(os.path.join(p, b".hg")):
1014 oldp, p = p, os.path.dirname(p)
1021 oldp, p = p, os.path.dirname(p)
1015 if p == oldp:
1022 if p == oldp:
1016 return None
1023 return None
1017
1024
1018 return p
1025 return p
1019
1026
1020
1027
1021 def bailifchanged(repo, merge=True, hint=None):
1028 def bailifchanged(repo, merge=True, hint=None):
1022 """ enforce the precondition that working directory must be clean.
1029 """ enforce the precondition that working directory must be clean.
1023
1030
1024 'merge' can be set to false if a pending uncommitted merge should be
1031 'merge' can be set to false if a pending uncommitted merge should be
1025 ignored (such as when 'update --check' runs).
1032 ignored (such as when 'update --check' runs).
1026
1033
1027 'hint' is the usual hint given to Abort exception.
1034 'hint' is the usual hint given to Abort exception.
1028 """
1035 """
1029
1036
1030 if merge and repo.dirstate.p2() != nullid:
1037 if merge and repo.dirstate.p2() != nullid:
1031 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1038 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1032 st = repo.status()
1039 st = repo.status()
1033 if st.modified or st.added or st.removed or st.deleted:
1040 if st.modified or st.added or st.removed or st.deleted:
1034 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1041 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1035 ctx = repo[None]
1042 ctx = repo[None]
1036 for s in sorted(ctx.substate):
1043 for s in sorted(ctx.substate):
1037 ctx.sub(s).bailifchanged(hint=hint)
1044 ctx.sub(s).bailifchanged(hint=hint)
1038
1045
1039
1046
1040 def logmessage(ui, opts):
1047 def logmessage(ui, opts):
1041 """ get the log message according to -m and -l option """
1048 """ get the log message according to -m and -l option """
1042 message = opts.get(b'message')
1049 message = opts.get(b'message')
1043 logfile = opts.get(b'logfile')
1050 logfile = opts.get(b'logfile')
1044
1051
1045 if message and logfile:
1052 if message and logfile:
1046 raise error.Abort(
1053 raise error.Abort(
1047 _(b'options --message and --logfile are mutually exclusive')
1054 _(b'options --message and --logfile are mutually exclusive')
1048 )
1055 )
1049 if not message and logfile:
1056 if not message and logfile:
1050 try:
1057 try:
1051 if isstdiofilename(logfile):
1058 if isstdiofilename(logfile):
1052 message = ui.fin.read()
1059 message = ui.fin.read()
1053 else:
1060 else:
1054 message = b'\n'.join(util.readfile(logfile).splitlines())
1061 message = b'\n'.join(util.readfile(logfile).splitlines())
1055 except IOError as inst:
1062 except IOError as inst:
1056 raise error.Abort(
1063 raise error.Abort(
1057 _(b"can't read commit message '%s': %s")
1064 _(b"can't read commit message '%s': %s")
1058 % (logfile, encoding.strtolocal(inst.strerror))
1065 % (logfile, encoding.strtolocal(inst.strerror))
1059 )
1066 )
1060 return message
1067 return message
1061
1068
1062
1069
1063 def mergeeditform(ctxorbool, baseformname):
1070 def mergeeditform(ctxorbool, baseformname):
1064 """return appropriate editform name (referencing a committemplate)
1071 """return appropriate editform name (referencing a committemplate)
1065
1072
1066 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1073 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1067 merging is committed.
1074 merging is committed.
1068
1075
1069 This returns baseformname with '.merge' appended if it is a merge,
1076 This returns baseformname with '.merge' appended if it is a merge,
1070 otherwise '.normal' is appended.
1077 otherwise '.normal' is appended.
1071 """
1078 """
1072 if isinstance(ctxorbool, bool):
1079 if isinstance(ctxorbool, bool):
1073 if ctxorbool:
1080 if ctxorbool:
1074 return baseformname + b".merge"
1081 return baseformname + b".merge"
1075 elif len(ctxorbool.parents()) > 1:
1082 elif len(ctxorbool.parents()) > 1:
1076 return baseformname + b".merge"
1083 return baseformname + b".merge"
1077
1084
1078 return baseformname + b".normal"
1085 return baseformname + b".normal"
1079
1086
1080
1087
1081 def getcommiteditor(
1088 def getcommiteditor(
1082 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1089 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1083 ):
1090 ):
1084 """get appropriate commit message editor according to '--edit' option
1091 """get appropriate commit message editor according to '--edit' option
1085
1092
1086 'finishdesc' is a function to be called with edited commit message
1093 'finishdesc' is a function to be called with edited commit message
1087 (= 'description' of the new changeset) just after editing, but
1094 (= 'description' of the new changeset) just after editing, but
1088 before checking empty-ness. It should return actual text to be
1095 before checking empty-ness. It should return actual text to be
1089 stored into history. This allows to change description before
1096 stored into history. This allows to change description before
1090 storing.
1097 storing.
1091
1098
1092 'extramsg' is a extra message to be shown in the editor instead of
1099 'extramsg' is a extra message to be shown in the editor instead of
1093 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1100 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1094 is automatically added.
1101 is automatically added.
1095
1102
1096 'editform' is a dot-separated list of names, to distinguish
1103 'editform' is a dot-separated list of names, to distinguish
1097 the purpose of commit text editing.
1104 the purpose of commit text editing.
1098
1105
1099 'getcommiteditor' returns 'commitforceeditor' regardless of
1106 'getcommiteditor' returns 'commitforceeditor' regardless of
1100 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1107 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1101 they are specific for usage in MQ.
1108 they are specific for usage in MQ.
1102 """
1109 """
1103 if edit or finishdesc or extramsg:
1110 if edit or finishdesc or extramsg:
1104 return lambda r, c, s: commitforceeditor(
1111 return lambda r, c, s: commitforceeditor(
1105 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1112 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1106 )
1113 )
1107 elif editform:
1114 elif editform:
1108 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1115 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1109 else:
1116 else:
1110 return commiteditor
1117 return commiteditor
1111
1118
1112
1119
1113 def _escapecommandtemplate(tmpl):
1120 def _escapecommandtemplate(tmpl):
1114 parts = []
1121 parts = []
1115 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1122 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1116 if typ == b'string':
1123 if typ == b'string':
1117 parts.append(stringutil.escapestr(tmpl[start:end]))
1124 parts.append(stringutil.escapestr(tmpl[start:end]))
1118 else:
1125 else:
1119 parts.append(tmpl[start:end])
1126 parts.append(tmpl[start:end])
1120 return b''.join(parts)
1127 return b''.join(parts)
1121
1128
1122
1129
1123 def rendercommandtemplate(ui, tmpl, props):
1130 def rendercommandtemplate(ui, tmpl, props):
1124 r"""Expand a literal template 'tmpl' in a way suitable for command line
1131 r"""Expand a literal template 'tmpl' in a way suitable for command line
1125
1132
1126 '\' in outermost string is not taken as an escape character because it
1133 '\' in outermost string is not taken as an escape character because it
1127 is a directory separator on Windows.
1134 is a directory separator on Windows.
1128
1135
1129 >>> from . import ui as uimod
1136 >>> from . import ui as uimod
1130 >>> ui = uimod.ui()
1137 >>> ui = uimod.ui()
1131 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1138 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1132 'c:\\foo'
1139 'c:\\foo'
1133 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1140 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1134 'c:{path}'
1141 'c:{path}'
1135 """
1142 """
1136 if not tmpl:
1143 if not tmpl:
1137 return tmpl
1144 return tmpl
1138 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1145 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1139 return t.renderdefault(props)
1146 return t.renderdefault(props)
1140
1147
1141
1148
1142 def rendertemplate(ctx, tmpl, props=None):
1149 def rendertemplate(ctx, tmpl, props=None):
1143 """Expand a literal template 'tmpl' byte-string against one changeset
1150 """Expand a literal template 'tmpl' byte-string against one changeset
1144
1151
1145 Each props item must be a stringify-able value or a callable returning
1152 Each props item must be a stringify-able value or a callable returning
1146 such value, i.e. no bare list nor dict should be passed.
1153 such value, i.e. no bare list nor dict should be passed.
1147 """
1154 """
1148 repo = ctx.repo()
1155 repo = ctx.repo()
1149 tres = formatter.templateresources(repo.ui, repo)
1156 tres = formatter.templateresources(repo.ui, repo)
1150 t = formatter.maketemplater(
1157 t = formatter.maketemplater(
1151 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1158 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1152 )
1159 )
1153 mapping = {b'ctx': ctx}
1160 mapping = {b'ctx': ctx}
1154 if props:
1161 if props:
1155 mapping.update(props)
1162 mapping.update(props)
1156 return t.renderdefault(mapping)
1163 return t.renderdefault(mapping)
1157
1164
1158
1165
1159 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1166 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1160 r"""Convert old-style filename format string to template string
1167 r"""Convert old-style filename format string to template string
1161
1168
1162 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1169 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1163 'foo-{reporoot|basename}-{seqno}.patch'
1170 'foo-{reporoot|basename}-{seqno}.patch'
1164 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1171 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1165 '{rev}{tags % "{tag}"}{node}'
1172 '{rev}{tags % "{tag}"}{node}'
1166
1173
1167 '\' in outermost strings has to be escaped because it is a directory
1174 '\' in outermost strings has to be escaped because it is a directory
1168 separator on Windows:
1175 separator on Windows:
1169
1176
1170 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1177 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1171 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1178 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1172 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1179 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1173 '\\\\\\\\foo\\\\bar.patch'
1180 '\\\\\\\\foo\\\\bar.patch'
1174 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1181 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1175 '\\\\{tags % "{tag}"}'
1182 '\\\\{tags % "{tag}"}'
1176
1183
1177 but inner strings follow the template rules (i.e. '\' is taken as an
1184 but inner strings follow the template rules (i.e. '\' is taken as an
1178 escape character):
1185 escape character):
1179
1186
1180 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1187 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1181 '{"c:\\tmp"}'
1188 '{"c:\\tmp"}'
1182 """
1189 """
1183 expander = {
1190 expander = {
1184 b'H': b'{node}',
1191 b'H': b'{node}',
1185 b'R': b'{rev}',
1192 b'R': b'{rev}',
1186 b'h': b'{node|short}',
1193 b'h': b'{node|short}',
1187 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1194 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1188 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1195 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1189 b'%': b'%',
1196 b'%': b'%',
1190 b'b': b'{reporoot|basename}',
1197 b'b': b'{reporoot|basename}',
1191 }
1198 }
1192 if total is not None:
1199 if total is not None:
1193 expander[b'N'] = b'{total}'
1200 expander[b'N'] = b'{total}'
1194 if seqno is not None:
1201 if seqno is not None:
1195 expander[b'n'] = b'{seqno}'
1202 expander[b'n'] = b'{seqno}'
1196 if total is not None and seqno is not None:
1203 if total is not None and seqno is not None:
1197 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1204 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1198 if pathname is not None:
1205 if pathname is not None:
1199 expander[b's'] = b'{pathname|basename}'
1206 expander[b's'] = b'{pathname|basename}'
1200 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1207 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1201 expander[b'p'] = b'{pathname}'
1208 expander[b'p'] = b'{pathname}'
1202
1209
1203 newname = []
1210 newname = []
1204 for typ, start, end in templater.scantemplate(pat, raw=True):
1211 for typ, start, end in templater.scantemplate(pat, raw=True):
1205 if typ != b'string':
1212 if typ != b'string':
1206 newname.append(pat[start:end])
1213 newname.append(pat[start:end])
1207 continue
1214 continue
1208 i = start
1215 i = start
1209 while i < end:
1216 while i < end:
1210 n = pat.find(b'%', i, end)
1217 n = pat.find(b'%', i, end)
1211 if n < 0:
1218 if n < 0:
1212 newname.append(stringutil.escapestr(pat[i:end]))
1219 newname.append(stringutil.escapestr(pat[i:end]))
1213 break
1220 break
1214 newname.append(stringutil.escapestr(pat[i:n]))
1221 newname.append(stringutil.escapestr(pat[i:n]))
1215 if n + 2 > end:
1222 if n + 2 > end:
1216 raise error.Abort(
1223 raise error.Abort(
1217 _(b"incomplete format spec in output filename")
1224 _(b"incomplete format spec in output filename")
1218 )
1225 )
1219 c = pat[n + 1 : n + 2]
1226 c = pat[n + 1 : n + 2]
1220 i = n + 2
1227 i = n + 2
1221 try:
1228 try:
1222 newname.append(expander[c])
1229 newname.append(expander[c])
1223 except KeyError:
1230 except KeyError:
1224 raise error.Abort(
1231 raise error.Abort(
1225 _(b"invalid format spec '%%%s' in output filename") % c
1232 _(b"invalid format spec '%%%s' in output filename") % c
1226 )
1233 )
1227 return b''.join(newname)
1234 return b''.join(newname)
1228
1235
1229
1236
1230 def makefilename(ctx, pat, **props):
1237 def makefilename(ctx, pat, **props):
1231 if not pat:
1238 if not pat:
1232 return pat
1239 return pat
1233 tmpl = _buildfntemplate(pat, **props)
1240 tmpl = _buildfntemplate(pat, **props)
1234 # BUG: alias expansion shouldn't be made against template fragments
1241 # BUG: alias expansion shouldn't be made against template fragments
1235 # rewritten from %-format strings, but we have no easy way to partially
1242 # rewritten from %-format strings, but we have no easy way to partially
1236 # disable the expansion.
1243 # disable the expansion.
1237 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1244 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1238
1245
1239
1246
1240 def isstdiofilename(pat):
1247 def isstdiofilename(pat):
1241 """True if the given pat looks like a filename denoting stdin/stdout"""
1248 """True if the given pat looks like a filename denoting stdin/stdout"""
1242 return not pat or pat == b'-'
1249 return not pat or pat == b'-'
1243
1250
1244
1251
1245 class _unclosablefile(object):
1252 class _unclosablefile(object):
1246 def __init__(self, fp):
1253 def __init__(self, fp):
1247 self._fp = fp
1254 self._fp = fp
1248
1255
1249 def close(self):
1256 def close(self):
1250 pass
1257 pass
1251
1258
1252 def __iter__(self):
1259 def __iter__(self):
1253 return iter(self._fp)
1260 return iter(self._fp)
1254
1261
1255 def __getattr__(self, attr):
1262 def __getattr__(self, attr):
1256 return getattr(self._fp, attr)
1263 return getattr(self._fp, attr)
1257
1264
1258 def __enter__(self):
1265 def __enter__(self):
1259 return self
1266 return self
1260
1267
1261 def __exit__(self, exc_type, exc_value, exc_tb):
1268 def __exit__(self, exc_type, exc_value, exc_tb):
1262 pass
1269 pass
1263
1270
1264
1271
1265 def makefileobj(ctx, pat, mode=b'wb', **props):
1272 def makefileobj(ctx, pat, mode=b'wb', **props):
1266 writable = mode not in (b'r', b'rb')
1273 writable = mode not in (b'r', b'rb')
1267
1274
1268 if isstdiofilename(pat):
1275 if isstdiofilename(pat):
1269 repo = ctx.repo()
1276 repo = ctx.repo()
1270 if writable:
1277 if writable:
1271 fp = repo.ui.fout
1278 fp = repo.ui.fout
1272 else:
1279 else:
1273 fp = repo.ui.fin
1280 fp = repo.ui.fin
1274 return _unclosablefile(fp)
1281 return _unclosablefile(fp)
1275 fn = makefilename(ctx, pat, **props)
1282 fn = makefilename(ctx, pat, **props)
1276 return open(fn, mode)
1283 return open(fn, mode)
1277
1284
1278
1285
1279 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1286 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1280 """opens the changelog, manifest, a filelog or a given revlog"""
1287 """opens the changelog, manifest, a filelog or a given revlog"""
1281 cl = opts[b'changelog']
1288 cl = opts[b'changelog']
1282 mf = opts[b'manifest']
1289 mf = opts[b'manifest']
1283 dir = opts[b'dir']
1290 dir = opts[b'dir']
1284 msg = None
1291 msg = None
1285 if cl and mf:
1292 if cl and mf:
1286 msg = _(b'cannot specify --changelog and --manifest at the same time')
1293 msg = _(b'cannot specify --changelog and --manifest at the same time')
1287 elif cl and dir:
1294 elif cl and dir:
1288 msg = _(b'cannot specify --changelog and --dir at the same time')
1295 msg = _(b'cannot specify --changelog and --dir at the same time')
1289 elif cl or mf or dir:
1296 elif cl or mf or dir:
1290 if file_:
1297 if file_:
1291 msg = _(b'cannot specify filename with --changelog or --manifest')
1298 msg = _(b'cannot specify filename with --changelog or --manifest')
1292 elif not repo:
1299 elif not repo:
1293 msg = _(
1300 msg = _(
1294 b'cannot specify --changelog or --manifest or --dir '
1301 b'cannot specify --changelog or --manifest or --dir '
1295 b'without a repository'
1302 b'without a repository'
1296 )
1303 )
1297 if msg:
1304 if msg:
1298 raise error.Abort(msg)
1305 raise error.Abort(msg)
1299
1306
1300 r = None
1307 r = None
1301 if repo:
1308 if repo:
1302 if cl:
1309 if cl:
1303 r = repo.unfiltered().changelog
1310 r = repo.unfiltered().changelog
1304 elif dir:
1311 elif dir:
1305 if b'treemanifest' not in repo.requirements:
1312 if b'treemanifest' not in repo.requirements:
1306 raise error.Abort(
1313 raise error.Abort(
1307 _(
1314 _(
1308 b"--dir can only be used on repos with "
1315 b"--dir can only be used on repos with "
1309 b"treemanifest enabled"
1316 b"treemanifest enabled"
1310 )
1317 )
1311 )
1318 )
1312 if not dir.endswith(b'/'):
1319 if not dir.endswith(b'/'):
1313 dir = dir + b'/'
1320 dir = dir + b'/'
1314 dirlog = repo.manifestlog.getstorage(dir)
1321 dirlog = repo.manifestlog.getstorage(dir)
1315 if len(dirlog):
1322 if len(dirlog):
1316 r = dirlog
1323 r = dirlog
1317 elif mf:
1324 elif mf:
1318 r = repo.manifestlog.getstorage(b'')
1325 r = repo.manifestlog.getstorage(b'')
1319 elif file_:
1326 elif file_:
1320 filelog = repo.file(file_)
1327 filelog = repo.file(file_)
1321 if len(filelog):
1328 if len(filelog):
1322 r = filelog
1329 r = filelog
1323
1330
1324 # Not all storage may be revlogs. If requested, try to return an actual
1331 # Not all storage may be revlogs. If requested, try to return an actual
1325 # revlog instance.
1332 # revlog instance.
1326 if returnrevlog:
1333 if returnrevlog:
1327 if isinstance(r, revlog.revlog):
1334 if isinstance(r, revlog.revlog):
1328 pass
1335 pass
1329 elif util.safehasattr(r, b'_revlog'):
1336 elif util.safehasattr(r, b'_revlog'):
1330 r = r._revlog # pytype: disable=attribute-error
1337 r = r._revlog # pytype: disable=attribute-error
1331 elif r is not None:
1338 elif r is not None:
1332 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1339 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1333
1340
1334 if not r:
1341 if not r:
1335 if not returnrevlog:
1342 if not returnrevlog:
1336 raise error.Abort(_(b'cannot give path to non-revlog'))
1343 raise error.Abort(_(b'cannot give path to non-revlog'))
1337
1344
1338 if not file_:
1345 if not file_:
1339 raise error.CommandError(cmd, _(b'invalid arguments'))
1346 raise error.CommandError(cmd, _(b'invalid arguments'))
1340 if not os.path.isfile(file_):
1347 if not os.path.isfile(file_):
1341 raise error.Abort(_(b"revlog '%s' not found") % file_)
1348 raise error.Abort(_(b"revlog '%s' not found") % file_)
1342 r = revlog.revlog(
1349 r = revlog.revlog(
1343 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1350 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1344 )
1351 )
1345 return r
1352 return r
1346
1353
1347
1354
1348 def openrevlog(repo, cmd, file_, opts):
1355 def openrevlog(repo, cmd, file_, opts):
1349 """Obtain a revlog backing storage of an item.
1356 """Obtain a revlog backing storage of an item.
1350
1357
1351 This is similar to ``openstorage()`` except it always returns a revlog.
1358 This is similar to ``openstorage()`` except it always returns a revlog.
1352
1359
1353 In most cases, a caller cares about the main storage object - not the
1360 In most cases, a caller cares about the main storage object - not the
1354 revlog backing it. Therefore, this function should only be used by code
1361 revlog backing it. Therefore, this function should only be used by code
1355 that needs to examine low-level revlog implementation details. e.g. debug
1362 that needs to examine low-level revlog implementation details. e.g. debug
1356 commands.
1363 commands.
1357 """
1364 """
1358 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1365 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1359
1366
1360
1367
1361 def copy(ui, repo, pats, opts, rename=False):
1368 def copy(ui, repo, pats, opts, rename=False):
1362 # called with the repo lock held
1369 # called with the repo lock held
1363 #
1370 #
1364 # hgsep => pathname that uses "/" to separate directories
1371 # hgsep => pathname that uses "/" to separate directories
1365 # ossep => pathname that uses os.sep to separate directories
1372 # ossep => pathname that uses os.sep to separate directories
1366 cwd = repo.getcwd()
1373 cwd = repo.getcwd()
1367 targets = {}
1374 targets = {}
1368 after = opts.get(b"after")
1375 after = opts.get(b"after")
1369 dryrun = opts.get(b"dry_run")
1376 dryrun = opts.get(b"dry_run")
1370 wctx = repo[None]
1377 wctx = repo[None]
1371
1378
1372 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1379 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1373
1380
1374 def walkpat(pat):
1381 def walkpat(pat):
1375 srcs = []
1382 srcs = []
1376 if after:
1383 if after:
1377 badstates = b'?'
1384 badstates = b'?'
1378 else:
1385 else:
1379 badstates = b'?r'
1386 badstates = b'?r'
1380 m = scmutil.match(wctx, [pat], opts, globbed=True)
1387 m = scmutil.match(wctx, [pat], opts, globbed=True)
1381 for abs in wctx.walk(m):
1388 for abs in wctx.walk(m):
1382 state = repo.dirstate[abs]
1389 state = repo.dirstate[abs]
1383 rel = uipathfn(abs)
1390 rel = uipathfn(abs)
1384 exact = m.exact(abs)
1391 exact = m.exact(abs)
1385 if state in badstates:
1392 if state in badstates:
1386 if exact and state == b'?':
1393 if exact and state == b'?':
1387 ui.warn(_(b'%s: not copying - file is not managed\n') % rel)
1394 ui.warn(_(b'%s: not copying - file is not managed\n') % rel)
1388 if exact and state == b'r':
1395 if exact and state == b'r':
1389 ui.warn(
1396 ui.warn(
1390 _(
1397 _(
1391 b'%s: not copying - file has been marked for'
1398 b'%s: not copying - file has been marked for'
1392 b' remove\n'
1399 b' remove\n'
1393 )
1400 )
1394 % rel
1401 % rel
1395 )
1402 )
1396 continue
1403 continue
1397 # abs: hgsep
1404 # abs: hgsep
1398 # rel: ossep
1405 # rel: ossep
1399 srcs.append((abs, rel, exact))
1406 srcs.append((abs, rel, exact))
1400 return srcs
1407 return srcs
1401
1408
1402 # abssrc: hgsep
1409 # abssrc: hgsep
1403 # relsrc: ossep
1410 # relsrc: ossep
1404 # otarget: ossep
1411 # otarget: ossep
1405 def copyfile(abssrc, relsrc, otarget, exact):
1412 def copyfile(abssrc, relsrc, otarget, exact):
1406 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1413 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1407 if b'/' in abstarget:
1414 if b'/' in abstarget:
1408 # We cannot normalize abstarget itself, this would prevent
1415 # We cannot normalize abstarget itself, this would prevent
1409 # case only renames, like a => A.
1416 # case only renames, like a => A.
1410 abspath, absname = abstarget.rsplit(b'/', 1)
1417 abspath, absname = abstarget.rsplit(b'/', 1)
1411 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1418 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1412 reltarget = repo.pathto(abstarget, cwd)
1419 reltarget = repo.pathto(abstarget, cwd)
1413 target = repo.wjoin(abstarget)
1420 target = repo.wjoin(abstarget)
1414 src = repo.wjoin(abssrc)
1421 src = repo.wjoin(abssrc)
1415 state = repo.dirstate[abstarget]
1422 state = repo.dirstate[abstarget]
1416
1423
1417 scmutil.checkportable(ui, abstarget)
1424 scmutil.checkportable(ui, abstarget)
1418
1425
1419 # check for collisions
1426 # check for collisions
1420 prevsrc = targets.get(abstarget)
1427 prevsrc = targets.get(abstarget)
1421 if prevsrc is not None:
1428 if prevsrc is not None:
1422 ui.warn(
1429 ui.warn(
1423 _(b'%s: not overwriting - %s collides with %s\n')
1430 _(b'%s: not overwriting - %s collides with %s\n')
1424 % (
1431 % (
1425 reltarget,
1432 reltarget,
1426 repo.pathto(abssrc, cwd),
1433 repo.pathto(abssrc, cwd),
1427 repo.pathto(prevsrc, cwd),
1434 repo.pathto(prevsrc, cwd),
1428 )
1435 )
1429 )
1436 )
1430 return True # report a failure
1437 return True # report a failure
1431
1438
1432 # check for overwrites
1439 # check for overwrites
1433 exists = os.path.lexists(target)
1440 exists = os.path.lexists(target)
1434 samefile = False
1441 samefile = False
1435 if exists and abssrc != abstarget:
1442 if exists and abssrc != abstarget:
1436 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1443 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1437 abstarget
1444 abstarget
1438 ):
1445 ):
1439 if not rename:
1446 if not rename:
1440 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1447 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1441 return True # report a failure
1448 return True # report a failure
1442 exists = False
1449 exists = False
1443 samefile = True
1450 samefile = True
1444
1451
1445 if not after and exists or after and state in b'mn':
1452 if not after and exists or after and state in b'mn':
1446 if not opts[b'force']:
1453 if not opts[b'force']:
1447 if state in b'mn':
1454 if state in b'mn':
1448 msg = _(b'%s: not overwriting - file already committed\n')
1455 msg = _(b'%s: not overwriting - file already committed\n')
1449 if after:
1456 if after:
1450 flags = b'--after --force'
1457 flags = b'--after --force'
1451 else:
1458 else:
1452 flags = b'--force'
1459 flags = b'--force'
1453 if rename:
1460 if rename:
1454 hint = (
1461 hint = (
1455 _(
1462 _(
1456 b"('hg rename %s' to replace the file by "
1463 b"('hg rename %s' to replace the file by "
1457 b'recording a rename)\n'
1464 b'recording a rename)\n'
1458 )
1465 )
1459 % flags
1466 % flags
1460 )
1467 )
1461 else:
1468 else:
1462 hint = (
1469 hint = (
1463 _(
1470 _(
1464 b"('hg copy %s' to replace the file by "
1471 b"('hg copy %s' to replace the file by "
1465 b'recording a copy)\n'
1472 b'recording a copy)\n'
1466 )
1473 )
1467 % flags
1474 % flags
1468 )
1475 )
1469 else:
1476 else:
1470 msg = _(b'%s: not overwriting - file exists\n')
1477 msg = _(b'%s: not overwriting - file exists\n')
1471 if rename:
1478 if rename:
1472 hint = _(
1479 hint = _(
1473 b"('hg rename --after' to record the rename)\n"
1480 b"('hg rename --after' to record the rename)\n"
1474 )
1481 )
1475 else:
1482 else:
1476 hint = _(b"('hg copy --after' to record the copy)\n")
1483 hint = _(b"('hg copy --after' to record the copy)\n")
1477 ui.warn(msg % reltarget)
1484 ui.warn(msg % reltarget)
1478 ui.warn(hint)
1485 ui.warn(hint)
1479 return True # report a failure
1486 return True # report a failure
1480
1487
1481 if after:
1488 if after:
1482 if not exists:
1489 if not exists:
1483 if rename:
1490 if rename:
1484 ui.warn(
1491 ui.warn(
1485 _(b'%s: not recording move - %s does not exist\n')
1492 _(b'%s: not recording move - %s does not exist\n')
1486 % (relsrc, reltarget)
1493 % (relsrc, reltarget)
1487 )
1494 )
1488 else:
1495 else:
1489 ui.warn(
1496 ui.warn(
1490 _(b'%s: not recording copy - %s does not exist\n')
1497 _(b'%s: not recording copy - %s does not exist\n')
1491 % (relsrc, reltarget)
1498 % (relsrc, reltarget)
1492 )
1499 )
1493 return True # report a failure
1500 return True # report a failure
1494 elif not dryrun:
1501 elif not dryrun:
1495 try:
1502 try:
1496 if exists:
1503 if exists:
1497 os.unlink(target)
1504 os.unlink(target)
1498 targetdir = os.path.dirname(target) or b'.'
1505 targetdir = os.path.dirname(target) or b'.'
1499 if not os.path.isdir(targetdir):
1506 if not os.path.isdir(targetdir):
1500 os.makedirs(targetdir)
1507 os.makedirs(targetdir)
1501 if samefile:
1508 if samefile:
1502 tmp = target + b"~hgrename"
1509 tmp = target + b"~hgrename"
1503 os.rename(src, tmp)
1510 os.rename(src, tmp)
1504 os.rename(tmp, target)
1511 os.rename(tmp, target)
1505 else:
1512 else:
1506 # Preserve stat info on renames, not on copies; this matches
1513 # Preserve stat info on renames, not on copies; this matches
1507 # Linux CLI behavior.
1514 # Linux CLI behavior.
1508 util.copyfile(src, target, copystat=rename)
1515 util.copyfile(src, target, copystat=rename)
1509 srcexists = True
1516 srcexists = True
1510 except IOError as inst:
1517 except IOError as inst:
1511 if inst.errno == errno.ENOENT:
1518 if inst.errno == errno.ENOENT:
1512 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1519 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1513 srcexists = False
1520 srcexists = False
1514 else:
1521 else:
1515 ui.warn(
1522 ui.warn(
1516 _(b'%s: cannot copy - %s\n')
1523 _(b'%s: cannot copy - %s\n')
1517 % (relsrc, encoding.strtolocal(inst.strerror))
1524 % (relsrc, encoding.strtolocal(inst.strerror))
1518 )
1525 )
1519 return True # report a failure
1526 return True # report a failure
1520
1527
1521 if ui.verbose or not exact:
1528 if ui.verbose or not exact:
1522 if rename:
1529 if rename:
1523 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1530 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1524 else:
1531 else:
1525 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1532 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1526
1533
1527 targets[abstarget] = abssrc
1534 targets[abstarget] = abssrc
1528
1535
1529 # fix up dirstate
1536 # fix up dirstate
1530 scmutil.dirstatecopy(
1537 scmutil.dirstatecopy(
1531 ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1538 ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1532 )
1539 )
1533 if rename and not dryrun:
1540 if rename and not dryrun:
1534 if not after and srcexists and not samefile:
1541 if not after and srcexists and not samefile:
1535 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1542 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1536 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1543 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1537 wctx.forget([abssrc])
1544 wctx.forget([abssrc])
1538
1545
1539 # pat: ossep
1546 # pat: ossep
1540 # dest ossep
1547 # dest ossep
1541 # srcs: list of (hgsep, hgsep, ossep, bool)
1548 # srcs: list of (hgsep, hgsep, ossep, bool)
1542 # return: function that takes hgsep and returns ossep
1549 # return: function that takes hgsep and returns ossep
1543 def targetpathfn(pat, dest, srcs):
1550 def targetpathfn(pat, dest, srcs):
1544 if os.path.isdir(pat):
1551 if os.path.isdir(pat):
1545 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1552 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1546 abspfx = util.localpath(abspfx)
1553 abspfx = util.localpath(abspfx)
1547 if destdirexists:
1554 if destdirexists:
1548 striplen = len(os.path.split(abspfx)[0])
1555 striplen = len(os.path.split(abspfx)[0])
1549 else:
1556 else:
1550 striplen = len(abspfx)
1557 striplen = len(abspfx)
1551 if striplen:
1558 if striplen:
1552 striplen += len(pycompat.ossep)
1559 striplen += len(pycompat.ossep)
1553 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1560 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1554 elif destdirexists:
1561 elif destdirexists:
1555 res = lambda p: os.path.join(
1562 res = lambda p: os.path.join(
1556 dest, os.path.basename(util.localpath(p))
1563 dest, os.path.basename(util.localpath(p))
1557 )
1564 )
1558 else:
1565 else:
1559 res = lambda p: dest
1566 res = lambda p: dest
1560 return res
1567 return res
1561
1568
1562 # pat: ossep
1569 # pat: ossep
1563 # dest ossep
1570 # dest ossep
1564 # srcs: list of (hgsep, hgsep, ossep, bool)
1571 # srcs: list of (hgsep, hgsep, ossep, bool)
1565 # return: function that takes hgsep and returns ossep
1572 # return: function that takes hgsep and returns ossep
1566 def targetpathafterfn(pat, dest, srcs):
1573 def targetpathafterfn(pat, dest, srcs):
1567 if matchmod.patkind(pat):
1574 if matchmod.patkind(pat):
1568 # a mercurial pattern
1575 # a mercurial pattern
1569 res = lambda p: os.path.join(
1576 res = lambda p: os.path.join(
1570 dest, os.path.basename(util.localpath(p))
1577 dest, os.path.basename(util.localpath(p))
1571 )
1578 )
1572 else:
1579 else:
1573 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1580 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1574 if len(abspfx) < len(srcs[0][0]):
1581 if len(abspfx) < len(srcs[0][0]):
1575 # A directory. Either the target path contains the last
1582 # A directory. Either the target path contains the last
1576 # component of the source path or it does not.
1583 # component of the source path or it does not.
1577 def evalpath(striplen):
1584 def evalpath(striplen):
1578 score = 0
1585 score = 0
1579 for s in srcs:
1586 for s in srcs:
1580 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1587 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1581 if os.path.lexists(t):
1588 if os.path.lexists(t):
1582 score += 1
1589 score += 1
1583 return score
1590 return score
1584
1591
1585 abspfx = util.localpath(abspfx)
1592 abspfx = util.localpath(abspfx)
1586 striplen = len(abspfx)
1593 striplen = len(abspfx)
1587 if striplen:
1594 if striplen:
1588 striplen += len(pycompat.ossep)
1595 striplen += len(pycompat.ossep)
1589 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1596 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1590 score = evalpath(striplen)
1597 score = evalpath(striplen)
1591 striplen1 = len(os.path.split(abspfx)[0])
1598 striplen1 = len(os.path.split(abspfx)[0])
1592 if striplen1:
1599 if striplen1:
1593 striplen1 += len(pycompat.ossep)
1600 striplen1 += len(pycompat.ossep)
1594 if evalpath(striplen1) > score:
1601 if evalpath(striplen1) > score:
1595 striplen = striplen1
1602 striplen = striplen1
1596 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1603 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1597 else:
1604 else:
1598 # a file
1605 # a file
1599 if destdirexists:
1606 if destdirexists:
1600 res = lambda p: os.path.join(
1607 res = lambda p: os.path.join(
1601 dest, os.path.basename(util.localpath(p))
1608 dest, os.path.basename(util.localpath(p))
1602 )
1609 )
1603 else:
1610 else:
1604 res = lambda p: dest
1611 res = lambda p: dest
1605 return res
1612 return res
1606
1613
1607 pats = scmutil.expandpats(pats)
1614 pats = scmutil.expandpats(pats)
1608 if not pats:
1615 if not pats:
1609 raise error.Abort(_(b'no source or destination specified'))
1616 raise error.Abort(_(b'no source or destination specified'))
1610 if len(pats) == 1:
1617 if len(pats) == 1:
1611 raise error.Abort(_(b'no destination specified'))
1618 raise error.Abort(_(b'no destination specified'))
1612 dest = pats.pop()
1619 dest = pats.pop()
1613 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1620 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1614 if not destdirexists:
1621 if not destdirexists:
1615 if len(pats) > 1 or matchmod.patkind(pats[0]):
1622 if len(pats) > 1 or matchmod.patkind(pats[0]):
1616 raise error.Abort(
1623 raise error.Abort(
1617 _(
1624 _(
1618 b'with multiple sources, destination must be an '
1625 b'with multiple sources, destination must be an '
1619 b'existing directory'
1626 b'existing directory'
1620 )
1627 )
1621 )
1628 )
1622 if util.endswithsep(dest):
1629 if util.endswithsep(dest):
1623 raise error.Abort(_(b'destination %s is not a directory') % dest)
1630 raise error.Abort(_(b'destination %s is not a directory') % dest)
1624
1631
1625 tfn = targetpathfn
1632 tfn = targetpathfn
1626 if after:
1633 if after:
1627 tfn = targetpathafterfn
1634 tfn = targetpathafterfn
1628 copylist = []
1635 copylist = []
1629 for pat in pats:
1636 for pat in pats:
1630 srcs = walkpat(pat)
1637 srcs = walkpat(pat)
1631 if not srcs:
1638 if not srcs:
1632 continue
1639 continue
1633 copylist.append((tfn(pat, dest, srcs), srcs))
1640 copylist.append((tfn(pat, dest, srcs), srcs))
1634 if not copylist:
1641 if not copylist:
1635 raise error.Abort(_(b'no files to copy'))
1642 raise error.Abort(_(b'no files to copy'))
1636
1643
1637 errors = 0
1644 errors = 0
1638 for targetpath, srcs in copylist:
1645 for targetpath, srcs in copylist:
1639 for abssrc, relsrc, exact in srcs:
1646 for abssrc, relsrc, exact in srcs:
1640 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1647 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1641 errors += 1
1648 errors += 1
1642
1649
1643 return errors != 0
1650 return errors != 0
1644
1651
1645
1652
1646 ## facility to let extension process additional data into an import patch
1653 ## facility to let extension process additional data into an import patch
1647 # list of identifier to be executed in order
1654 # list of identifier to be executed in order
1648 extrapreimport = [] # run before commit
1655 extrapreimport = [] # run before commit
1649 extrapostimport = [] # run after commit
1656 extrapostimport = [] # run after commit
1650 # mapping from identifier to actual import function
1657 # mapping from identifier to actual import function
1651 #
1658 #
1652 # 'preimport' are run before the commit is made and are provided the following
1659 # 'preimport' are run before the commit is made and are provided the following
1653 # arguments:
1660 # arguments:
1654 # - repo: the localrepository instance,
1661 # - repo: the localrepository instance,
1655 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1662 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1656 # - extra: the future extra dictionary of the changeset, please mutate it,
1663 # - extra: the future extra dictionary of the changeset, please mutate it,
1657 # - opts: the import options.
1664 # - opts: the import options.
1658 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1665 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1659 # mutation of in memory commit and more. Feel free to rework the code to get
1666 # mutation of in memory commit and more. Feel free to rework the code to get
1660 # there.
1667 # there.
1661 extrapreimportmap = {}
1668 extrapreimportmap = {}
1662 # 'postimport' are run after the commit is made and are provided the following
1669 # 'postimport' are run after the commit is made and are provided the following
1663 # argument:
1670 # argument:
1664 # - ctx: the changectx created by import.
1671 # - ctx: the changectx created by import.
1665 extrapostimportmap = {}
1672 extrapostimportmap = {}
1666
1673
1667
1674
1668 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1675 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1669 """Utility function used by commands.import to import a single patch
1676 """Utility function used by commands.import to import a single patch
1670
1677
1671 This function is explicitly defined here to help the evolve extension to
1678 This function is explicitly defined here to help the evolve extension to
1672 wrap this part of the import logic.
1679 wrap this part of the import logic.
1673
1680
1674 The API is currently a bit ugly because it a simple code translation from
1681 The API is currently a bit ugly because it a simple code translation from
1675 the import command. Feel free to make it better.
1682 the import command. Feel free to make it better.
1676
1683
1677 :patchdata: a dictionary containing parsed patch data (such as from
1684 :patchdata: a dictionary containing parsed patch data (such as from
1678 ``patch.extract()``)
1685 ``patch.extract()``)
1679 :parents: nodes that will be parent of the created commit
1686 :parents: nodes that will be parent of the created commit
1680 :opts: the full dict of option passed to the import command
1687 :opts: the full dict of option passed to the import command
1681 :msgs: list to save commit message to.
1688 :msgs: list to save commit message to.
1682 (used in case we need to save it when failing)
1689 (used in case we need to save it when failing)
1683 :updatefunc: a function that update a repo to a given node
1690 :updatefunc: a function that update a repo to a given node
1684 updatefunc(<repo>, <node>)
1691 updatefunc(<repo>, <node>)
1685 """
1692 """
1686 # avoid cycle context -> subrepo -> cmdutil
1693 # avoid cycle context -> subrepo -> cmdutil
1687 from . import context
1694 from . import context
1688
1695
1689 tmpname = patchdata.get(b'filename')
1696 tmpname = patchdata.get(b'filename')
1690 message = patchdata.get(b'message')
1697 message = patchdata.get(b'message')
1691 user = opts.get(b'user') or patchdata.get(b'user')
1698 user = opts.get(b'user') or patchdata.get(b'user')
1692 date = opts.get(b'date') or patchdata.get(b'date')
1699 date = opts.get(b'date') or patchdata.get(b'date')
1693 branch = patchdata.get(b'branch')
1700 branch = patchdata.get(b'branch')
1694 nodeid = patchdata.get(b'nodeid')
1701 nodeid = patchdata.get(b'nodeid')
1695 p1 = patchdata.get(b'p1')
1702 p1 = patchdata.get(b'p1')
1696 p2 = patchdata.get(b'p2')
1703 p2 = patchdata.get(b'p2')
1697
1704
1698 nocommit = opts.get(b'no_commit')
1705 nocommit = opts.get(b'no_commit')
1699 importbranch = opts.get(b'import_branch')
1706 importbranch = opts.get(b'import_branch')
1700 update = not opts.get(b'bypass')
1707 update = not opts.get(b'bypass')
1701 strip = opts[b"strip"]
1708 strip = opts[b"strip"]
1702 prefix = opts[b"prefix"]
1709 prefix = opts[b"prefix"]
1703 sim = float(opts.get(b'similarity') or 0)
1710 sim = float(opts.get(b'similarity') or 0)
1704
1711
1705 if not tmpname:
1712 if not tmpname:
1706 return None, None, False
1713 return None, None, False
1707
1714
1708 rejects = False
1715 rejects = False
1709
1716
1710 cmdline_message = logmessage(ui, opts)
1717 cmdline_message = logmessage(ui, opts)
1711 if cmdline_message:
1718 if cmdline_message:
1712 # pickup the cmdline msg
1719 # pickup the cmdline msg
1713 message = cmdline_message
1720 message = cmdline_message
1714 elif message:
1721 elif message:
1715 # pickup the patch msg
1722 # pickup the patch msg
1716 message = message.strip()
1723 message = message.strip()
1717 else:
1724 else:
1718 # launch the editor
1725 # launch the editor
1719 message = None
1726 message = None
1720 ui.debug(b'message:\n%s\n' % (message or b''))
1727 ui.debug(b'message:\n%s\n' % (message or b''))
1721
1728
1722 if len(parents) == 1:
1729 if len(parents) == 1:
1723 parents.append(repo[nullid])
1730 parents.append(repo[nullid])
1724 if opts.get(b'exact'):
1731 if opts.get(b'exact'):
1725 if not nodeid or not p1:
1732 if not nodeid or not p1:
1726 raise error.Abort(_(b'not a Mercurial patch'))
1733 raise error.Abort(_(b'not a Mercurial patch'))
1727 p1 = repo[p1]
1734 p1 = repo[p1]
1728 p2 = repo[p2 or nullid]
1735 p2 = repo[p2 or nullid]
1729 elif p2:
1736 elif p2:
1730 try:
1737 try:
1731 p1 = repo[p1]
1738 p1 = repo[p1]
1732 p2 = repo[p2]
1739 p2 = repo[p2]
1733 # Without any options, consider p2 only if the
1740 # Without any options, consider p2 only if the
1734 # patch is being applied on top of the recorded
1741 # patch is being applied on top of the recorded
1735 # first parent.
1742 # first parent.
1736 if p1 != parents[0]:
1743 if p1 != parents[0]:
1737 p1 = parents[0]
1744 p1 = parents[0]
1738 p2 = repo[nullid]
1745 p2 = repo[nullid]
1739 except error.RepoError:
1746 except error.RepoError:
1740 p1, p2 = parents
1747 p1, p2 = parents
1741 if p2.node() == nullid:
1748 if p2.node() == nullid:
1742 ui.warn(
1749 ui.warn(
1743 _(
1750 _(
1744 b"warning: import the patch as a normal revision\n"
1751 b"warning: import the patch as a normal revision\n"
1745 b"(use --exact to import the patch as a merge)\n"
1752 b"(use --exact to import the patch as a merge)\n"
1746 )
1753 )
1747 )
1754 )
1748 else:
1755 else:
1749 p1, p2 = parents
1756 p1, p2 = parents
1750
1757
1751 n = None
1758 n = None
1752 if update:
1759 if update:
1753 if p1 != parents[0]:
1760 if p1 != parents[0]:
1754 updatefunc(repo, p1.node())
1761 updatefunc(repo, p1.node())
1755 if p2 != parents[1]:
1762 if p2 != parents[1]:
1756 repo.setparents(p1.node(), p2.node())
1763 repo.setparents(p1.node(), p2.node())
1757
1764
1758 if opts.get(b'exact') or importbranch:
1765 if opts.get(b'exact') or importbranch:
1759 repo.dirstate.setbranch(branch or b'default')
1766 repo.dirstate.setbranch(branch or b'default')
1760
1767
1761 partial = opts.get(b'partial', False)
1768 partial = opts.get(b'partial', False)
1762 files = set()
1769 files = set()
1763 try:
1770 try:
1764 patch.patch(
1771 patch.patch(
1765 ui,
1772 ui,
1766 repo,
1773 repo,
1767 tmpname,
1774 tmpname,
1768 strip=strip,
1775 strip=strip,
1769 prefix=prefix,
1776 prefix=prefix,
1770 files=files,
1777 files=files,
1771 eolmode=None,
1778 eolmode=None,
1772 similarity=sim / 100.0,
1779 similarity=sim / 100.0,
1773 )
1780 )
1774 except error.PatchError as e:
1781 except error.PatchError as e:
1775 if not partial:
1782 if not partial:
1776 raise error.Abort(pycompat.bytestr(e))
1783 raise error.Abort(pycompat.bytestr(e))
1777 if partial:
1784 if partial:
1778 rejects = True
1785 rejects = True
1779
1786
1780 files = list(files)
1787 files = list(files)
1781 if nocommit:
1788 if nocommit:
1782 if message:
1789 if message:
1783 msgs.append(message)
1790 msgs.append(message)
1784 else:
1791 else:
1785 if opts.get(b'exact') or p2:
1792 if opts.get(b'exact') or p2:
1786 # If you got here, you either use --force and know what
1793 # If you got here, you either use --force and know what
1787 # you are doing or used --exact or a merge patch while
1794 # you are doing or used --exact or a merge patch while
1788 # being updated to its first parent.
1795 # being updated to its first parent.
1789 m = None
1796 m = None
1790 else:
1797 else:
1791 m = scmutil.matchfiles(repo, files or [])
1798 m = scmutil.matchfiles(repo, files or [])
1792 editform = mergeeditform(repo[None], b'import.normal')
1799 editform = mergeeditform(repo[None], b'import.normal')
1793 if opts.get(b'exact'):
1800 if opts.get(b'exact'):
1794 editor = None
1801 editor = None
1795 else:
1802 else:
1796 editor = getcommiteditor(
1803 editor = getcommiteditor(
1797 editform=editform, **pycompat.strkwargs(opts)
1804 editform=editform, **pycompat.strkwargs(opts)
1798 )
1805 )
1799 extra = {}
1806 extra = {}
1800 for idfunc in extrapreimport:
1807 for idfunc in extrapreimport:
1801 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1808 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1802 overrides = {}
1809 overrides = {}
1803 if partial:
1810 if partial:
1804 overrides[(b'ui', b'allowemptycommit')] = True
1811 overrides[(b'ui', b'allowemptycommit')] = True
1805 if opts.get(b'secret'):
1812 if opts.get(b'secret'):
1806 overrides[(b'phases', b'new-commit')] = b'secret'
1813 overrides[(b'phases', b'new-commit')] = b'secret'
1807 with repo.ui.configoverride(overrides, b'import'):
1814 with repo.ui.configoverride(overrides, b'import'):
1808 n = repo.commit(
1815 n = repo.commit(
1809 message, user, date, match=m, editor=editor, extra=extra
1816 message, user, date, match=m, editor=editor, extra=extra
1810 )
1817 )
1811 for idfunc in extrapostimport:
1818 for idfunc in extrapostimport:
1812 extrapostimportmap[idfunc](repo[n])
1819 extrapostimportmap[idfunc](repo[n])
1813 else:
1820 else:
1814 if opts.get(b'exact') or importbranch:
1821 if opts.get(b'exact') or importbranch:
1815 branch = branch or b'default'
1822 branch = branch or b'default'
1816 else:
1823 else:
1817 branch = p1.branch()
1824 branch = p1.branch()
1818 store = patch.filestore()
1825 store = patch.filestore()
1819 try:
1826 try:
1820 files = set()
1827 files = set()
1821 try:
1828 try:
1822 patch.patchrepo(
1829 patch.patchrepo(
1823 ui,
1830 ui,
1824 repo,
1831 repo,
1825 p1,
1832 p1,
1826 store,
1833 store,
1827 tmpname,
1834 tmpname,
1828 strip,
1835 strip,
1829 prefix,
1836 prefix,
1830 files,
1837 files,
1831 eolmode=None,
1838 eolmode=None,
1832 )
1839 )
1833 except error.PatchError as e:
1840 except error.PatchError as e:
1834 raise error.Abort(stringutil.forcebytestr(e))
1841 raise error.Abort(stringutil.forcebytestr(e))
1835 if opts.get(b'exact'):
1842 if opts.get(b'exact'):
1836 editor = None
1843 editor = None
1837 else:
1844 else:
1838 editor = getcommiteditor(editform=b'import.bypass')
1845 editor = getcommiteditor(editform=b'import.bypass')
1839 memctx = context.memctx(
1846 memctx = context.memctx(
1840 repo,
1847 repo,
1841 (p1.node(), p2.node()),
1848 (p1.node(), p2.node()),
1842 message,
1849 message,
1843 files=files,
1850 files=files,
1844 filectxfn=store,
1851 filectxfn=store,
1845 user=user,
1852 user=user,
1846 date=date,
1853 date=date,
1847 branch=branch,
1854 branch=branch,
1848 editor=editor,
1855 editor=editor,
1849 )
1856 )
1850 n = memctx.commit()
1857 n = memctx.commit()
1851 finally:
1858 finally:
1852 store.close()
1859 store.close()
1853 if opts.get(b'exact') and nocommit:
1860 if opts.get(b'exact') and nocommit:
1854 # --exact with --no-commit is still useful in that it does merge
1861 # --exact with --no-commit is still useful in that it does merge
1855 # and branch bits
1862 # and branch bits
1856 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
1863 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
1857 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
1864 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
1858 raise error.Abort(_(b'patch is damaged or loses information'))
1865 raise error.Abort(_(b'patch is damaged or loses information'))
1859 msg = _(b'applied to working directory')
1866 msg = _(b'applied to working directory')
1860 if n:
1867 if n:
1861 # i18n: refers to a short changeset id
1868 # i18n: refers to a short changeset id
1862 msg = _(b'created %s') % short(n)
1869 msg = _(b'created %s') % short(n)
1863 return msg, n, rejects
1870 return msg, n, rejects
1864
1871
1865
1872
1866 # facility to let extensions include additional data in an exported patch
1873 # facility to let extensions include additional data in an exported patch
1867 # list of identifiers to be executed in order
1874 # list of identifiers to be executed in order
1868 extraexport = []
1875 extraexport = []
1869 # mapping from identifier to actual export function
1876 # mapping from identifier to actual export function
1870 # function as to return a string to be added to the header or None
1877 # function as to return a string to be added to the header or None
1871 # it is given two arguments (sequencenumber, changectx)
1878 # it is given two arguments (sequencenumber, changectx)
1872 extraexportmap = {}
1879 extraexportmap = {}
1873
1880
1874
1881
1875 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1882 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1876 node = scmutil.binnode(ctx)
1883 node = scmutil.binnode(ctx)
1877 parents = [p.node() for p in ctx.parents() if p]
1884 parents = [p.node() for p in ctx.parents() if p]
1878 branch = ctx.branch()
1885 branch = ctx.branch()
1879 if switch_parent:
1886 if switch_parent:
1880 parents.reverse()
1887 parents.reverse()
1881
1888
1882 if parents:
1889 if parents:
1883 prev = parents[0]
1890 prev = parents[0]
1884 else:
1891 else:
1885 prev = nullid
1892 prev = nullid
1886
1893
1887 fm.context(ctx=ctx)
1894 fm.context(ctx=ctx)
1888 fm.plain(b'# HG changeset patch\n')
1895 fm.plain(b'# HG changeset patch\n')
1889 fm.write(b'user', b'# User %s\n', ctx.user())
1896 fm.write(b'user', b'# User %s\n', ctx.user())
1890 fm.plain(b'# Date %d %d\n' % ctx.date())
1897 fm.plain(b'# Date %d %d\n' % ctx.date())
1891 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
1898 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
1892 fm.condwrite(
1899 fm.condwrite(
1893 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
1900 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
1894 )
1901 )
1895 fm.write(b'node', b'# Node ID %s\n', hex(node))
1902 fm.write(b'node', b'# Node ID %s\n', hex(node))
1896 fm.plain(b'# Parent %s\n' % hex(prev))
1903 fm.plain(b'# Parent %s\n' % hex(prev))
1897 if len(parents) > 1:
1904 if len(parents) > 1:
1898 fm.plain(b'# Parent %s\n' % hex(parents[1]))
1905 fm.plain(b'# Parent %s\n' % hex(parents[1]))
1899 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
1906 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
1900
1907
1901 # TODO: redesign extraexportmap function to support formatter
1908 # TODO: redesign extraexportmap function to support formatter
1902 for headerid in extraexport:
1909 for headerid in extraexport:
1903 header = extraexportmap[headerid](seqno, ctx)
1910 header = extraexportmap[headerid](seqno, ctx)
1904 if header is not None:
1911 if header is not None:
1905 fm.plain(b'# %s\n' % header)
1912 fm.plain(b'# %s\n' % header)
1906
1913
1907 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
1914 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
1908 fm.plain(b'\n')
1915 fm.plain(b'\n')
1909
1916
1910 if fm.isplain():
1917 if fm.isplain():
1911 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1918 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1912 for chunk, label in chunkiter:
1919 for chunk, label in chunkiter:
1913 fm.plain(chunk, label=label)
1920 fm.plain(chunk, label=label)
1914 else:
1921 else:
1915 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1922 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1916 # TODO: make it structured?
1923 # TODO: make it structured?
1917 fm.data(diff=b''.join(chunkiter))
1924 fm.data(diff=b''.join(chunkiter))
1918
1925
1919
1926
1920 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1927 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1921 """Export changesets to stdout or a single file"""
1928 """Export changesets to stdout or a single file"""
1922 for seqno, rev in enumerate(revs, 1):
1929 for seqno, rev in enumerate(revs, 1):
1923 ctx = repo[rev]
1930 ctx = repo[rev]
1924 if not dest.startswith(b'<'):
1931 if not dest.startswith(b'<'):
1925 repo.ui.note(b"%s\n" % dest)
1932 repo.ui.note(b"%s\n" % dest)
1926 fm.startitem()
1933 fm.startitem()
1927 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1934 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1928
1935
1929
1936
1930 def _exportfntemplate(
1937 def _exportfntemplate(
1931 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
1938 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
1932 ):
1939 ):
1933 """Export changesets to possibly multiple files"""
1940 """Export changesets to possibly multiple files"""
1934 total = len(revs)
1941 total = len(revs)
1935 revwidth = max(len(str(rev)) for rev in revs)
1942 revwidth = max(len(str(rev)) for rev in revs)
1936 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1943 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1937
1944
1938 for seqno, rev in enumerate(revs, 1):
1945 for seqno, rev in enumerate(revs, 1):
1939 ctx = repo[rev]
1946 ctx = repo[rev]
1940 dest = makefilename(
1947 dest = makefilename(
1941 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
1948 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
1942 )
1949 )
1943 filemap.setdefault(dest, []).append((seqno, rev))
1950 filemap.setdefault(dest, []).append((seqno, rev))
1944
1951
1945 for dest in filemap:
1952 for dest in filemap:
1946 with formatter.maybereopen(basefm, dest) as fm:
1953 with formatter.maybereopen(basefm, dest) as fm:
1947 repo.ui.note(b"%s\n" % dest)
1954 repo.ui.note(b"%s\n" % dest)
1948 for seqno, rev in filemap[dest]:
1955 for seqno, rev in filemap[dest]:
1949 fm.startitem()
1956 fm.startitem()
1950 ctx = repo[rev]
1957 ctx = repo[rev]
1951 _exportsingle(
1958 _exportsingle(
1952 repo, ctx, fm, match, switch_parent, seqno, diffopts
1959 repo, ctx, fm, match, switch_parent, seqno, diffopts
1953 )
1960 )
1954
1961
1955
1962
1956 def _prefetchchangedfiles(repo, revs, match):
1963 def _prefetchchangedfiles(repo, revs, match):
1957 allfiles = set()
1964 allfiles = set()
1958 for rev in revs:
1965 for rev in revs:
1959 for file in repo[rev].files():
1966 for file in repo[rev].files():
1960 if not match or match(file):
1967 if not match or match(file):
1961 allfiles.add(file)
1968 allfiles.add(file)
1962 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1969 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1963
1970
1964
1971
1965 def export(
1972 def export(
1966 repo,
1973 repo,
1967 revs,
1974 revs,
1968 basefm,
1975 basefm,
1969 fntemplate=b'hg-%h.patch',
1976 fntemplate=b'hg-%h.patch',
1970 switch_parent=False,
1977 switch_parent=False,
1971 opts=None,
1978 opts=None,
1972 match=None,
1979 match=None,
1973 ):
1980 ):
1974 '''export changesets as hg patches
1981 '''export changesets as hg patches
1975
1982
1976 Args:
1983 Args:
1977 repo: The repository from which we're exporting revisions.
1984 repo: The repository from which we're exporting revisions.
1978 revs: A list of revisions to export as revision numbers.
1985 revs: A list of revisions to export as revision numbers.
1979 basefm: A formatter to which patches should be written.
1986 basefm: A formatter to which patches should be written.
1980 fntemplate: An optional string to use for generating patch file names.
1987 fntemplate: An optional string to use for generating patch file names.
1981 switch_parent: If True, show diffs against second parent when not nullid.
1988 switch_parent: If True, show diffs against second parent when not nullid.
1982 Default is false, which always shows diff against p1.
1989 Default is false, which always shows diff against p1.
1983 opts: diff options to use for generating the patch.
1990 opts: diff options to use for generating the patch.
1984 match: If specified, only export changes to files matching this matcher.
1991 match: If specified, only export changes to files matching this matcher.
1985
1992
1986 Returns:
1993 Returns:
1987 Nothing.
1994 Nothing.
1988
1995
1989 Side Effect:
1996 Side Effect:
1990 "HG Changeset Patch" data is emitted to one of the following
1997 "HG Changeset Patch" data is emitted to one of the following
1991 destinations:
1998 destinations:
1992 fntemplate specified: Each rev is written to a unique file named using
1999 fntemplate specified: Each rev is written to a unique file named using
1993 the given template.
2000 the given template.
1994 Otherwise: All revs will be written to basefm.
2001 Otherwise: All revs will be written to basefm.
1995 '''
2002 '''
1996 _prefetchchangedfiles(repo, revs, match)
2003 _prefetchchangedfiles(repo, revs, match)
1997
2004
1998 if not fntemplate:
2005 if not fntemplate:
1999 _exportfile(
2006 _exportfile(
2000 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2007 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2001 )
2008 )
2002 else:
2009 else:
2003 _exportfntemplate(
2010 _exportfntemplate(
2004 repo, revs, basefm, fntemplate, switch_parent, opts, match
2011 repo, revs, basefm, fntemplate, switch_parent, opts, match
2005 )
2012 )
2006
2013
2007
2014
2008 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2015 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2009 """Export changesets to the given file stream"""
2016 """Export changesets to the given file stream"""
2010 _prefetchchangedfiles(repo, revs, match)
2017 _prefetchchangedfiles(repo, revs, match)
2011
2018
2012 dest = getattr(fp, 'name', b'<unnamed>')
2019 dest = getattr(fp, 'name', b'<unnamed>')
2013 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2020 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2014 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2021 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2015
2022
2016
2023
2017 def showmarker(fm, marker, index=None):
2024 def showmarker(fm, marker, index=None):
2018 """utility function to display obsolescence marker in a readable way
2025 """utility function to display obsolescence marker in a readable way
2019
2026
2020 To be used by debug function."""
2027 To be used by debug function."""
2021 if index is not None:
2028 if index is not None:
2022 fm.write(b'index', b'%i ', index)
2029 fm.write(b'index', b'%i ', index)
2023 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2030 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2024 succs = marker.succnodes()
2031 succs = marker.succnodes()
2025 fm.condwrite(
2032 fm.condwrite(
2026 succs,
2033 succs,
2027 b'succnodes',
2034 b'succnodes',
2028 b'%s ',
2035 b'%s ',
2029 fm.formatlist(map(hex, succs), name=b'node'),
2036 fm.formatlist(map(hex, succs), name=b'node'),
2030 )
2037 )
2031 fm.write(b'flag', b'%X ', marker.flags())
2038 fm.write(b'flag', b'%X ', marker.flags())
2032 parents = marker.parentnodes()
2039 parents = marker.parentnodes()
2033 if parents is not None:
2040 if parents is not None:
2034 fm.write(
2041 fm.write(
2035 b'parentnodes',
2042 b'parentnodes',
2036 b'{%s} ',
2043 b'{%s} ',
2037 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2044 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2038 )
2045 )
2039 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2046 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2040 meta = marker.metadata().copy()
2047 meta = marker.metadata().copy()
2041 meta.pop(b'date', None)
2048 meta.pop(b'date', None)
2042 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2049 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2043 fm.write(
2050 fm.write(
2044 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2051 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2045 )
2052 )
2046 fm.plain(b'\n')
2053 fm.plain(b'\n')
2047
2054
2048
2055
2049 def finddate(ui, repo, date):
2056 def finddate(ui, repo, date):
2050 """Find the tipmost changeset that matches the given date spec"""
2057 """Find the tipmost changeset that matches the given date spec"""
2051
2058
2052 df = dateutil.matchdate(date)
2059 df = dateutil.matchdate(date)
2053 m = scmutil.matchall(repo)
2060 m = scmutil.matchall(repo)
2054 results = {}
2061 results = {}
2055
2062
2056 def prep(ctx, fns):
2063 def prep(ctx, fns):
2057 d = ctx.date()
2064 d = ctx.date()
2058 if df(d[0]):
2065 if df(d[0]):
2059 results[ctx.rev()] = d
2066 results[ctx.rev()] = d
2060
2067
2061 for ctx in walkchangerevs(repo, m, {b'rev': None}, prep):
2068 for ctx in walkchangerevs(repo, m, {b'rev': None}, prep):
2062 rev = ctx.rev()
2069 rev = ctx.rev()
2063 if rev in results:
2070 if rev in results:
2064 ui.status(
2071 ui.status(
2065 _(b"found revision %d from %s\n")
2072 _(b"found revision %d from %s\n")
2066 % (rev, dateutil.datestr(results[rev]))
2073 % (rev, dateutil.datestr(results[rev]))
2067 )
2074 )
2068 return b'%d' % rev
2075 return b'%d' % rev
2069
2076
2070 raise error.Abort(_(b"revision matching date not found"))
2077 raise error.Abort(_(b"revision matching date not found"))
2071
2078
2072
2079
2073 def increasingwindows(windowsize=8, sizelimit=512):
2080 def increasingwindows(windowsize=8, sizelimit=512):
2074 while True:
2081 while True:
2075 yield windowsize
2082 yield windowsize
2076 if windowsize < sizelimit:
2083 if windowsize < sizelimit:
2077 windowsize *= 2
2084 windowsize *= 2
2078
2085
2079
2086
2080 def _walkrevs(repo, opts):
2087 def _walkrevs(repo, opts):
2081 # Default --rev value depends on --follow but --follow behavior
2088 # Default --rev value depends on --follow but --follow behavior
2082 # depends on revisions resolved from --rev...
2089 # depends on revisions resolved from --rev...
2083 follow = opts.get(b'follow') or opts.get(b'follow_first')
2090 follow = opts.get(b'follow') or opts.get(b'follow_first')
2084 if opts.get(b'rev'):
2091 if opts.get(b'rev'):
2085 revs = scmutil.revrange(repo, opts[b'rev'])
2092 revs = scmutil.revrange(repo, opts[b'rev'])
2086 elif follow and repo.dirstate.p1() == nullid:
2093 elif follow and repo.dirstate.p1() == nullid:
2087 revs = smartset.baseset()
2094 revs = smartset.baseset()
2088 elif follow:
2095 elif follow:
2089 revs = repo.revs(b'reverse(:.)')
2096 revs = repo.revs(b'reverse(:.)')
2090 else:
2097 else:
2091 revs = smartset.spanset(repo)
2098 revs = smartset.spanset(repo)
2092 revs.reverse()
2099 revs.reverse()
2093 return revs
2100 return revs
2094
2101
2095
2102
2096 class FileWalkError(Exception):
2103 class FileWalkError(Exception):
2097 pass
2104 pass
2098
2105
2099
2106
2100 def walkfilerevs(repo, match, follow, revs, fncache):
2107 def walkfilerevs(repo, match, follow, revs, fncache):
2101 '''Walks the file history for the matched files.
2108 '''Walks the file history for the matched files.
2102
2109
2103 Returns the changeset revs that are involved in the file history.
2110 Returns the changeset revs that are involved in the file history.
2104
2111
2105 Throws FileWalkError if the file history can't be walked using
2112 Throws FileWalkError if the file history can't be walked using
2106 filelogs alone.
2113 filelogs alone.
2107 '''
2114 '''
2108 wanted = set()
2115 wanted = set()
2109 copies = []
2116 copies = []
2110 minrev, maxrev = min(revs), max(revs)
2117 minrev, maxrev = min(revs), max(revs)
2111
2118
2112 def filerevs(filelog, last):
2119 def filerevs(filelog, last):
2113 """
2120 """
2114 Only files, no patterns. Check the history of each file.
2121 Only files, no patterns. Check the history of each file.
2115
2122
2116 Examines filelog entries within minrev, maxrev linkrev range
2123 Examines filelog entries within minrev, maxrev linkrev range
2117 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2124 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2118 tuples in backwards order
2125 tuples in backwards order
2119 """
2126 """
2120 cl_count = len(repo)
2127 cl_count = len(repo)
2121 revs = []
2128 revs = []
2122 for j in pycompat.xrange(0, last + 1):
2129 for j in pycompat.xrange(0, last + 1):
2123 linkrev = filelog.linkrev(j)
2130 linkrev = filelog.linkrev(j)
2124 if linkrev < minrev:
2131 if linkrev < minrev:
2125 continue
2132 continue
2126 # only yield rev for which we have the changelog, it can
2133 # only yield rev for which we have the changelog, it can
2127 # happen while doing "hg log" during a pull or commit
2134 # happen while doing "hg log" during a pull or commit
2128 if linkrev >= cl_count:
2135 if linkrev >= cl_count:
2129 break
2136 break
2130
2137
2131 parentlinkrevs = []
2138 parentlinkrevs = []
2132 for p in filelog.parentrevs(j):
2139 for p in filelog.parentrevs(j):
2133 if p != nullrev:
2140 if p != nullrev:
2134 parentlinkrevs.append(filelog.linkrev(p))
2141 parentlinkrevs.append(filelog.linkrev(p))
2135 n = filelog.node(j)
2142 n = filelog.node(j)
2136 revs.append(
2143 revs.append(
2137 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2144 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2138 )
2145 )
2139
2146
2140 return reversed(revs)
2147 return reversed(revs)
2141
2148
2142 def iterfiles():
2149 def iterfiles():
2143 pctx = repo[b'.']
2150 pctx = repo[b'.']
2144 for filename in match.files():
2151 for filename in match.files():
2145 if follow:
2152 if follow:
2146 if filename not in pctx:
2153 if filename not in pctx:
2147 raise error.Abort(
2154 raise error.Abort(
2148 _(
2155 _(
2149 b'cannot follow file not in parent '
2156 b'cannot follow file not in parent '
2150 b'revision: "%s"'
2157 b'revision: "%s"'
2151 )
2158 )
2152 % filename
2159 % filename
2153 )
2160 )
2154 yield filename, pctx[filename].filenode()
2161 yield filename, pctx[filename].filenode()
2155 else:
2162 else:
2156 yield filename, None
2163 yield filename, None
2157 for filename_node in copies:
2164 for filename_node in copies:
2158 yield filename_node
2165 yield filename_node
2159
2166
2160 for file_, node in iterfiles():
2167 for file_, node in iterfiles():
2161 filelog = repo.file(file_)
2168 filelog = repo.file(file_)
2162 if not len(filelog):
2169 if not len(filelog):
2163 if node is None:
2170 if node is None:
2164 # A zero count may be a directory or deleted file, so
2171 # A zero count may be a directory or deleted file, so
2165 # try to find matching entries on the slow path.
2172 # try to find matching entries on the slow path.
2166 if follow:
2173 if follow:
2167 raise error.Abort(
2174 raise error.Abort(
2168 _(b'cannot follow nonexistent file: "%s"') % file_
2175 _(b'cannot follow nonexistent file: "%s"') % file_
2169 )
2176 )
2170 raise FileWalkError(b"Cannot walk via filelog")
2177 raise FileWalkError(b"Cannot walk via filelog")
2171 else:
2178 else:
2172 continue
2179 continue
2173
2180
2174 if node is None:
2181 if node is None:
2175 last = len(filelog) - 1
2182 last = len(filelog) - 1
2176 else:
2183 else:
2177 last = filelog.rev(node)
2184 last = filelog.rev(node)
2178
2185
2179 # keep track of all ancestors of the file
2186 # keep track of all ancestors of the file
2180 ancestors = {filelog.linkrev(last)}
2187 ancestors = {filelog.linkrev(last)}
2181
2188
2182 # iterate from latest to oldest revision
2189 # iterate from latest to oldest revision
2183 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2190 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2184 if not follow:
2191 if not follow:
2185 if rev > maxrev:
2192 if rev > maxrev:
2186 continue
2193 continue
2187 else:
2194 else:
2188 # Note that last might not be the first interesting
2195 # Note that last might not be the first interesting
2189 # rev to us:
2196 # rev to us:
2190 # if the file has been changed after maxrev, we'll
2197 # if the file has been changed after maxrev, we'll
2191 # have linkrev(last) > maxrev, and we still need
2198 # have linkrev(last) > maxrev, and we still need
2192 # to explore the file graph
2199 # to explore the file graph
2193 if rev not in ancestors:
2200 if rev not in ancestors:
2194 continue
2201 continue
2195 # XXX insert 1327 fix here
2202 # XXX insert 1327 fix here
2196 if flparentlinkrevs:
2203 if flparentlinkrevs:
2197 ancestors.update(flparentlinkrevs)
2204 ancestors.update(flparentlinkrevs)
2198
2205
2199 fncache.setdefault(rev, []).append(file_)
2206 fncache.setdefault(rev, []).append(file_)
2200 wanted.add(rev)
2207 wanted.add(rev)
2201 if copied:
2208 if copied:
2202 copies.append(copied)
2209 copies.append(copied)
2203
2210
2204 return wanted
2211 return wanted
2205
2212
2206
2213
2207 class _followfilter(object):
2214 class _followfilter(object):
2208 def __init__(self, repo, onlyfirst=False):
2215 def __init__(self, repo, onlyfirst=False):
2209 self.repo = repo
2216 self.repo = repo
2210 self.startrev = nullrev
2217 self.startrev = nullrev
2211 self.roots = set()
2218 self.roots = set()
2212 self.onlyfirst = onlyfirst
2219 self.onlyfirst = onlyfirst
2213
2220
2214 def match(self, rev):
2221 def match(self, rev):
2215 def realparents(rev):
2222 def realparents(rev):
2216 if self.onlyfirst:
2223 if self.onlyfirst:
2217 return self.repo.changelog.parentrevs(rev)[0:1]
2224 return self.repo.changelog.parentrevs(rev)[0:1]
2218 else:
2225 else:
2219 return filter(
2226 return filter(
2220 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2227 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2221 )
2228 )
2222
2229
2223 if self.startrev == nullrev:
2230 if self.startrev == nullrev:
2224 self.startrev = rev
2231 self.startrev = rev
2225 return True
2232 return True
2226
2233
2227 if rev > self.startrev:
2234 if rev > self.startrev:
2228 # forward: all descendants
2235 # forward: all descendants
2229 if not self.roots:
2236 if not self.roots:
2230 self.roots.add(self.startrev)
2237 self.roots.add(self.startrev)
2231 for parent in realparents(rev):
2238 for parent in realparents(rev):
2232 if parent in self.roots:
2239 if parent in self.roots:
2233 self.roots.add(rev)
2240 self.roots.add(rev)
2234 return True
2241 return True
2235 else:
2242 else:
2236 # backwards: all parents
2243 # backwards: all parents
2237 if not self.roots:
2244 if not self.roots:
2238 self.roots.update(realparents(self.startrev))
2245 self.roots.update(realparents(self.startrev))
2239 if rev in self.roots:
2246 if rev in self.roots:
2240 self.roots.remove(rev)
2247 self.roots.remove(rev)
2241 self.roots.update(realparents(rev))
2248 self.roots.update(realparents(rev))
2242 return True
2249 return True
2243
2250
2244 return False
2251 return False
2245
2252
2246
2253
2247 def walkchangerevs(repo, match, opts, prepare):
2254 def walkchangerevs(repo, match, opts, prepare):
2248 '''Iterate over files and the revs in which they changed.
2255 '''Iterate over files and the revs in which they changed.
2249
2256
2250 Callers most commonly need to iterate backwards over the history
2257 Callers most commonly need to iterate backwards over the history
2251 in which they are interested. Doing so has awful (quadratic-looking)
2258 in which they are interested. Doing so has awful (quadratic-looking)
2252 performance, so we use iterators in a "windowed" way.
2259 performance, so we use iterators in a "windowed" way.
2253
2260
2254 We walk a window of revisions in the desired order. Within the
2261 We walk a window of revisions in the desired order. Within the
2255 window, we first walk forwards to gather data, then in the desired
2262 window, we first walk forwards to gather data, then in the desired
2256 order (usually backwards) to display it.
2263 order (usually backwards) to display it.
2257
2264
2258 This function returns an iterator yielding contexts. Before
2265 This function returns an iterator yielding contexts. Before
2259 yielding each context, the iterator will first call the prepare
2266 yielding each context, the iterator will first call the prepare
2260 function on each context in the window in forward order.'''
2267 function on each context in the window in forward order.'''
2261
2268
2262 allfiles = opts.get(b'all_files')
2269 allfiles = opts.get(b'all_files')
2263 follow = opts.get(b'follow') or opts.get(b'follow_first')
2270 follow = opts.get(b'follow') or opts.get(b'follow_first')
2264 revs = _walkrevs(repo, opts)
2271 revs = _walkrevs(repo, opts)
2265 if not revs:
2272 if not revs:
2266 return []
2273 return []
2267 wanted = set()
2274 wanted = set()
2268 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2275 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2269 fncache = {}
2276 fncache = {}
2270 change = repo.__getitem__
2277 change = repo.__getitem__
2271
2278
2272 # First step is to fill wanted, the set of revisions that we want to yield.
2279 # First step is to fill wanted, the set of revisions that we want to yield.
2273 # When it does not induce extra cost, we also fill fncache for revisions in
2280 # When it does not induce extra cost, we also fill fncache for revisions in
2274 # wanted: a cache of filenames that were changed (ctx.files()) and that
2281 # wanted: a cache of filenames that were changed (ctx.files()) and that
2275 # match the file filtering conditions.
2282 # match the file filtering conditions.
2276
2283
2277 if match.always() or allfiles:
2284 if match.always() or allfiles:
2278 # No files, no patterns. Display all revs.
2285 # No files, no patterns. Display all revs.
2279 wanted = revs
2286 wanted = revs
2280 elif not slowpath:
2287 elif not slowpath:
2281 # We only have to read through the filelog to find wanted revisions
2288 # We only have to read through the filelog to find wanted revisions
2282
2289
2283 try:
2290 try:
2284 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2291 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2285 except FileWalkError:
2292 except FileWalkError:
2286 slowpath = True
2293 slowpath = True
2287
2294
2288 # We decided to fall back to the slowpath because at least one
2295 # We decided to fall back to the slowpath because at least one
2289 # of the paths was not a file. Check to see if at least one of them
2296 # of the paths was not a file. Check to see if at least one of them
2290 # existed in history, otherwise simply return
2297 # existed in history, otherwise simply return
2291 for path in match.files():
2298 for path in match.files():
2292 if path == b'.' or path in repo.store:
2299 if path == b'.' or path in repo.store:
2293 break
2300 break
2294 else:
2301 else:
2295 return []
2302 return []
2296
2303
2297 if slowpath:
2304 if slowpath:
2298 # We have to read the changelog to match filenames against
2305 # We have to read the changelog to match filenames against
2299 # changed files
2306 # changed files
2300
2307
2301 if follow:
2308 if follow:
2302 raise error.Abort(
2309 raise error.Abort(
2303 _(b'can only follow copies/renames for explicit filenames')
2310 _(b'can only follow copies/renames for explicit filenames')
2304 )
2311 )
2305
2312
2306 # The slow path checks files modified in every changeset.
2313 # The slow path checks files modified in every changeset.
2307 # This is really slow on large repos, so compute the set lazily.
2314 # This is really slow on large repos, so compute the set lazily.
2308 class lazywantedset(object):
2315 class lazywantedset(object):
2309 def __init__(self):
2316 def __init__(self):
2310 self.set = set()
2317 self.set = set()
2311 self.revs = set(revs)
2318 self.revs = set(revs)
2312
2319
2313 # No need to worry about locality here because it will be accessed
2320 # No need to worry about locality here because it will be accessed
2314 # in the same order as the increasing window below.
2321 # in the same order as the increasing window below.
2315 def __contains__(self, value):
2322 def __contains__(self, value):
2316 if value in self.set:
2323 if value in self.set:
2317 return True
2324 return True
2318 elif not value in self.revs:
2325 elif not value in self.revs:
2319 return False
2326 return False
2320 else:
2327 else:
2321 self.revs.discard(value)
2328 self.revs.discard(value)
2322 ctx = change(value)
2329 ctx = change(value)
2323 if allfiles:
2330 if allfiles:
2324 matches = list(ctx.manifest().walk(match))
2331 matches = list(ctx.manifest().walk(match))
2325 else:
2332 else:
2326 matches = [f for f in ctx.files() if match(f)]
2333 matches = [f for f in ctx.files() if match(f)]
2327 if matches:
2334 if matches:
2328 fncache[value] = matches
2335 fncache[value] = matches
2329 self.set.add(value)
2336 self.set.add(value)
2330 return True
2337 return True
2331 return False
2338 return False
2332
2339
2333 def discard(self, value):
2340 def discard(self, value):
2334 self.revs.discard(value)
2341 self.revs.discard(value)
2335 self.set.discard(value)
2342 self.set.discard(value)
2336
2343
2337 wanted = lazywantedset()
2344 wanted = lazywantedset()
2338
2345
2339 # it might be worthwhile to do this in the iterator if the rev range
2346 # it might be worthwhile to do this in the iterator if the rev range
2340 # is descending and the prune args are all within that range
2347 # is descending and the prune args are all within that range
2341 for rev in opts.get(b'prune', ()):
2348 for rev in opts.get(b'prune', ()):
2342 rev = repo[rev].rev()
2349 rev = repo[rev].rev()
2343 ff = _followfilter(repo)
2350 ff = _followfilter(repo)
2344 stop = min(revs[0], revs[-1])
2351 stop = min(revs[0], revs[-1])
2345 for x in pycompat.xrange(rev, stop - 1, -1):
2352 for x in pycompat.xrange(rev, stop - 1, -1):
2346 if ff.match(x):
2353 if ff.match(x):
2347 wanted = wanted - [x]
2354 wanted = wanted - [x]
2348
2355
2349 # Now that wanted is correctly initialized, we can iterate over the
2356 # Now that wanted is correctly initialized, we can iterate over the
2350 # revision range, yielding only revisions in wanted.
2357 # revision range, yielding only revisions in wanted.
2351 def iterate():
2358 def iterate():
2352 if follow and match.always():
2359 if follow and match.always():
2353 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2360 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2354
2361
2355 def want(rev):
2362 def want(rev):
2356 return ff.match(rev) and rev in wanted
2363 return ff.match(rev) and rev in wanted
2357
2364
2358 else:
2365 else:
2359
2366
2360 def want(rev):
2367 def want(rev):
2361 return rev in wanted
2368 return rev in wanted
2362
2369
2363 it = iter(revs)
2370 it = iter(revs)
2364 stopiteration = False
2371 stopiteration = False
2365 for windowsize in increasingwindows():
2372 for windowsize in increasingwindows():
2366 nrevs = []
2373 nrevs = []
2367 for i in pycompat.xrange(windowsize):
2374 for i in pycompat.xrange(windowsize):
2368 rev = next(it, None)
2375 rev = next(it, None)
2369 if rev is None:
2376 if rev is None:
2370 stopiteration = True
2377 stopiteration = True
2371 break
2378 break
2372 elif want(rev):
2379 elif want(rev):
2373 nrevs.append(rev)
2380 nrevs.append(rev)
2374 for rev in sorted(nrevs):
2381 for rev in sorted(nrevs):
2375 fns = fncache.get(rev)
2382 fns = fncache.get(rev)
2376 ctx = change(rev)
2383 ctx = change(rev)
2377 if not fns:
2384 if not fns:
2378
2385
2379 def fns_generator():
2386 def fns_generator():
2380 if allfiles:
2387 if allfiles:
2381 fiter = iter(ctx)
2388 fiter = iter(ctx)
2382 else:
2389 else:
2383 fiter = ctx.files()
2390 fiter = ctx.files()
2384 for f in fiter:
2391 for f in fiter:
2385 if match(f):
2392 if match(f):
2386 yield f
2393 yield f
2387
2394
2388 fns = fns_generator()
2395 fns = fns_generator()
2389 prepare(ctx, fns)
2396 prepare(ctx, fns)
2390 for rev in nrevs:
2397 for rev in nrevs:
2391 yield change(rev)
2398 yield change(rev)
2392
2399
2393 if stopiteration:
2400 if stopiteration:
2394 break
2401 break
2395
2402
2396 return iterate()
2403 return iterate()
2397
2404
2398
2405
2399 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2406 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2400 bad = []
2407 bad = []
2401
2408
2402 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2409 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2403 names = []
2410 names = []
2404 wctx = repo[None]
2411 wctx = repo[None]
2405 cca = None
2412 cca = None
2406 abort, warn = scmutil.checkportabilityalert(ui)
2413 abort, warn = scmutil.checkportabilityalert(ui)
2407 if abort or warn:
2414 if abort or warn:
2408 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2415 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2409
2416
2410 match = repo.narrowmatch(match, includeexact=True)
2417 match = repo.narrowmatch(match, includeexact=True)
2411 badmatch = matchmod.badmatch(match, badfn)
2418 badmatch = matchmod.badmatch(match, badfn)
2412 dirstate = repo.dirstate
2419 dirstate = repo.dirstate
2413 # We don't want to just call wctx.walk here, since it would return a lot of
2420 # We don't want to just call wctx.walk here, since it would return a lot of
2414 # clean files, which we aren't interested in and takes time.
2421 # clean files, which we aren't interested in and takes time.
2415 for f in sorted(
2422 for f in sorted(
2416 dirstate.walk(
2423 dirstate.walk(
2417 badmatch,
2424 badmatch,
2418 subrepos=sorted(wctx.substate),
2425 subrepos=sorted(wctx.substate),
2419 unknown=True,
2426 unknown=True,
2420 ignored=False,
2427 ignored=False,
2421 full=False,
2428 full=False,
2422 )
2429 )
2423 ):
2430 ):
2424 exact = match.exact(f)
2431 exact = match.exact(f)
2425 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2432 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2426 if cca:
2433 if cca:
2427 cca(f)
2434 cca(f)
2428 names.append(f)
2435 names.append(f)
2429 if ui.verbose or not exact:
2436 if ui.verbose or not exact:
2430 ui.status(
2437 ui.status(
2431 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2438 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2432 )
2439 )
2433
2440
2434 for subpath in sorted(wctx.substate):
2441 for subpath in sorted(wctx.substate):
2435 sub = wctx.sub(subpath)
2442 sub = wctx.sub(subpath)
2436 try:
2443 try:
2437 submatch = matchmod.subdirmatcher(subpath, match)
2444 submatch = matchmod.subdirmatcher(subpath, match)
2438 subprefix = repo.wvfs.reljoin(prefix, subpath)
2445 subprefix = repo.wvfs.reljoin(prefix, subpath)
2439 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2446 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2440 if opts.get('subrepos'):
2447 if opts.get('subrepos'):
2441 bad.extend(
2448 bad.extend(
2442 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2449 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2443 )
2450 )
2444 else:
2451 else:
2445 bad.extend(
2452 bad.extend(
2446 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2453 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2447 )
2454 )
2448 except error.LookupError:
2455 except error.LookupError:
2449 ui.status(
2456 ui.status(
2450 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2457 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2451 )
2458 )
2452
2459
2453 if not opts.get('dry_run'):
2460 if not opts.get('dry_run'):
2454 rejected = wctx.add(names, prefix)
2461 rejected = wctx.add(names, prefix)
2455 bad.extend(f for f in rejected if f in match.files())
2462 bad.extend(f for f in rejected if f in match.files())
2456 return bad
2463 return bad
2457
2464
2458
2465
2459 def addwebdirpath(repo, serverpath, webconf):
2466 def addwebdirpath(repo, serverpath, webconf):
2460 webconf[serverpath] = repo.root
2467 webconf[serverpath] = repo.root
2461 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2468 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2462
2469
2463 for r in repo.revs(b'filelog("path:.hgsub")'):
2470 for r in repo.revs(b'filelog("path:.hgsub")'):
2464 ctx = repo[r]
2471 ctx = repo[r]
2465 for subpath in ctx.substate:
2472 for subpath in ctx.substate:
2466 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2473 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2467
2474
2468
2475
2469 def forget(
2476 def forget(
2470 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2477 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2471 ):
2478 ):
2472 if dryrun and interactive:
2479 if dryrun and interactive:
2473 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2480 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2474 bad = []
2481 bad = []
2475 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2482 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2476 wctx = repo[None]
2483 wctx = repo[None]
2477 forgot = []
2484 forgot = []
2478
2485
2479 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2486 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2480 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2487 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2481 if explicitonly:
2488 if explicitonly:
2482 forget = [f for f in forget if match.exact(f)]
2489 forget = [f for f in forget if match.exact(f)]
2483
2490
2484 for subpath in sorted(wctx.substate):
2491 for subpath in sorted(wctx.substate):
2485 sub = wctx.sub(subpath)
2492 sub = wctx.sub(subpath)
2486 submatch = matchmod.subdirmatcher(subpath, match)
2493 submatch = matchmod.subdirmatcher(subpath, match)
2487 subprefix = repo.wvfs.reljoin(prefix, subpath)
2494 subprefix = repo.wvfs.reljoin(prefix, subpath)
2488 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2495 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2489 try:
2496 try:
2490 subbad, subforgot = sub.forget(
2497 subbad, subforgot = sub.forget(
2491 submatch,
2498 submatch,
2492 subprefix,
2499 subprefix,
2493 subuipathfn,
2500 subuipathfn,
2494 dryrun=dryrun,
2501 dryrun=dryrun,
2495 interactive=interactive,
2502 interactive=interactive,
2496 )
2503 )
2497 bad.extend([subpath + b'/' + f for f in subbad])
2504 bad.extend([subpath + b'/' + f for f in subbad])
2498 forgot.extend([subpath + b'/' + f for f in subforgot])
2505 forgot.extend([subpath + b'/' + f for f in subforgot])
2499 except error.LookupError:
2506 except error.LookupError:
2500 ui.status(
2507 ui.status(
2501 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2508 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2502 )
2509 )
2503
2510
2504 if not explicitonly:
2511 if not explicitonly:
2505 for f in match.files():
2512 for f in match.files():
2506 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2513 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2507 if f not in forgot:
2514 if f not in forgot:
2508 if repo.wvfs.exists(f):
2515 if repo.wvfs.exists(f):
2509 # Don't complain if the exact case match wasn't given.
2516 # Don't complain if the exact case match wasn't given.
2510 # But don't do this until after checking 'forgot', so
2517 # But don't do this until after checking 'forgot', so
2511 # that subrepo files aren't normalized, and this op is
2518 # that subrepo files aren't normalized, and this op is
2512 # purely from data cached by the status walk above.
2519 # purely from data cached by the status walk above.
2513 if repo.dirstate.normalize(f) in repo.dirstate:
2520 if repo.dirstate.normalize(f) in repo.dirstate:
2514 continue
2521 continue
2515 ui.warn(
2522 ui.warn(
2516 _(
2523 _(
2517 b'not removing %s: '
2524 b'not removing %s: '
2518 b'file is already untracked\n'
2525 b'file is already untracked\n'
2519 )
2526 )
2520 % uipathfn(f)
2527 % uipathfn(f)
2521 )
2528 )
2522 bad.append(f)
2529 bad.append(f)
2523
2530
2524 if interactive:
2531 if interactive:
2525 responses = _(
2532 responses = _(
2526 b'[Ynsa?]'
2533 b'[Ynsa?]'
2527 b'$$ &Yes, forget this file'
2534 b'$$ &Yes, forget this file'
2528 b'$$ &No, skip this file'
2535 b'$$ &No, skip this file'
2529 b'$$ &Skip remaining files'
2536 b'$$ &Skip remaining files'
2530 b'$$ Include &all remaining files'
2537 b'$$ Include &all remaining files'
2531 b'$$ &? (display help)'
2538 b'$$ &? (display help)'
2532 )
2539 )
2533 for filename in forget[:]:
2540 for filename in forget[:]:
2534 r = ui.promptchoice(
2541 r = ui.promptchoice(
2535 _(b'forget %s %s') % (uipathfn(filename), responses)
2542 _(b'forget %s %s') % (uipathfn(filename), responses)
2536 )
2543 )
2537 if r == 4: # ?
2544 if r == 4: # ?
2538 while r == 4:
2545 while r == 4:
2539 for c, t in ui.extractchoices(responses)[1]:
2546 for c, t in ui.extractchoices(responses)[1]:
2540 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2547 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2541 r = ui.promptchoice(
2548 r = ui.promptchoice(
2542 _(b'forget %s %s') % (uipathfn(filename), responses)
2549 _(b'forget %s %s') % (uipathfn(filename), responses)
2543 )
2550 )
2544 if r == 0: # yes
2551 if r == 0: # yes
2545 continue
2552 continue
2546 elif r == 1: # no
2553 elif r == 1: # no
2547 forget.remove(filename)
2554 forget.remove(filename)
2548 elif r == 2: # Skip
2555 elif r == 2: # Skip
2549 fnindex = forget.index(filename)
2556 fnindex = forget.index(filename)
2550 del forget[fnindex:]
2557 del forget[fnindex:]
2551 break
2558 break
2552 elif r == 3: # All
2559 elif r == 3: # All
2553 break
2560 break
2554
2561
2555 for f in forget:
2562 for f in forget:
2556 if ui.verbose or not match.exact(f) or interactive:
2563 if ui.verbose or not match.exact(f) or interactive:
2557 ui.status(
2564 ui.status(
2558 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2565 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2559 )
2566 )
2560
2567
2561 if not dryrun:
2568 if not dryrun:
2562 rejected = wctx.forget(forget, prefix)
2569 rejected = wctx.forget(forget, prefix)
2563 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())
2564 forgot.extend(f for f in forget if f not in rejected)
2571 forgot.extend(f for f in forget if f not in rejected)
2565 return bad, forgot
2572 return bad, forgot
2566
2573
2567
2574
2568 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2575 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2569 ret = 1
2576 ret = 1
2570
2577
2571 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2578 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2572 for f in ctx.matches(m):
2579 for f in ctx.matches(m):
2573 fm.startitem()
2580 fm.startitem()
2574 fm.context(ctx=ctx)
2581 fm.context(ctx=ctx)
2575 if needsfctx:
2582 if needsfctx:
2576 fc = ctx[f]
2583 fc = ctx[f]
2577 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2584 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2578 fm.data(path=f)
2585 fm.data(path=f)
2579 fm.plain(fmt % uipathfn(f))
2586 fm.plain(fmt % uipathfn(f))
2580 ret = 0
2587 ret = 0
2581
2588
2582 for subpath in sorted(ctx.substate):
2589 for subpath in sorted(ctx.substate):
2583 submatch = matchmod.subdirmatcher(subpath, m)
2590 submatch = matchmod.subdirmatcher(subpath, m)
2584 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2591 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2585 if subrepos or m.exact(subpath) or any(submatch.files()):
2592 if subrepos or m.exact(subpath) or any(submatch.files()):
2586 sub = ctx.sub(subpath)
2593 sub = ctx.sub(subpath)
2587 try:
2594 try:
2588 recurse = m.exact(subpath) or subrepos
2595 recurse = m.exact(subpath) or subrepos
2589 if (
2596 if (
2590 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2597 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2591 == 0
2598 == 0
2592 ):
2599 ):
2593 ret = 0
2600 ret = 0
2594 except error.LookupError:
2601 except error.LookupError:
2595 ui.status(
2602 ui.status(
2596 _(b"skipping missing subrepository: %s\n")
2603 _(b"skipping missing subrepository: %s\n")
2597 % uipathfn(subpath)
2604 % uipathfn(subpath)
2598 )
2605 )
2599
2606
2600 return ret
2607 return ret
2601
2608
2602
2609
2603 def remove(
2610 def remove(
2604 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2611 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2605 ):
2612 ):
2606 ret = 0
2613 ret = 0
2607 s = repo.status(match=m, clean=True)
2614 s = repo.status(match=m, clean=True)
2608 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2615 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2609
2616
2610 wctx = repo[None]
2617 wctx = repo[None]
2611
2618
2612 if warnings is None:
2619 if warnings is None:
2613 warnings = []
2620 warnings = []
2614 warn = True
2621 warn = True
2615 else:
2622 else:
2616 warn = False
2623 warn = False
2617
2624
2618 subs = sorted(wctx.substate)
2625 subs = sorted(wctx.substate)
2619 progress = ui.makeprogress(
2626 progress = ui.makeprogress(
2620 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2627 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2621 )
2628 )
2622 for subpath in subs:
2629 for subpath in subs:
2623 submatch = matchmod.subdirmatcher(subpath, m)
2630 submatch = matchmod.subdirmatcher(subpath, m)
2624 subprefix = repo.wvfs.reljoin(prefix, subpath)
2631 subprefix = repo.wvfs.reljoin(prefix, subpath)
2625 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2632 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2626 if subrepos or m.exact(subpath) or any(submatch.files()):
2633 if subrepos or m.exact(subpath) or any(submatch.files()):
2627 progress.increment()
2634 progress.increment()
2628 sub = wctx.sub(subpath)
2635 sub = wctx.sub(subpath)
2629 try:
2636 try:
2630 if sub.removefiles(
2637 if sub.removefiles(
2631 submatch,
2638 submatch,
2632 subprefix,
2639 subprefix,
2633 subuipathfn,
2640 subuipathfn,
2634 after,
2641 after,
2635 force,
2642 force,
2636 subrepos,
2643 subrepos,
2637 dryrun,
2644 dryrun,
2638 warnings,
2645 warnings,
2639 ):
2646 ):
2640 ret = 1
2647 ret = 1
2641 except error.LookupError:
2648 except error.LookupError:
2642 warnings.append(
2649 warnings.append(
2643 _(b"skipping missing subrepository: %s\n")
2650 _(b"skipping missing subrepository: %s\n")
2644 % uipathfn(subpath)
2651 % uipathfn(subpath)
2645 )
2652 )
2646 progress.complete()
2653 progress.complete()
2647
2654
2648 # warn about failure to delete explicit files/dirs
2655 # warn about failure to delete explicit files/dirs
2649 deleteddirs = pathutil.dirs(deleted)
2656 deleteddirs = pathutil.dirs(deleted)
2650 files = m.files()
2657 files = m.files()
2651 progress = ui.makeprogress(
2658 progress = ui.makeprogress(
2652 _(b'deleting'), total=len(files), unit=_(b'files')
2659 _(b'deleting'), total=len(files), unit=_(b'files')
2653 )
2660 )
2654 for f in files:
2661 for f in files:
2655
2662
2656 def insubrepo():
2663 def insubrepo():
2657 for subpath in wctx.substate:
2664 for subpath in wctx.substate:
2658 if f.startswith(subpath + b'/'):
2665 if f.startswith(subpath + b'/'):
2659 return True
2666 return True
2660 return False
2667 return False
2661
2668
2662 progress.increment()
2669 progress.increment()
2663 isdir = f in deleteddirs or wctx.hasdir(f)
2670 isdir = f in deleteddirs or wctx.hasdir(f)
2664 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2671 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2665 continue
2672 continue
2666
2673
2667 if repo.wvfs.exists(f):
2674 if repo.wvfs.exists(f):
2668 if repo.wvfs.isdir(f):
2675 if repo.wvfs.isdir(f):
2669 warnings.append(
2676 warnings.append(
2670 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2677 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2671 )
2678 )
2672 else:
2679 else:
2673 warnings.append(
2680 warnings.append(
2674 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2681 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2675 )
2682 )
2676 # missing files will generate a warning elsewhere
2683 # missing files will generate a warning elsewhere
2677 ret = 1
2684 ret = 1
2678 progress.complete()
2685 progress.complete()
2679
2686
2680 if force:
2687 if force:
2681 list = modified + deleted + clean + added
2688 list = modified + deleted + clean + added
2682 elif after:
2689 elif after:
2683 list = deleted
2690 list = deleted
2684 remaining = modified + added + clean
2691 remaining = modified + added + clean
2685 progress = ui.makeprogress(
2692 progress = ui.makeprogress(
2686 _(b'skipping'), total=len(remaining), unit=_(b'files')
2693 _(b'skipping'), total=len(remaining), unit=_(b'files')
2687 )
2694 )
2688 for f in remaining:
2695 for f in remaining:
2689 progress.increment()
2696 progress.increment()
2690 if ui.verbose or (f in files):
2697 if ui.verbose or (f in files):
2691 warnings.append(
2698 warnings.append(
2692 _(b'not removing %s: file still exists\n') % uipathfn(f)
2699 _(b'not removing %s: file still exists\n') % uipathfn(f)
2693 )
2700 )
2694 ret = 1
2701 ret = 1
2695 progress.complete()
2702 progress.complete()
2696 else:
2703 else:
2697 list = deleted + clean
2704 list = deleted + clean
2698 progress = ui.makeprogress(
2705 progress = ui.makeprogress(
2699 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2706 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2700 )
2707 )
2701 for f in modified:
2708 for f in modified:
2702 progress.increment()
2709 progress.increment()
2703 warnings.append(
2710 warnings.append(
2704 _(
2711 _(
2705 b'not removing %s: file is modified (use -f'
2712 b'not removing %s: file is modified (use -f'
2706 b' to force removal)\n'
2713 b' to force removal)\n'
2707 )
2714 )
2708 % uipathfn(f)
2715 % uipathfn(f)
2709 )
2716 )
2710 ret = 1
2717 ret = 1
2711 for f in added:
2718 for f in added:
2712 progress.increment()
2719 progress.increment()
2713 warnings.append(
2720 warnings.append(
2714 _(
2721 _(
2715 b"not removing %s: file has been marked for add"
2722 b"not removing %s: file has been marked for add"
2716 b" (use 'hg forget' to undo add)\n"
2723 b" (use 'hg forget' to undo add)\n"
2717 )
2724 )
2718 % uipathfn(f)
2725 % uipathfn(f)
2719 )
2726 )
2720 ret = 1
2727 ret = 1
2721 progress.complete()
2728 progress.complete()
2722
2729
2723 list = sorted(list)
2730 list = sorted(list)
2724 progress = ui.makeprogress(
2731 progress = ui.makeprogress(
2725 _(b'deleting'), total=len(list), unit=_(b'files')
2732 _(b'deleting'), total=len(list), unit=_(b'files')
2726 )
2733 )
2727 for f in list:
2734 for f in list:
2728 if ui.verbose or not m.exact(f):
2735 if ui.verbose or not m.exact(f):
2729 progress.increment()
2736 progress.increment()
2730 ui.status(
2737 ui.status(
2731 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2738 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2732 )
2739 )
2733 progress.complete()
2740 progress.complete()
2734
2741
2735 if not dryrun:
2742 if not dryrun:
2736 with repo.wlock():
2743 with repo.wlock():
2737 if not after:
2744 if not after:
2738 for f in list:
2745 for f in list:
2739 if f in added:
2746 if f in added:
2740 continue # we never unlink added files on remove
2747 continue # we never unlink added files on remove
2741 rmdir = repo.ui.configbool(
2748 rmdir = repo.ui.configbool(
2742 b'experimental', b'removeemptydirs'
2749 b'experimental', b'removeemptydirs'
2743 )
2750 )
2744 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2751 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2745 repo[None].forget(list)
2752 repo[None].forget(list)
2746
2753
2747 if warn:
2754 if warn:
2748 for warning in warnings:
2755 for warning in warnings:
2749 ui.warn(warning)
2756 ui.warn(warning)
2750
2757
2751 return ret
2758 return ret
2752
2759
2753
2760
2754 def _catfmtneedsdata(fm):
2761 def _catfmtneedsdata(fm):
2755 return not fm.datahint() or b'data' in fm.datahint()
2762 return not fm.datahint() or b'data' in fm.datahint()
2756
2763
2757
2764
2758 def _updatecatformatter(fm, ctx, matcher, path, decode):
2765 def _updatecatformatter(fm, ctx, matcher, path, decode):
2759 """Hook for adding data to the formatter used by ``hg cat``.
2766 """Hook for adding data to the formatter used by ``hg cat``.
2760
2767
2761 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2768 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2762 this method first."""
2769 this method first."""
2763
2770
2764 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2771 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2765 # wasn't requested.
2772 # wasn't requested.
2766 data = b''
2773 data = b''
2767 if _catfmtneedsdata(fm):
2774 if _catfmtneedsdata(fm):
2768 data = ctx[path].data()
2775 data = ctx[path].data()
2769 if decode:
2776 if decode:
2770 data = ctx.repo().wwritedata(path, data)
2777 data = ctx.repo().wwritedata(path, data)
2771 fm.startitem()
2778 fm.startitem()
2772 fm.context(ctx=ctx)
2779 fm.context(ctx=ctx)
2773 fm.write(b'data', b'%s', data)
2780 fm.write(b'data', b'%s', data)
2774 fm.data(path=path)
2781 fm.data(path=path)
2775
2782
2776
2783
2777 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2784 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2778 err = 1
2785 err = 1
2779 opts = pycompat.byteskwargs(opts)
2786 opts = pycompat.byteskwargs(opts)
2780
2787
2781 def write(path):
2788 def write(path):
2782 filename = None
2789 filename = None
2783 if fntemplate:
2790 if fntemplate:
2784 filename = makefilename(
2791 filename = makefilename(
2785 ctx, fntemplate, pathname=os.path.join(prefix, path)
2792 ctx, fntemplate, pathname=os.path.join(prefix, path)
2786 )
2793 )
2787 # attempt to create the directory if it does not already exist
2794 # attempt to create the directory if it does not already exist
2788 try:
2795 try:
2789 os.makedirs(os.path.dirname(filename))
2796 os.makedirs(os.path.dirname(filename))
2790 except OSError:
2797 except OSError:
2791 pass
2798 pass
2792 with formatter.maybereopen(basefm, filename) as fm:
2799 with formatter.maybereopen(basefm, filename) as fm:
2793 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2800 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2794
2801
2795 # Automation often uses hg cat on single files, so special case it
2802 # Automation often uses hg cat on single files, so special case it
2796 # for performance to avoid the cost of parsing the manifest.
2803 # for performance to avoid the cost of parsing the manifest.
2797 if len(matcher.files()) == 1 and not matcher.anypats():
2804 if len(matcher.files()) == 1 and not matcher.anypats():
2798 file = matcher.files()[0]
2805 file = matcher.files()[0]
2799 mfl = repo.manifestlog
2806 mfl = repo.manifestlog
2800 mfnode = ctx.manifestnode()
2807 mfnode = ctx.manifestnode()
2801 try:
2808 try:
2802 if mfnode and mfl[mfnode].find(file)[0]:
2809 if mfnode and mfl[mfnode].find(file)[0]:
2803 if _catfmtneedsdata(basefm):
2810 if _catfmtneedsdata(basefm):
2804 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2811 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2805 write(file)
2812 write(file)
2806 return 0
2813 return 0
2807 except KeyError:
2814 except KeyError:
2808 pass
2815 pass
2809
2816
2810 if _catfmtneedsdata(basefm):
2817 if _catfmtneedsdata(basefm):
2811 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2818 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2812
2819
2813 for abs in ctx.walk(matcher):
2820 for abs in ctx.walk(matcher):
2814 write(abs)
2821 write(abs)
2815 err = 0
2822 err = 0
2816
2823
2817 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2824 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2818 for subpath in sorted(ctx.substate):
2825 for subpath in sorted(ctx.substate):
2819 sub = ctx.sub(subpath)
2826 sub = ctx.sub(subpath)
2820 try:
2827 try:
2821 submatch = matchmod.subdirmatcher(subpath, matcher)
2828 submatch = matchmod.subdirmatcher(subpath, matcher)
2822 subprefix = os.path.join(prefix, subpath)
2829 subprefix = os.path.join(prefix, subpath)
2823 if not sub.cat(
2830 if not sub.cat(
2824 submatch,
2831 submatch,
2825 basefm,
2832 basefm,
2826 fntemplate,
2833 fntemplate,
2827 subprefix,
2834 subprefix,
2828 **pycompat.strkwargs(opts)
2835 **pycompat.strkwargs(opts)
2829 ):
2836 ):
2830 err = 0
2837 err = 0
2831 except error.RepoLookupError:
2838 except error.RepoLookupError:
2832 ui.status(
2839 ui.status(
2833 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2840 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2834 )
2841 )
2835
2842
2836 return err
2843 return err
2837
2844
2838
2845
2839 def commit(ui, repo, commitfunc, pats, opts):
2846 def commit(ui, repo, commitfunc, pats, opts):
2840 '''commit the specified files or all outstanding changes'''
2847 '''commit the specified files or all outstanding changes'''
2841 date = opts.get(b'date')
2848 date = opts.get(b'date')
2842 if date:
2849 if date:
2843 opts[b'date'] = dateutil.parsedate(date)
2850 opts[b'date'] = dateutil.parsedate(date)
2844 message = logmessage(ui, opts)
2851 message = logmessage(ui, opts)
2845 matcher = scmutil.match(repo[None], pats, opts)
2852 matcher = scmutil.match(repo[None], pats, opts)
2846
2853
2847 dsguard = None
2854 dsguard = None
2848 # extract addremove carefully -- this function can be called from a command
2855 # extract addremove carefully -- this function can be called from a command
2849 # that doesn't support addremove
2856 # that doesn't support addremove
2850 if opts.get(b'addremove'):
2857 if opts.get(b'addremove'):
2851 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2858 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2852 with dsguard or util.nullcontextmanager():
2859 with dsguard or util.nullcontextmanager():
2853 if dsguard:
2860 if dsguard:
2854 relative = scmutil.anypats(pats, opts)
2861 relative = scmutil.anypats(pats, opts)
2855 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2862 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2856 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2863 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2857 raise error.Abort(
2864 raise error.Abort(
2858 _(b"failed to mark all new/missing files as added/removed")
2865 _(b"failed to mark all new/missing files as added/removed")
2859 )
2866 )
2860
2867
2861 return commitfunc(ui, repo, message, matcher, opts)
2868 return commitfunc(ui, repo, message, matcher, opts)
2862
2869
2863
2870
2864 def samefile(f, ctx1, ctx2):
2871 def samefile(f, ctx1, ctx2):
2865 if f in ctx1.manifest():
2872 if f in ctx1.manifest():
2866 a = ctx1.filectx(f)
2873 a = ctx1.filectx(f)
2867 if f in ctx2.manifest():
2874 if f in ctx2.manifest():
2868 b = ctx2.filectx(f)
2875 b = ctx2.filectx(f)
2869 return not a.cmp(b) and a.flags() == b.flags()
2876 return not a.cmp(b) and a.flags() == b.flags()
2870 else:
2877 else:
2871 return False
2878 return False
2872 else:
2879 else:
2873 return f not in ctx2.manifest()
2880 return f not in ctx2.manifest()
2874
2881
2875
2882
2876 def amend(ui, repo, old, extra, pats, opts):
2883 def amend(ui, repo, old, extra, pats, opts):
2877 # avoid cycle context -> subrepo -> cmdutil
2884 # avoid cycle context -> subrepo -> cmdutil
2878 from . import context
2885 from . import context
2879
2886
2880 # amend will reuse the existing user if not specified, but the obsolete
2887 # amend will reuse the existing user if not specified, but the obsolete
2881 # marker creation requires that the current user's name is specified.
2888 # marker creation requires that the current user's name is specified.
2882 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2889 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2883 ui.username() # raise exception if username not set
2890 ui.username() # raise exception if username not set
2884
2891
2885 ui.note(_(b'amending changeset %s\n') % old)
2892 ui.note(_(b'amending changeset %s\n') % old)
2886 base = old.p1()
2893 base = old.p1()
2887
2894
2888 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2895 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2889 # Participating changesets:
2896 # Participating changesets:
2890 #
2897 #
2891 # wctx o - workingctx that contains changes from working copy
2898 # wctx o - workingctx that contains changes from working copy
2892 # | to go into amending commit
2899 # | to go into amending commit
2893 # |
2900 # |
2894 # old o - changeset to amend
2901 # old o - changeset to amend
2895 # |
2902 # |
2896 # base o - first parent of the changeset to amend
2903 # base o - first parent of the changeset to amend
2897 wctx = repo[None]
2904 wctx = repo[None]
2898
2905
2899 # Copy to avoid mutating input
2906 # Copy to avoid mutating input
2900 extra = extra.copy()
2907 extra = extra.copy()
2901 # Update extra dict from amended commit (e.g. to preserve graft
2908 # Update extra dict from amended commit (e.g. to preserve graft
2902 # source)
2909 # source)
2903 extra.update(old.extra())
2910 extra.update(old.extra())
2904
2911
2905 # Also update it from the from the wctx
2912 # Also update it from the from the wctx
2906 extra.update(wctx.extra())
2913 extra.update(wctx.extra())
2907
2914
2908 # date-only change should be ignored?
2915 # date-only change should be ignored?
2909 datemaydiffer = resolvecommitoptions(ui, opts)
2916 datemaydiffer = resolvecommitoptions(ui, opts)
2910
2917
2911 date = old.date()
2918 date = old.date()
2912 if opts.get(b'date'):
2919 if opts.get(b'date'):
2913 date = dateutil.parsedate(opts.get(b'date'))
2920 date = dateutil.parsedate(opts.get(b'date'))
2914 user = opts.get(b'user') or old.user()
2921 user = opts.get(b'user') or old.user()
2915
2922
2916 if len(old.parents()) > 1:
2923 if len(old.parents()) > 1:
2917 # ctx.files() isn't reliable for merges, so fall back to the
2924 # ctx.files() isn't reliable for merges, so fall back to the
2918 # slower repo.status() method
2925 # slower repo.status() method
2919 st = base.status(old)
2926 st = base.status(old)
2920 files = set(st.modified) | set(st.added) | set(st.removed)
2927 files = set(st.modified) | set(st.added) | set(st.removed)
2921 else:
2928 else:
2922 files = set(old.files())
2929 files = set(old.files())
2923
2930
2924 # add/remove the files to the working copy if the "addremove" option
2931 # add/remove the files to the working copy if the "addremove" option
2925 # was specified.
2932 # was specified.
2926 matcher = scmutil.match(wctx, pats, opts)
2933 matcher = scmutil.match(wctx, pats, opts)
2927 relative = scmutil.anypats(pats, opts)
2934 relative = scmutil.anypats(pats, opts)
2928 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2935 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2929 if opts.get(b'addremove') and scmutil.addremove(
2936 if opts.get(b'addremove') and scmutil.addremove(
2930 repo, matcher, b"", uipathfn, opts
2937 repo, matcher, b"", uipathfn, opts
2931 ):
2938 ):
2932 raise error.Abort(
2939 raise error.Abort(
2933 _(b"failed to mark all new/missing files as added/removed")
2940 _(b"failed to mark all new/missing files as added/removed")
2934 )
2941 )
2935
2942
2936 # Check subrepos. This depends on in-place wctx._status update in
2943 # Check subrepos. This depends on in-place wctx._status update in
2937 # subrepo.precommit(). To minimize the risk of this hack, we do
2944 # subrepo.precommit(). To minimize the risk of this hack, we do
2938 # nothing if .hgsub does not exist.
2945 # nothing if .hgsub does not exist.
2939 if b'.hgsub' in wctx or b'.hgsub' in old:
2946 if b'.hgsub' in wctx or b'.hgsub' in old:
2940 subs, commitsubs, newsubstate = subrepoutil.precommit(
2947 subs, commitsubs, newsubstate = subrepoutil.precommit(
2941 ui, wctx, wctx._status, matcher
2948 ui, wctx, wctx._status, matcher
2942 )
2949 )
2943 # amend should abort if commitsubrepos is enabled
2950 # amend should abort if commitsubrepos is enabled
2944 assert not commitsubs
2951 assert not commitsubs
2945 if subs:
2952 if subs:
2946 subrepoutil.writestate(repo, newsubstate)
2953 subrepoutil.writestate(repo, newsubstate)
2947
2954
2948 ms = mergemod.mergestate.read(repo)
2955 ms = mergemod.mergestate.read(repo)
2949 mergeutil.checkunresolved(ms)
2956 mergeutil.checkunresolved(ms)
2950
2957
2951 filestoamend = set(f for f in wctx.files() if matcher(f))
2958 filestoamend = set(f for f in wctx.files() if matcher(f))
2952
2959
2953 changes = len(filestoamend) > 0
2960 changes = len(filestoamend) > 0
2954 if changes:
2961 if changes:
2955 # Recompute copies (avoid recording a -> b -> a)
2962 # Recompute copies (avoid recording a -> b -> a)
2956 copied = copies.pathcopies(base, wctx, matcher)
2963 copied = copies.pathcopies(base, wctx, matcher)
2957 if old.p2:
2964 if old.p2:
2958 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2965 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2959
2966
2960 # Prune files which were reverted by the updates: if old
2967 # Prune files which were reverted by the updates: if old
2961 # introduced file X and the file was renamed in the working
2968 # introduced file X and the file was renamed in the working
2962 # copy, then those two files are the same and
2969 # copy, then those two files are the same and
2963 # we can discard X from our list of files. Likewise if X
2970 # we can discard X from our list of files. Likewise if X
2964 # was removed, it's no longer relevant. If X is missing (aka
2971 # was removed, it's no longer relevant. If X is missing (aka
2965 # deleted), old X must be preserved.
2972 # deleted), old X must be preserved.
2966 files.update(filestoamend)
2973 files.update(filestoamend)
2967 files = [
2974 files = [
2968 f
2975 f
2969 for f in files
2976 for f in files
2970 if (f not in filestoamend or not samefile(f, wctx, base))
2977 if (f not in filestoamend or not samefile(f, wctx, base))
2971 ]
2978 ]
2972
2979
2973 def filectxfn(repo, ctx_, path):
2980 def filectxfn(repo, ctx_, path):
2974 try:
2981 try:
2975 # If the file being considered is not amongst the files
2982 # If the file being considered is not amongst the files
2976 # to be amended, we should return the file context from the
2983 # to be amended, we should return the file context from the
2977 # old changeset. This avoids issues when only some files in
2984 # old changeset. This avoids issues when only some files in
2978 # the working copy are being amended but there are also
2985 # the working copy are being amended but there are also
2979 # changes to other files from the old changeset.
2986 # changes to other files from the old changeset.
2980 if path not in filestoamend:
2987 if path not in filestoamend:
2981 return old.filectx(path)
2988 return old.filectx(path)
2982
2989
2983 # Return None for removed files.
2990 # Return None for removed files.
2984 if path in wctx.removed():
2991 if path in wctx.removed():
2985 return None
2992 return None
2986
2993
2987 fctx = wctx[path]
2994 fctx = wctx[path]
2988 flags = fctx.flags()
2995 flags = fctx.flags()
2989 mctx = context.memfilectx(
2996 mctx = context.memfilectx(
2990 repo,
2997 repo,
2991 ctx_,
2998 ctx_,
2992 fctx.path(),
2999 fctx.path(),
2993 fctx.data(),
3000 fctx.data(),
2994 islink=b'l' in flags,
3001 islink=b'l' in flags,
2995 isexec=b'x' in flags,
3002 isexec=b'x' in flags,
2996 copysource=copied.get(path),
3003 copysource=copied.get(path),
2997 )
3004 )
2998 return mctx
3005 return mctx
2999 except KeyError:
3006 except KeyError:
3000 return None
3007 return None
3001
3008
3002 else:
3009 else:
3003 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3010 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3004
3011
3005 # Use version of files as in the old cset
3012 # Use version of files as in the old cset
3006 def filectxfn(repo, ctx_, path):
3013 def filectxfn(repo, ctx_, path):
3007 try:
3014 try:
3008 return old.filectx(path)
3015 return old.filectx(path)
3009 except KeyError:
3016 except KeyError:
3010 return None
3017 return None
3011
3018
3012 # See if we got a message from -m or -l, if not, open the editor with
3019 # See if we got a message from -m or -l, if not, open the editor with
3013 # the message of the changeset to amend.
3020 # the message of the changeset to amend.
3014 message = logmessage(ui, opts)
3021 message = logmessage(ui, opts)
3015
3022
3016 editform = mergeeditform(old, b'commit.amend')
3023 editform = mergeeditform(old, b'commit.amend')
3017
3024
3018 if not message:
3025 if not message:
3019 message = old.description()
3026 message = old.description()
3020 # Default if message isn't provided and --edit is not passed is to
3027 # Default if message isn't provided and --edit is not passed is to
3021 # invoke editor, but allow --no-edit. If somehow we don't have any
3028 # invoke editor, but allow --no-edit. If somehow we don't have any
3022 # description, let's always start the editor.
3029 # description, let's always start the editor.
3023 doedit = not message or opts.get(b'edit') in [True, None]
3030 doedit = not message or opts.get(b'edit') in [True, None]
3024 else:
3031 else:
3025 # Default if message is provided is to not invoke editor, but allow
3032 # Default if message is provided is to not invoke editor, but allow
3026 # --edit.
3033 # --edit.
3027 doedit = opts.get(b'edit') is True
3034 doedit = opts.get(b'edit') is True
3028 editor = getcommiteditor(edit=doedit, editform=editform)
3035 editor = getcommiteditor(edit=doedit, editform=editform)
3029
3036
3030 pureextra = extra.copy()
3037 pureextra = extra.copy()
3031 extra[b'amend_source'] = old.hex()
3038 extra[b'amend_source'] = old.hex()
3032
3039
3033 new = context.memctx(
3040 new = context.memctx(
3034 repo,
3041 repo,
3035 parents=[base.node(), old.p2().node()],
3042 parents=[base.node(), old.p2().node()],
3036 text=message,
3043 text=message,
3037 files=files,
3044 files=files,
3038 filectxfn=filectxfn,
3045 filectxfn=filectxfn,
3039 user=user,
3046 user=user,
3040 date=date,
3047 date=date,
3041 extra=extra,
3048 extra=extra,
3042 editor=editor,
3049 editor=editor,
3043 )
3050 )
3044
3051
3045 newdesc = changelog.stripdesc(new.description())
3052 newdesc = changelog.stripdesc(new.description())
3046 if (
3053 if (
3047 (not changes)
3054 (not changes)
3048 and newdesc == old.description()
3055 and newdesc == old.description()
3049 and user == old.user()
3056 and user == old.user()
3050 and (date == old.date() or datemaydiffer)
3057 and (date == old.date() or datemaydiffer)
3051 and pureextra == old.extra()
3058 and pureextra == old.extra()
3052 ):
3059 ):
3053 # nothing changed. continuing here would create a new node
3060 # nothing changed. continuing here would create a new node
3054 # anyway because of the amend_source noise.
3061 # anyway because of the amend_source noise.
3055 #
3062 #
3056 # This not what we expect from amend.
3063 # This not what we expect from amend.
3057 return old.node()
3064 return old.node()
3058
3065
3059 commitphase = None
3066 commitphase = None
3060 if opts.get(b'secret'):
3067 if opts.get(b'secret'):
3061 commitphase = phases.secret
3068 commitphase = phases.secret
3062 newid = repo.commitctx(new)
3069 newid = repo.commitctx(new)
3063
3070
3064 # Reroute the working copy parent to the new changeset
3071 # Reroute the working copy parent to the new changeset
3065 repo.setparents(newid, nullid)
3072 repo.setparents(newid, nullid)
3066 mapping = {old.node(): (newid,)}
3073 mapping = {old.node(): (newid,)}
3067 obsmetadata = None
3074 obsmetadata = None
3068 if opts.get(b'note'):
3075 if opts.get(b'note'):
3069 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3076 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3070 backup = ui.configbool(b'rewrite', b'backup-bundle')
3077 backup = ui.configbool(b'rewrite', b'backup-bundle')
3071 scmutil.cleanupnodes(
3078 scmutil.cleanupnodes(
3072 repo,
3079 repo,
3073 mapping,
3080 mapping,
3074 b'amend',
3081 b'amend',
3075 metadata=obsmetadata,
3082 metadata=obsmetadata,
3076 fixphase=True,
3083 fixphase=True,
3077 targetphase=commitphase,
3084 targetphase=commitphase,
3078 backup=backup,
3085 backup=backup,
3079 )
3086 )
3080
3087
3081 # Fixing the dirstate because localrepo.commitctx does not update
3088 # Fixing the dirstate because localrepo.commitctx does not update
3082 # it. This is rather convenient because we did not need to update
3089 # it. This is rather convenient because we did not need to update
3083 # the dirstate for all the files in the new commit which commitctx
3090 # the dirstate for all the files in the new commit which commitctx
3084 # could have done if it updated the dirstate. Now, we can
3091 # could have done if it updated the dirstate. Now, we can
3085 # selectively update the dirstate only for the amended files.
3092 # selectively update the dirstate only for the amended files.
3086 dirstate = repo.dirstate
3093 dirstate = repo.dirstate
3087
3094
3088 # Update the state of the files which were added and modified in the
3095 # Update the state of the files which were added and modified in the
3089 # amend to "normal" in the dirstate. We need to use "normallookup" since
3096 # amend to "normal" in the dirstate. We need to use "normallookup" since
3090 # the files may have changed since the command started; using "normal"
3097 # the files may have changed since the command started; using "normal"
3091 # would mark them as clean but with uncommitted contents.
3098 # would mark them as clean but with uncommitted contents.
3092 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3099 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3093 for f in normalfiles:
3100 for f in normalfiles:
3094 dirstate.normallookup(f)
3101 dirstate.normallookup(f)
3095
3102
3096 # Update the state of files which were removed in the amend
3103 # Update the state of files which were removed in the amend
3097 # to "removed" in the dirstate.
3104 # to "removed" in the dirstate.
3098 removedfiles = set(wctx.removed()) & filestoamend
3105 removedfiles = set(wctx.removed()) & filestoamend
3099 for f in removedfiles:
3106 for f in removedfiles:
3100 dirstate.drop(f)
3107 dirstate.drop(f)
3101
3108
3102 return newid
3109 return newid
3103
3110
3104
3111
3105 def commiteditor(repo, ctx, subs, editform=b''):
3112 def commiteditor(repo, ctx, subs, editform=b''):
3106 if ctx.description():
3113 if ctx.description():
3107 return ctx.description()
3114 return ctx.description()
3108 return commitforceeditor(
3115 return commitforceeditor(
3109 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3116 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3110 )
3117 )
3111
3118
3112
3119
3113 def commitforceeditor(
3120 def commitforceeditor(
3114 repo,
3121 repo,
3115 ctx,
3122 ctx,
3116 subs,
3123 subs,
3117 finishdesc=None,
3124 finishdesc=None,
3118 extramsg=None,
3125 extramsg=None,
3119 editform=b'',
3126 editform=b'',
3120 unchangedmessagedetection=False,
3127 unchangedmessagedetection=False,
3121 ):
3128 ):
3122 if not extramsg:
3129 if not extramsg:
3123 extramsg = _(b"Leave message empty to abort commit.")
3130 extramsg = _(b"Leave message empty to abort commit.")
3124
3131
3125 forms = [e for e in editform.split(b'.') if e]
3132 forms = [e for e in editform.split(b'.') if e]
3126 forms.insert(0, b'changeset')
3133 forms.insert(0, b'changeset')
3127 templatetext = None
3134 templatetext = None
3128 while forms:
3135 while forms:
3129 ref = b'.'.join(forms)
3136 ref = b'.'.join(forms)
3130 if repo.ui.config(b'committemplate', ref):
3137 if repo.ui.config(b'committemplate', ref):
3131 templatetext = committext = buildcommittemplate(
3138 templatetext = committext = buildcommittemplate(
3132 repo, ctx, subs, extramsg, ref
3139 repo, ctx, subs, extramsg, ref
3133 )
3140 )
3134 break
3141 break
3135 forms.pop()
3142 forms.pop()
3136 else:
3143 else:
3137 committext = buildcommittext(repo, ctx, subs, extramsg)
3144 committext = buildcommittext(repo, ctx, subs, extramsg)
3138
3145
3139 # run editor in the repository root
3146 # run editor in the repository root
3140 olddir = encoding.getcwd()
3147 olddir = encoding.getcwd()
3141 os.chdir(repo.root)
3148 os.chdir(repo.root)
3142
3149
3143 # make in-memory changes visible to external process
3150 # make in-memory changes visible to external process
3144 tr = repo.currenttransaction()
3151 tr = repo.currenttransaction()
3145 repo.dirstate.write(tr)
3152 repo.dirstate.write(tr)
3146 pending = tr and tr.writepending() and repo.root
3153 pending = tr and tr.writepending() and repo.root
3147
3154
3148 editortext = repo.ui.edit(
3155 editortext = repo.ui.edit(
3149 committext,
3156 committext,
3150 ctx.user(),
3157 ctx.user(),
3151 ctx.extra(),
3158 ctx.extra(),
3152 editform=editform,
3159 editform=editform,
3153 pending=pending,
3160 pending=pending,
3154 repopath=repo.path,
3161 repopath=repo.path,
3155 action=b'commit',
3162 action=b'commit',
3156 )
3163 )
3157 text = editortext
3164 text = editortext
3158
3165
3159 # strip away anything below this special string (used for editors that want
3166 # strip away anything below this special string (used for editors that want
3160 # to display the diff)
3167 # to display the diff)
3161 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3168 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3162 if stripbelow:
3169 if stripbelow:
3163 text = text[: stripbelow.start()]
3170 text = text[: stripbelow.start()]
3164
3171
3165 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3172 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3166 os.chdir(olddir)
3173 os.chdir(olddir)
3167
3174
3168 if finishdesc:
3175 if finishdesc:
3169 text = finishdesc(text)
3176 text = finishdesc(text)
3170 if not text.strip():
3177 if not text.strip():
3171 raise error.Abort(_(b"empty commit message"))
3178 raise error.Abort(_(b"empty commit message"))
3172 if unchangedmessagedetection and editortext == templatetext:
3179 if unchangedmessagedetection and editortext == templatetext:
3173 raise error.Abort(_(b"commit message unchanged"))
3180 raise error.Abort(_(b"commit message unchanged"))
3174
3181
3175 return text
3182 return text
3176
3183
3177
3184
3178 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3185 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3179 ui = repo.ui
3186 ui = repo.ui
3180 spec = formatter.templatespec(ref, None, None)
3187 spec = formatter.templatespec(ref, None, None)
3181 t = logcmdutil.changesettemplater(ui, repo, spec)
3188 t = logcmdutil.changesettemplater(ui, repo, spec)
3182 t.t.cache.update(
3189 t.t.cache.update(
3183 (k, templater.unquotestring(v))
3190 (k, templater.unquotestring(v))
3184 for k, v in repo.ui.configitems(b'committemplate')
3191 for k, v in repo.ui.configitems(b'committemplate')
3185 )
3192 )
3186
3193
3187 if not extramsg:
3194 if not extramsg:
3188 extramsg = b'' # ensure that extramsg is string
3195 extramsg = b'' # ensure that extramsg is string
3189
3196
3190 ui.pushbuffer()
3197 ui.pushbuffer()
3191 t.show(ctx, extramsg=extramsg)
3198 t.show(ctx, extramsg=extramsg)
3192 return ui.popbuffer()
3199 return ui.popbuffer()
3193
3200
3194
3201
3195 def hgprefix(msg):
3202 def hgprefix(msg):
3196 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3203 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3197
3204
3198
3205
3199 def buildcommittext(repo, ctx, subs, extramsg):
3206 def buildcommittext(repo, ctx, subs, extramsg):
3200 edittext = []
3207 edittext = []
3201 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3208 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3202 if ctx.description():
3209 if ctx.description():
3203 edittext.append(ctx.description())
3210 edittext.append(ctx.description())
3204 edittext.append(b"")
3211 edittext.append(b"")
3205 edittext.append(b"") # Empty line between message and comments.
3212 edittext.append(b"") # Empty line between message and comments.
3206 edittext.append(
3213 edittext.append(
3207 hgprefix(
3214 hgprefix(
3208 _(
3215 _(
3209 b"Enter commit message."
3216 b"Enter commit message."
3210 b" Lines beginning with 'HG:' are removed."
3217 b" Lines beginning with 'HG:' are removed."
3211 )
3218 )
3212 )
3219 )
3213 )
3220 )
3214 edittext.append(hgprefix(extramsg))
3221 edittext.append(hgprefix(extramsg))
3215 edittext.append(b"HG: --")
3222 edittext.append(b"HG: --")
3216 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3223 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3217 if ctx.p2():
3224 if ctx.p2():
3218 edittext.append(hgprefix(_(b"branch merge")))
3225 edittext.append(hgprefix(_(b"branch merge")))
3219 if ctx.branch():
3226 if ctx.branch():
3220 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3227 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3221 if bookmarks.isactivewdirparent(repo):
3228 if bookmarks.isactivewdirparent(repo):
3222 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3229 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3223 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3230 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3224 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3231 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3225 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3232 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3226 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3233 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3227 if not added and not modified and not removed:
3234 if not added and not modified and not removed:
3228 edittext.append(hgprefix(_(b"no files changed")))
3235 edittext.append(hgprefix(_(b"no files changed")))
3229 edittext.append(b"")
3236 edittext.append(b"")
3230
3237
3231 return b"\n".join(edittext)
3238 return b"\n".join(edittext)
3232
3239
3233
3240
3234 def commitstatus(repo, node, branch, bheads=None, opts=None):
3241 def commitstatus(repo, node, branch, bheads=None, opts=None):
3235 if opts is None:
3242 if opts is None:
3236 opts = {}
3243 opts = {}
3237 ctx = repo[node]
3244 ctx = repo[node]
3238 parents = ctx.parents()
3245 parents = ctx.parents()
3239
3246
3240 if (
3247 if (
3241 not opts.get(b'amend')
3248 not opts.get(b'amend')
3242 and bheads
3249 and bheads
3243 and node not in bheads
3250 and node not in bheads
3244 and not [
3251 and not [
3245 x for x in parents if x.node() in bheads and x.branch() == branch
3252 x for x in parents if x.node() in bheads and x.branch() == branch
3246 ]
3253 ]
3247 ):
3254 ):
3248 repo.ui.status(_(b'created new head\n'))
3255 repo.ui.status(_(b'created new head\n'))
3249 # The message is not printed for initial roots. For the other
3256 # The message is not printed for initial roots. For the other
3250 # changesets, it is printed in the following situations:
3257 # changesets, it is printed in the following situations:
3251 #
3258 #
3252 # Par column: for the 2 parents with ...
3259 # Par column: for the 2 parents with ...
3253 # N: null or no parent
3260 # N: null or no parent
3254 # B: parent is on another named branch
3261 # B: parent is on another named branch
3255 # C: parent is a regular non head changeset
3262 # C: parent is a regular non head changeset
3256 # H: parent was a branch head of the current branch
3263 # H: parent was a branch head of the current branch
3257 # Msg column: whether we print "created new head" message
3264 # Msg column: whether we print "created new head" message
3258 # In the following, it is assumed that there already exists some
3265 # In the following, it is assumed that there already exists some
3259 # initial branch heads of the current branch, otherwise nothing is
3266 # initial branch heads of the current branch, otherwise nothing is
3260 # printed anyway.
3267 # printed anyway.
3261 #
3268 #
3262 # Par Msg Comment
3269 # Par Msg Comment
3263 # N N y additional topo root
3270 # N N y additional topo root
3264 #
3271 #
3265 # B N y additional branch root
3272 # B N y additional branch root
3266 # C N y additional topo head
3273 # C N y additional topo head
3267 # H N n usual case
3274 # H N n usual case
3268 #
3275 #
3269 # B B y weird additional branch root
3276 # B B y weird additional branch root
3270 # C B y branch merge
3277 # C B y branch merge
3271 # H B n merge with named branch
3278 # H B n merge with named branch
3272 #
3279 #
3273 # C C y additional head from merge
3280 # C C y additional head from merge
3274 # C H n merge with a head
3281 # C H n merge with a head
3275 #
3282 #
3276 # H H n head merge: head count decreases
3283 # H H n head merge: head count decreases
3277
3284
3278 if not opts.get(b'close_branch'):
3285 if not opts.get(b'close_branch'):
3279 for r in parents:
3286 for r in parents:
3280 if r.closesbranch() and r.branch() == branch:
3287 if r.closesbranch() and r.branch() == branch:
3281 repo.ui.status(
3288 repo.ui.status(
3282 _(b'reopening closed branch head %d\n') % r.rev()
3289 _(b'reopening closed branch head %d\n') % r.rev()
3283 )
3290 )
3284
3291
3285 if repo.ui.debugflag:
3292 if repo.ui.debugflag:
3286 repo.ui.write(
3293 repo.ui.write(
3287 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3294 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3288 )
3295 )
3289 elif repo.ui.verbose:
3296 elif repo.ui.verbose:
3290 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3297 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3291
3298
3292
3299
3293 def postcommitstatus(repo, pats, opts):
3300 def postcommitstatus(repo, pats, opts):
3294 return repo.status(match=scmutil.match(repo[None], pats, opts))
3301 return repo.status(match=scmutil.match(repo[None], pats, opts))
3295
3302
3296
3303
3297 def revert(ui, repo, ctx, parents, *pats, **opts):
3304 def revert(ui, repo, ctx, parents, *pats, **opts):
3298 opts = pycompat.byteskwargs(opts)
3305 opts = pycompat.byteskwargs(opts)
3299 parent, p2 = parents
3306 parent, p2 = parents
3300 node = ctx.node()
3307 node = ctx.node()
3301
3308
3302 mf = ctx.manifest()
3309 mf = ctx.manifest()
3303 if node == p2:
3310 if node == p2:
3304 parent = p2
3311 parent = p2
3305
3312
3306 # need all matching names in dirstate and manifest of target rev,
3313 # need all matching names in dirstate and manifest of target rev,
3307 # so have to walk both. do not print errors if files exist in one
3314 # so have to walk both. do not print errors if files exist in one
3308 # but not other. in both cases, filesets should be evaluated against
3315 # but not other. in both cases, filesets should be evaluated against
3309 # workingctx to get consistent result (issue4497). this means 'set:**'
3316 # workingctx to get consistent result (issue4497). this means 'set:**'
3310 # cannot be used to select missing files from target rev.
3317 # cannot be used to select missing files from target rev.
3311
3318
3312 # `names` is a mapping for all elements in working copy and target revision
3319 # `names` is a mapping for all elements in working copy and target revision
3313 # The mapping is in the form:
3320 # The mapping is in the form:
3314 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3321 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3315 names = {}
3322 names = {}
3316 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3323 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3317
3324
3318 with repo.wlock():
3325 with repo.wlock():
3319 ## filling of the `names` mapping
3326 ## filling of the `names` mapping
3320 # walk dirstate to fill `names`
3327 # walk dirstate to fill `names`
3321
3328
3322 interactive = opts.get(b'interactive', False)
3329 interactive = opts.get(b'interactive', False)
3323 wctx = repo[None]
3330 wctx = repo[None]
3324 m = scmutil.match(wctx, pats, opts)
3331 m = scmutil.match(wctx, pats, opts)
3325
3332
3326 # we'll need this later
3333 # we'll need this later
3327 targetsubs = sorted(s for s in wctx.substate if m(s))
3334 targetsubs = sorted(s for s in wctx.substate if m(s))
3328
3335
3329 if not m.always():
3336 if not m.always():
3330 matcher = matchmod.badmatch(m, lambda x, y: False)
3337 matcher = matchmod.badmatch(m, lambda x, y: False)
3331 for abs in wctx.walk(matcher):
3338 for abs in wctx.walk(matcher):
3332 names[abs] = m.exact(abs)
3339 names[abs] = m.exact(abs)
3333
3340
3334 # walk target manifest to fill `names`
3341 # walk target manifest to fill `names`
3335
3342
3336 def badfn(path, msg):
3343 def badfn(path, msg):
3337 if path in names:
3344 if path in names:
3338 return
3345 return
3339 if path in ctx.substate:
3346 if path in ctx.substate:
3340 return
3347 return
3341 path_ = path + b'/'
3348 path_ = path + b'/'
3342 for f in names:
3349 for f in names:
3343 if f.startswith(path_):
3350 if f.startswith(path_):
3344 return
3351 return
3345 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3352 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3346
3353
3347 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3354 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3348 if abs not in names:
3355 if abs not in names:
3349 names[abs] = m.exact(abs)
3356 names[abs] = m.exact(abs)
3350
3357
3351 # Find status of all file in `names`.
3358 # Find status of all file in `names`.
3352 m = scmutil.matchfiles(repo, names)
3359 m = scmutil.matchfiles(repo, names)
3353
3360
3354 changes = repo.status(
3361 changes = repo.status(
3355 node1=node, match=m, unknown=True, ignored=True, clean=True
3362 node1=node, match=m, unknown=True, ignored=True, clean=True
3356 )
3363 )
3357 else:
3364 else:
3358 changes = repo.status(node1=node, match=m)
3365 changes = repo.status(node1=node, match=m)
3359 for kind in changes:
3366 for kind in changes:
3360 for abs in kind:
3367 for abs in kind:
3361 names[abs] = m.exact(abs)
3368 names[abs] = m.exact(abs)
3362
3369
3363 m = scmutil.matchfiles(repo, names)
3370 m = scmutil.matchfiles(repo, names)
3364
3371
3365 modified = set(changes.modified)
3372 modified = set(changes.modified)
3366 added = set(changes.added)
3373 added = set(changes.added)
3367 removed = set(changes.removed)
3374 removed = set(changes.removed)
3368 _deleted = set(changes.deleted)
3375 _deleted = set(changes.deleted)
3369 unknown = set(changes.unknown)
3376 unknown = set(changes.unknown)
3370 unknown.update(changes.ignored)
3377 unknown.update(changes.ignored)
3371 clean = set(changes.clean)
3378 clean = set(changes.clean)
3372 modadded = set()
3379 modadded = set()
3373
3380
3374 # We need to account for the state of the file in the dirstate,
3381 # We need to account for the state of the file in the dirstate,
3375 # even when we revert against something else than parent. This will
3382 # even when we revert against something else than parent. This will
3376 # slightly alter the behavior of revert (doing back up or not, delete
3383 # slightly alter the behavior of revert (doing back up or not, delete
3377 # or just forget etc).
3384 # or just forget etc).
3378 if parent == node:
3385 if parent == node:
3379 dsmodified = modified
3386 dsmodified = modified
3380 dsadded = added
3387 dsadded = added
3381 dsremoved = removed
3388 dsremoved = removed
3382 # store all local modifications, useful later for rename detection
3389 # store all local modifications, useful later for rename detection
3383 localchanges = dsmodified | dsadded
3390 localchanges = dsmodified | dsadded
3384 modified, added, removed = set(), set(), set()
3391 modified, added, removed = set(), set(), set()
3385 else:
3392 else:
3386 changes = repo.status(node1=parent, match=m)
3393 changes = repo.status(node1=parent, match=m)
3387 dsmodified = set(changes.modified)
3394 dsmodified = set(changes.modified)
3388 dsadded = set(changes.added)
3395 dsadded = set(changes.added)
3389 dsremoved = set(changes.removed)
3396 dsremoved = set(changes.removed)
3390 # store all local modifications, useful later for rename detection
3397 # store all local modifications, useful later for rename detection
3391 localchanges = dsmodified | dsadded
3398 localchanges = dsmodified | dsadded
3392
3399
3393 # only take into account for removes between wc and target
3400 # only take into account for removes between wc and target
3394 clean |= dsremoved - removed
3401 clean |= dsremoved - removed
3395 dsremoved &= removed
3402 dsremoved &= removed
3396 # distinct between dirstate remove and other
3403 # distinct between dirstate remove and other
3397 removed -= dsremoved
3404 removed -= dsremoved
3398
3405
3399 modadded = added & dsmodified
3406 modadded = added & dsmodified
3400 added -= modadded
3407 added -= modadded
3401
3408
3402 # tell newly modified apart.
3409 # tell newly modified apart.
3403 dsmodified &= modified
3410 dsmodified &= modified
3404 dsmodified |= modified & dsadded # dirstate added may need backup
3411 dsmodified |= modified & dsadded # dirstate added may need backup
3405 modified -= dsmodified
3412 modified -= dsmodified
3406
3413
3407 # We need to wait for some post-processing to update this set
3414 # We need to wait for some post-processing to update this set
3408 # before making the distinction. The dirstate will be used for
3415 # before making the distinction. The dirstate will be used for
3409 # that purpose.
3416 # that purpose.
3410 dsadded = added
3417 dsadded = added
3411
3418
3412 # in case of merge, files that are actually added can be reported as
3419 # in case of merge, files that are actually added can be reported as
3413 # modified, we need to post process the result
3420 # modified, we need to post process the result
3414 if p2 != nullid:
3421 if p2 != nullid:
3415 mergeadd = set(dsmodified)
3422 mergeadd = set(dsmodified)
3416 for path in dsmodified:
3423 for path in dsmodified:
3417 if path in mf:
3424 if path in mf:
3418 mergeadd.remove(path)
3425 mergeadd.remove(path)
3419 dsadded |= mergeadd
3426 dsadded |= mergeadd
3420 dsmodified -= mergeadd
3427 dsmodified -= mergeadd
3421
3428
3422 # if f is a rename, update `names` to also revert the source
3429 # if f is a rename, update `names` to also revert the source
3423 for f in localchanges:
3430 for f in localchanges:
3424 src = repo.dirstate.copied(f)
3431 src = repo.dirstate.copied(f)
3425 # XXX should we check for rename down to target node?
3432 # XXX should we check for rename down to target node?
3426 if src and src not in names and repo.dirstate[src] == b'r':
3433 if src and src not in names and repo.dirstate[src] == b'r':
3427 dsremoved.add(src)
3434 dsremoved.add(src)
3428 names[src] = True
3435 names[src] = True
3429
3436
3430 # determine the exact nature of the deleted changesets
3437 # determine the exact nature of the deleted changesets
3431 deladded = set(_deleted)
3438 deladded = set(_deleted)
3432 for path in _deleted:
3439 for path in _deleted:
3433 if path in mf:
3440 if path in mf:
3434 deladded.remove(path)
3441 deladded.remove(path)
3435 deleted = _deleted - deladded
3442 deleted = _deleted - deladded
3436
3443
3437 # distinguish between file to forget and the other
3444 # distinguish between file to forget and the other
3438 added = set()
3445 added = set()
3439 for abs in dsadded:
3446 for abs in dsadded:
3440 if repo.dirstate[abs] != b'a':
3447 if repo.dirstate[abs] != b'a':
3441 added.add(abs)
3448 added.add(abs)
3442 dsadded -= added
3449 dsadded -= added
3443
3450
3444 for abs in deladded:
3451 for abs in deladded:
3445 if repo.dirstate[abs] == b'a':
3452 if repo.dirstate[abs] == b'a':
3446 dsadded.add(abs)
3453 dsadded.add(abs)
3447 deladded -= dsadded
3454 deladded -= dsadded
3448
3455
3449 # For files marked as removed, we check if an unknown file is present at
3456 # For files marked as removed, we check if an unknown file is present at
3450 # the same path. If a such file exists it may need to be backed up.
3457 # the same path. If a such file exists it may need to be backed up.
3451 # Making the distinction at this stage helps have simpler backup
3458 # Making the distinction at this stage helps have simpler backup
3452 # logic.
3459 # logic.
3453 removunk = set()
3460 removunk = set()
3454 for abs in removed:
3461 for abs in removed:
3455 target = repo.wjoin(abs)
3462 target = repo.wjoin(abs)
3456 if os.path.lexists(target):
3463 if os.path.lexists(target):
3457 removunk.add(abs)
3464 removunk.add(abs)
3458 removed -= removunk
3465 removed -= removunk
3459
3466
3460 dsremovunk = set()
3467 dsremovunk = set()
3461 for abs in dsremoved:
3468 for abs in dsremoved:
3462 target = repo.wjoin(abs)
3469 target = repo.wjoin(abs)
3463 if os.path.lexists(target):
3470 if os.path.lexists(target):
3464 dsremovunk.add(abs)
3471 dsremovunk.add(abs)
3465 dsremoved -= dsremovunk
3472 dsremoved -= dsremovunk
3466
3473
3467 # action to be actually performed by revert
3474 # action to be actually performed by revert
3468 # (<list of file>, message>) tuple
3475 # (<list of file>, message>) tuple
3469 actions = {
3476 actions = {
3470 b'revert': ([], _(b'reverting %s\n')),
3477 b'revert': ([], _(b'reverting %s\n')),
3471 b'add': ([], _(b'adding %s\n')),
3478 b'add': ([], _(b'adding %s\n')),
3472 b'remove': ([], _(b'removing %s\n')),
3479 b'remove': ([], _(b'removing %s\n')),
3473 b'drop': ([], _(b'removing %s\n')),
3480 b'drop': ([], _(b'removing %s\n')),
3474 b'forget': ([], _(b'forgetting %s\n')),
3481 b'forget': ([], _(b'forgetting %s\n')),
3475 b'undelete': ([], _(b'undeleting %s\n')),
3482 b'undelete': ([], _(b'undeleting %s\n')),
3476 b'noop': (None, _(b'no changes needed to %s\n')),
3483 b'noop': (None, _(b'no changes needed to %s\n')),
3477 b'unknown': (None, _(b'file not managed: %s\n')),
3484 b'unknown': (None, _(b'file not managed: %s\n')),
3478 }
3485 }
3479
3486
3480 # "constant" that convey the backup strategy.
3487 # "constant" that convey the backup strategy.
3481 # All set to `discard` if `no-backup` is set do avoid checking
3488 # All set to `discard` if `no-backup` is set do avoid checking
3482 # no_backup lower in the code.
3489 # no_backup lower in the code.
3483 # These values are ordered for comparison purposes
3490 # These values are ordered for comparison purposes
3484 backupinteractive = 3 # do backup if interactively modified
3491 backupinteractive = 3 # do backup if interactively modified
3485 backup = 2 # unconditionally do backup
3492 backup = 2 # unconditionally do backup
3486 check = 1 # check if the existing file differs from target
3493 check = 1 # check if the existing file differs from target
3487 discard = 0 # never do backup
3494 discard = 0 # never do backup
3488 if opts.get(b'no_backup'):
3495 if opts.get(b'no_backup'):
3489 backupinteractive = backup = check = discard
3496 backupinteractive = backup = check = discard
3490 if interactive:
3497 if interactive:
3491 dsmodifiedbackup = backupinteractive
3498 dsmodifiedbackup = backupinteractive
3492 else:
3499 else:
3493 dsmodifiedbackup = backup
3500 dsmodifiedbackup = backup
3494 tobackup = set()
3501 tobackup = set()
3495
3502
3496 backupanddel = actions[b'remove']
3503 backupanddel = actions[b'remove']
3497 if not opts.get(b'no_backup'):
3504 if not opts.get(b'no_backup'):
3498 backupanddel = actions[b'drop']
3505 backupanddel = actions[b'drop']
3499
3506
3500 disptable = (
3507 disptable = (
3501 # dispatch table:
3508 # dispatch table:
3502 # file state
3509 # file state
3503 # action
3510 # action
3504 # make backup
3511 # make backup
3505 ## Sets that results that will change file on disk
3512 ## Sets that results that will change file on disk
3506 # Modified compared to target, no local change
3513 # Modified compared to target, no local change
3507 (modified, actions[b'revert'], discard),
3514 (modified, actions[b'revert'], discard),
3508 # Modified compared to target, but local file is deleted
3515 # Modified compared to target, but local file is deleted
3509 (deleted, actions[b'revert'], discard),
3516 (deleted, actions[b'revert'], discard),
3510 # Modified compared to target, local change
3517 # Modified compared to target, local change
3511 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3518 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3512 # Added since target
3519 # Added since target
3513 (added, actions[b'remove'], discard),
3520 (added, actions[b'remove'], discard),
3514 # Added in working directory
3521 # Added in working directory
3515 (dsadded, actions[b'forget'], discard),
3522 (dsadded, actions[b'forget'], discard),
3516 # Added since target, have local modification
3523 # Added since target, have local modification
3517 (modadded, backupanddel, backup),
3524 (modadded, backupanddel, backup),
3518 # Added since target but file is missing in working directory
3525 # Added since target but file is missing in working directory
3519 (deladded, actions[b'drop'], discard),
3526 (deladded, actions[b'drop'], discard),
3520 # Removed since target, before working copy parent
3527 # Removed since target, before working copy parent
3521 (removed, actions[b'add'], discard),
3528 (removed, actions[b'add'], discard),
3522 # Same as `removed` but an unknown file exists at the same path
3529 # Same as `removed` but an unknown file exists at the same path
3523 (removunk, actions[b'add'], check),
3530 (removunk, actions[b'add'], check),
3524 # Removed since targe, marked as such in working copy parent
3531 # Removed since targe, marked as such in working copy parent
3525 (dsremoved, actions[b'undelete'], discard),
3532 (dsremoved, actions[b'undelete'], discard),
3526 # Same as `dsremoved` but an unknown file exists at the same path
3533 # Same as `dsremoved` but an unknown file exists at the same path
3527 (dsremovunk, actions[b'undelete'], check),
3534 (dsremovunk, actions[b'undelete'], check),
3528 ## the following sets does not result in any file changes
3535 ## the following sets does not result in any file changes
3529 # File with no modification
3536 # File with no modification
3530 (clean, actions[b'noop'], discard),
3537 (clean, actions[b'noop'], discard),
3531 # Existing file, not tracked anywhere
3538 # Existing file, not tracked anywhere
3532 (unknown, actions[b'unknown'], discard),
3539 (unknown, actions[b'unknown'], discard),
3533 )
3540 )
3534
3541
3535 for abs, exact in sorted(names.items()):
3542 for abs, exact in sorted(names.items()):
3536 # target file to be touch on disk (relative to cwd)
3543 # target file to be touch on disk (relative to cwd)
3537 target = repo.wjoin(abs)
3544 target = repo.wjoin(abs)
3538 # search the entry in the dispatch table.
3545 # search the entry in the dispatch table.
3539 # if the file is in any of these sets, it was touched in the working
3546 # if the file is in any of these sets, it was touched in the working
3540 # directory parent and we are sure it needs to be reverted.
3547 # directory parent and we are sure it needs to be reverted.
3541 for table, (xlist, msg), dobackup in disptable:
3548 for table, (xlist, msg), dobackup in disptable:
3542 if abs not in table:
3549 if abs not in table:
3543 continue
3550 continue
3544 if xlist is not None:
3551 if xlist is not None:
3545 xlist.append(abs)
3552 xlist.append(abs)
3546 if dobackup:
3553 if dobackup:
3547 # If in interactive mode, don't automatically create
3554 # If in interactive mode, don't automatically create
3548 # .orig files (issue4793)
3555 # .orig files (issue4793)
3549 if dobackup == backupinteractive:
3556 if dobackup == backupinteractive:
3550 tobackup.add(abs)
3557 tobackup.add(abs)
3551 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3558 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3552 absbakname = scmutil.backuppath(ui, repo, abs)
3559 absbakname = scmutil.backuppath(ui, repo, abs)
3553 bakname = os.path.relpath(
3560 bakname = os.path.relpath(
3554 absbakname, start=repo.root
3561 absbakname, start=repo.root
3555 )
3562 )
3556 ui.note(
3563 ui.note(
3557 _(b'saving current version of %s as %s\n')
3564 _(b'saving current version of %s as %s\n')
3558 % (uipathfn(abs), uipathfn(bakname))
3565 % (uipathfn(abs), uipathfn(bakname))
3559 )
3566 )
3560 if not opts.get(b'dry_run'):
3567 if not opts.get(b'dry_run'):
3561 if interactive:
3568 if interactive:
3562 util.copyfile(target, absbakname)
3569 util.copyfile(target, absbakname)
3563 else:
3570 else:
3564 util.rename(target, absbakname)
3571 util.rename(target, absbakname)
3565 if opts.get(b'dry_run'):
3572 if opts.get(b'dry_run'):
3566 if ui.verbose or not exact:
3573 if ui.verbose or not exact:
3567 ui.status(msg % uipathfn(abs))
3574 ui.status(msg % uipathfn(abs))
3568 elif exact:
3575 elif exact:
3569 ui.warn(msg % uipathfn(abs))
3576 ui.warn(msg % uipathfn(abs))
3570 break
3577 break
3571
3578
3572 if not opts.get(b'dry_run'):
3579 if not opts.get(b'dry_run'):
3573 needdata = (b'revert', b'add', b'undelete')
3580 needdata = (b'revert', b'add', b'undelete')
3574 oplist = [actions[name][0] for name in needdata]
3581 oplist = [actions[name][0] for name in needdata]
3575 prefetch = scmutil.prefetchfiles
3582 prefetch = scmutil.prefetchfiles
3576 matchfiles = scmutil.matchfiles
3583 matchfiles = scmutil.matchfiles
3577 prefetch(
3584 prefetch(
3578 repo,
3585 repo,
3579 [ctx.rev()],
3586 [ctx.rev()],
3580 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3587 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3581 )
3588 )
3582 match = scmutil.match(repo[None], pats)
3589 match = scmutil.match(repo[None], pats)
3583 _performrevert(
3590 _performrevert(
3584 repo,
3591 repo,
3585 parents,
3592 parents,
3586 ctx,
3593 ctx,
3587 names,
3594 names,
3588 uipathfn,
3595 uipathfn,
3589 actions,
3596 actions,
3590 match,
3597 match,
3591 interactive,
3598 interactive,
3592 tobackup,
3599 tobackup,
3593 )
3600 )
3594
3601
3595 if targetsubs:
3602 if targetsubs:
3596 # Revert the subrepos on the revert list
3603 # Revert the subrepos on the revert list
3597 for sub in targetsubs:
3604 for sub in targetsubs:
3598 try:
3605 try:
3599 wctx.sub(sub).revert(
3606 wctx.sub(sub).revert(
3600 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3607 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3601 )
3608 )
3602 except KeyError:
3609 except KeyError:
3603 raise error.Abort(
3610 raise error.Abort(
3604 b"subrepository '%s' does not exist in %s!"
3611 b"subrepository '%s' does not exist in %s!"
3605 % (sub, short(ctx.node()))
3612 % (sub, short(ctx.node()))
3606 )
3613 )
3607
3614
3608
3615
3609 def _performrevert(
3616 def _performrevert(
3610 repo,
3617 repo,
3611 parents,
3618 parents,
3612 ctx,
3619 ctx,
3613 names,
3620 names,
3614 uipathfn,
3621 uipathfn,
3615 actions,
3622 actions,
3616 match,
3623 match,
3617 interactive=False,
3624 interactive=False,
3618 tobackup=None,
3625 tobackup=None,
3619 ):
3626 ):
3620 """function that actually perform all the actions computed for revert
3627 """function that actually perform all the actions computed for revert
3621
3628
3622 This is an independent function to let extension to plug in and react to
3629 This is an independent function to let extension to plug in and react to
3623 the imminent revert.
3630 the imminent revert.
3624
3631
3625 Make sure you have the working directory locked when calling this function.
3632 Make sure you have the working directory locked when calling this function.
3626 """
3633 """
3627 parent, p2 = parents
3634 parent, p2 = parents
3628 node = ctx.node()
3635 node = ctx.node()
3629 excluded_files = []
3636 excluded_files = []
3630
3637
3631 def checkout(f):
3638 def checkout(f):
3632 fc = ctx[f]
3639 fc = ctx[f]
3633 repo.wwrite(f, fc.data(), fc.flags())
3640 repo.wwrite(f, fc.data(), fc.flags())
3634
3641
3635 def doremove(f):
3642 def doremove(f):
3636 try:
3643 try:
3637 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3644 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3638 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3645 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3639 except OSError:
3646 except OSError:
3640 pass
3647 pass
3641 repo.dirstate.remove(f)
3648 repo.dirstate.remove(f)
3642
3649
3643 def prntstatusmsg(action, f):
3650 def prntstatusmsg(action, f):
3644 exact = names[f]
3651 exact = names[f]
3645 if repo.ui.verbose or not exact:
3652 if repo.ui.verbose or not exact:
3646 repo.ui.status(actions[action][1] % uipathfn(f))
3653 repo.ui.status(actions[action][1] % uipathfn(f))
3647
3654
3648 audit_path = pathutil.pathauditor(repo.root, cached=True)
3655 audit_path = pathutil.pathauditor(repo.root, cached=True)
3649 for f in actions[b'forget'][0]:
3656 for f in actions[b'forget'][0]:
3650 if interactive:
3657 if interactive:
3651 choice = repo.ui.promptchoice(
3658 choice = repo.ui.promptchoice(
3652 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3659 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3653 )
3660 )
3654 if choice == 0:
3661 if choice == 0:
3655 prntstatusmsg(b'forget', f)
3662 prntstatusmsg(b'forget', f)
3656 repo.dirstate.drop(f)
3663 repo.dirstate.drop(f)
3657 else:
3664 else:
3658 excluded_files.append(f)
3665 excluded_files.append(f)
3659 else:
3666 else:
3660 prntstatusmsg(b'forget', f)
3667 prntstatusmsg(b'forget', f)
3661 repo.dirstate.drop(f)
3668 repo.dirstate.drop(f)
3662 for f in actions[b'remove'][0]:
3669 for f in actions[b'remove'][0]:
3663 audit_path(f)
3670 audit_path(f)
3664 if interactive:
3671 if interactive:
3665 choice = repo.ui.promptchoice(
3672 choice = repo.ui.promptchoice(
3666 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3673 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3667 )
3674 )
3668 if choice == 0:
3675 if choice == 0:
3669 prntstatusmsg(b'remove', f)
3676 prntstatusmsg(b'remove', f)
3670 doremove(f)
3677 doremove(f)
3671 else:
3678 else:
3672 excluded_files.append(f)
3679 excluded_files.append(f)
3673 else:
3680 else:
3674 prntstatusmsg(b'remove', f)
3681 prntstatusmsg(b'remove', f)
3675 doremove(f)
3682 doremove(f)
3676 for f in actions[b'drop'][0]:
3683 for f in actions[b'drop'][0]:
3677 audit_path(f)
3684 audit_path(f)
3678 prntstatusmsg(b'drop', f)
3685 prntstatusmsg(b'drop', f)
3679 repo.dirstate.remove(f)
3686 repo.dirstate.remove(f)
3680
3687
3681 normal = None
3688 normal = None
3682 if node == parent:
3689 if node == parent:
3683 # We're reverting to our parent. If possible, we'd like status
3690 # We're reverting to our parent. If possible, we'd like status
3684 # to report the file as clean. We have to use normallookup for
3691 # to report the file as clean. We have to use normallookup for
3685 # merges to avoid losing information about merged/dirty files.
3692 # merges to avoid losing information about merged/dirty files.
3686 if p2 != nullid:
3693 if p2 != nullid:
3687 normal = repo.dirstate.normallookup
3694 normal = repo.dirstate.normallookup
3688 else:
3695 else:
3689 normal = repo.dirstate.normal
3696 normal = repo.dirstate.normal
3690
3697
3691 newlyaddedandmodifiedfiles = set()
3698 newlyaddedandmodifiedfiles = set()
3692 if interactive:
3699 if interactive:
3693 # Prompt the user for changes to revert
3700 # Prompt the user for changes to revert
3694 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3701 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3695 m = scmutil.matchfiles(repo, torevert)
3702 m = scmutil.matchfiles(repo, torevert)
3696 diffopts = patch.difffeatureopts(
3703 diffopts = patch.difffeatureopts(
3697 repo.ui,
3704 repo.ui,
3698 whitespace=True,
3705 whitespace=True,
3699 section=b'commands',
3706 section=b'commands',
3700 configprefix=b'revert.interactive.',
3707 configprefix=b'revert.interactive.',
3701 )
3708 )
3702 diffopts.nodates = True
3709 diffopts.nodates = True
3703 diffopts.git = True
3710 diffopts.git = True
3704 operation = b'apply'
3711 operation = b'apply'
3705 if node == parent:
3712 if node == parent:
3706 if repo.ui.configbool(
3713 if repo.ui.configbool(
3707 b'experimental', b'revert.interactive.select-to-keep'
3714 b'experimental', b'revert.interactive.select-to-keep'
3708 ):
3715 ):
3709 operation = b'keep'
3716 operation = b'keep'
3710 else:
3717 else:
3711 operation = b'discard'
3718 operation = b'discard'
3712
3719
3713 if operation == b'apply':
3720 if operation == b'apply':
3714 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3721 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3715 else:
3722 else:
3716 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3723 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3717 originalchunks = patch.parsepatch(diff)
3724 originalchunks = patch.parsepatch(diff)
3718
3725
3719 try:
3726 try:
3720
3727
3721 chunks, opts = recordfilter(
3728 chunks, opts = recordfilter(
3722 repo.ui, originalchunks, match, operation=operation
3729 repo.ui, originalchunks, match, operation=operation
3723 )
3730 )
3724 if operation == b'discard':
3731 if operation == b'discard':
3725 chunks = patch.reversehunks(chunks)
3732 chunks = patch.reversehunks(chunks)
3726
3733
3727 except error.PatchError as err:
3734 except error.PatchError as err:
3728 raise error.Abort(_(b'error parsing patch: %s') % err)
3735 raise error.Abort(_(b'error parsing patch: %s') % err)
3729
3736
3730 # FIXME: when doing an interactive revert of a copy, there's no way of
3737 # FIXME: when doing an interactive revert of a copy, there's no way of
3731 # performing a partial revert of the added file, the only option is
3738 # performing a partial revert of the added file, the only option is
3732 # "remove added file <name> (Yn)?", so we don't need to worry about the
3739 # "remove added file <name> (Yn)?", so we don't need to worry about the
3733 # alsorestore value. Ideally we'd be able to partially revert
3740 # alsorestore value. Ideally we'd be able to partially revert
3734 # copied/renamed files.
3741 # copied/renamed files.
3735 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3742 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3736 chunks, originalchunks
3743 chunks, originalchunks
3737 )
3744 )
3738 if tobackup is None:
3745 if tobackup is None:
3739 tobackup = set()
3746 tobackup = set()
3740 # Apply changes
3747 # Apply changes
3741 fp = stringio()
3748 fp = stringio()
3742 # chunks are serialized per file, but files aren't sorted
3749 # chunks are serialized per file, but files aren't sorted
3743 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3750 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3744 prntstatusmsg(b'revert', f)
3751 prntstatusmsg(b'revert', f)
3745 files = set()
3752 files = set()
3746 for c in chunks:
3753 for c in chunks:
3747 if ishunk(c):
3754 if ishunk(c):
3748 abs = c.header.filename()
3755 abs = c.header.filename()
3749 # Create a backup file only if this hunk should be backed up
3756 # Create a backup file only if this hunk should be backed up
3750 if c.header.filename() in tobackup:
3757 if c.header.filename() in tobackup:
3751 target = repo.wjoin(abs)
3758 target = repo.wjoin(abs)
3752 bakname = scmutil.backuppath(repo.ui, repo, abs)
3759 bakname = scmutil.backuppath(repo.ui, repo, abs)
3753 util.copyfile(target, bakname)
3760 util.copyfile(target, bakname)
3754 tobackup.remove(abs)
3761 tobackup.remove(abs)
3755 if abs not in files:
3762 if abs not in files:
3756 files.add(abs)
3763 files.add(abs)
3757 if operation == b'keep':
3764 if operation == b'keep':
3758 checkout(abs)
3765 checkout(abs)
3759 c.write(fp)
3766 c.write(fp)
3760 dopatch = fp.tell()
3767 dopatch = fp.tell()
3761 fp.seek(0)
3768 fp.seek(0)
3762 if dopatch:
3769 if dopatch:
3763 try:
3770 try:
3764 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3771 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3765 except error.PatchError as err:
3772 except error.PatchError as err:
3766 raise error.Abort(pycompat.bytestr(err))
3773 raise error.Abort(pycompat.bytestr(err))
3767 del fp
3774 del fp
3768 else:
3775 else:
3769 for f in actions[b'revert'][0]:
3776 for f in actions[b'revert'][0]:
3770 prntstatusmsg(b'revert', f)
3777 prntstatusmsg(b'revert', f)
3771 checkout(f)
3778 checkout(f)
3772 if normal:
3779 if normal:
3773 normal(f)
3780 normal(f)
3774
3781
3775 for f in actions[b'add'][0]:
3782 for f in actions[b'add'][0]:
3776 # Don't checkout modified files, they are already created by the diff
3783 # Don't checkout modified files, they are already created by the diff
3777 if f not in newlyaddedandmodifiedfiles:
3784 if f not in newlyaddedandmodifiedfiles:
3778 prntstatusmsg(b'add', f)
3785 prntstatusmsg(b'add', f)
3779 checkout(f)
3786 checkout(f)
3780 repo.dirstate.add(f)
3787 repo.dirstate.add(f)
3781
3788
3782 normal = repo.dirstate.normallookup
3789 normal = repo.dirstate.normallookup
3783 if node == parent and p2 == nullid:
3790 if node == parent and p2 == nullid:
3784 normal = repo.dirstate.normal
3791 normal = repo.dirstate.normal
3785 for f in actions[b'undelete'][0]:
3792 for f in actions[b'undelete'][0]:
3786 if interactive:
3793 if interactive:
3787 choice = repo.ui.promptchoice(
3794 choice = repo.ui.promptchoice(
3788 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3795 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3789 )
3796 )
3790 if choice == 0:
3797 if choice == 0:
3791 prntstatusmsg(b'undelete', f)
3798 prntstatusmsg(b'undelete', f)
3792 checkout(f)
3799 checkout(f)
3793 normal(f)
3800 normal(f)
3794 else:
3801 else:
3795 excluded_files.append(f)
3802 excluded_files.append(f)
3796 else:
3803 else:
3797 prntstatusmsg(b'undelete', f)
3804 prntstatusmsg(b'undelete', f)
3798 checkout(f)
3805 checkout(f)
3799 normal(f)
3806 normal(f)
3800
3807
3801 copied = copies.pathcopies(repo[parent], ctx)
3808 copied = copies.pathcopies(repo[parent], ctx)
3802
3809
3803 for f in (
3810 for f in (
3804 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3811 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3805 ):
3812 ):
3806 if f in copied:
3813 if f in copied:
3807 repo.dirstate.copy(copied[f], f)
3814 repo.dirstate.copy(copied[f], f)
3808
3815
3809
3816
3810 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3817 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3811 # commands.outgoing. "missing" is "missing" of the result of
3818 # commands.outgoing. "missing" is "missing" of the result of
3812 # "findcommonoutgoing()"
3819 # "findcommonoutgoing()"
3813 outgoinghooks = util.hooks()
3820 outgoinghooks = util.hooks()
3814
3821
3815 # a list of (ui, repo) functions called by commands.summary
3822 # a list of (ui, repo) functions called by commands.summary
3816 summaryhooks = util.hooks()
3823 summaryhooks = util.hooks()
3817
3824
3818 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3825 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3819 #
3826 #
3820 # functions should return tuple of booleans below, if 'changes' is None:
3827 # functions should return tuple of booleans below, if 'changes' is None:
3821 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3828 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3822 #
3829 #
3823 # otherwise, 'changes' is a tuple of tuples below:
3830 # otherwise, 'changes' is a tuple of tuples below:
3824 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3831 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3825 # - (desturl, destbranch, destpeer, outgoing)
3832 # - (desturl, destbranch, destpeer, outgoing)
3826 summaryremotehooks = util.hooks()
3833 summaryremotehooks = util.hooks()
3827
3834
3828
3835
3829 def checkunfinished(repo, commit=False, skipmerge=False):
3836 def checkunfinished(repo, commit=False, skipmerge=False):
3830 '''Look for an unfinished multistep operation, like graft, and abort
3837 '''Look for an unfinished multistep operation, like graft, and abort
3831 if found. It's probably good to check this right before
3838 if found. It's probably good to check this right before
3832 bailifchanged().
3839 bailifchanged().
3833 '''
3840 '''
3834 # Check for non-clearable states first, so things like rebase will take
3841 # Check for non-clearable states first, so things like rebase will take
3835 # precedence over update.
3842 # precedence over update.
3836 for state in statemod._unfinishedstates:
3843 for state in statemod._unfinishedstates:
3837 if (
3844 if (
3838 state._clearable
3845 state._clearable
3839 or (commit and state._allowcommit)
3846 or (commit and state._allowcommit)
3840 or state._reportonly
3847 or state._reportonly
3841 ):
3848 ):
3842 continue
3849 continue
3843 if state.isunfinished(repo):
3850 if state.isunfinished(repo):
3844 raise error.Abort(state.msg(), hint=state.hint())
3851 raise error.Abort(state.msg(), hint=state.hint())
3845
3852
3846 for s in statemod._unfinishedstates:
3853 for s in statemod._unfinishedstates:
3847 if (
3854 if (
3848 not s._clearable
3855 not s._clearable
3849 or (commit and s._allowcommit)
3856 or (commit and s._allowcommit)
3850 or (s._opname == b'merge' and skipmerge)
3857 or (s._opname == b'merge' and skipmerge)
3851 or s._reportonly
3858 or s._reportonly
3852 ):
3859 ):
3853 continue
3860 continue
3854 if s.isunfinished(repo):
3861 if s.isunfinished(repo):
3855 raise error.Abort(s.msg(), hint=s.hint())
3862 raise error.Abort(s.msg(), hint=s.hint())
3856
3863
3857
3864
3858 def clearunfinished(repo):
3865 def clearunfinished(repo):
3859 '''Check for unfinished operations (as above), and clear the ones
3866 '''Check for unfinished operations (as above), and clear the ones
3860 that are clearable.
3867 that are clearable.
3861 '''
3868 '''
3862 for state in statemod._unfinishedstates:
3869 for state in statemod._unfinishedstates:
3863 if state._reportonly:
3870 if state._reportonly:
3864 continue
3871 continue
3865 if not state._clearable and state.isunfinished(repo):
3872 if not state._clearable and state.isunfinished(repo):
3866 raise error.Abort(state.msg(), hint=state.hint())
3873 raise error.Abort(state.msg(), hint=state.hint())
3867
3874
3868 for s in statemod._unfinishedstates:
3875 for s in statemod._unfinishedstates:
3869 if s._opname == b'merge' or state._reportonly:
3876 if s._opname == b'merge' or state._reportonly:
3870 continue
3877 continue
3871 if s._clearable and s.isunfinished(repo):
3878 if s._clearable and s.isunfinished(repo):
3872 util.unlink(repo.vfs.join(s._fname))
3879 util.unlink(repo.vfs.join(s._fname))
3873
3880
3874
3881
3875 def getunfinishedstate(repo):
3882 def getunfinishedstate(repo):
3876 ''' Checks for unfinished operations and returns statecheck object
3883 ''' Checks for unfinished operations and returns statecheck object
3877 for it'''
3884 for it'''
3878 for state in statemod._unfinishedstates:
3885 for state in statemod._unfinishedstates:
3879 if state.isunfinished(repo):
3886 if state.isunfinished(repo):
3880 return state
3887 return state
3881 return None
3888 return None
3882
3889
3883
3890
3884 def howtocontinue(repo):
3891 def howtocontinue(repo):
3885 '''Check for an unfinished operation and return the command to finish
3892 '''Check for an unfinished operation and return the command to finish
3886 it.
3893 it.
3887
3894
3888 statemod._unfinishedstates list is checked for an unfinished operation
3895 statemod._unfinishedstates list is checked for an unfinished operation
3889 and the corresponding message to finish it is generated if a method to
3896 and the corresponding message to finish it is generated if a method to
3890 continue is supported by the operation.
3897 continue is supported by the operation.
3891
3898
3892 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3899 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3893 a boolean.
3900 a boolean.
3894 '''
3901 '''
3895 contmsg = _(b"continue: %s")
3902 contmsg = _(b"continue: %s")
3896 for state in statemod._unfinishedstates:
3903 for state in statemod._unfinishedstates:
3897 if not state._continueflag:
3904 if not state._continueflag:
3898 continue
3905 continue
3899 if state.isunfinished(repo):
3906 if state.isunfinished(repo):
3900 return contmsg % state.continuemsg(), True
3907 return contmsg % state.continuemsg(), True
3901 if repo[None].dirty(missing=True, merge=False, branch=False):
3908 if repo[None].dirty(missing=True, merge=False, branch=False):
3902 return contmsg % _(b"hg commit"), False
3909 return contmsg % _(b"hg commit"), False
3903 return None, None
3910 return None, None
3904
3911
3905
3912
3906 def checkafterresolved(repo):
3913 def checkafterresolved(repo):
3907 '''Inform the user about the next action after completing hg resolve
3914 '''Inform the user about the next action after completing hg resolve
3908
3915
3909 If there's a an unfinished operation that supports continue flag,
3916 If there's a an unfinished operation that supports continue flag,
3910 howtocontinue will yield repo.ui.warn as the reporter.
3917 howtocontinue will yield repo.ui.warn as the reporter.
3911
3918
3912 Otherwise, it will yield repo.ui.note.
3919 Otherwise, it will yield repo.ui.note.
3913 '''
3920 '''
3914 msg, warning = howtocontinue(repo)
3921 msg, warning = howtocontinue(repo)
3915 if msg is not None:
3922 if msg is not None:
3916 if warning:
3923 if warning:
3917 repo.ui.warn(b"%s\n" % msg)
3924 repo.ui.warn(b"%s\n" % msg)
3918 else:
3925 else:
3919 repo.ui.note(b"%s\n" % msg)
3926 repo.ui.note(b"%s\n" % msg)
3920
3927
3921
3928
3922 def wrongtooltocontinue(repo, task):
3929 def wrongtooltocontinue(repo, task):
3923 '''Raise an abort suggesting how to properly continue if there is an
3930 '''Raise an abort suggesting how to properly continue if there is an
3924 active task.
3931 active task.
3925
3932
3926 Uses howtocontinue() to find the active task.
3933 Uses howtocontinue() to find the active task.
3927
3934
3928 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3935 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3929 a hint.
3936 a hint.
3930 '''
3937 '''
3931 after = howtocontinue(repo)
3938 after = howtocontinue(repo)
3932 hint = None
3939 hint = None
3933 if after[1]:
3940 if after[1]:
3934 hint = after[0]
3941 hint = after[0]
3935 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
3942 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
3936
3943
3937
3944
3938 def abortgraft(ui, repo, graftstate):
3945 def abortgraft(ui, repo, graftstate):
3939 """abort the interrupted graft and rollbacks to the state before interrupted
3946 """abort the interrupted graft and rollbacks to the state before interrupted
3940 graft"""
3947 graft"""
3941 if not graftstate.exists():
3948 if not graftstate.exists():
3942 raise error.Abort(_(b"no interrupted graft to abort"))
3949 raise error.Abort(_(b"no interrupted graft to abort"))
3943 statedata = readgraftstate(repo, graftstate)
3950 statedata = readgraftstate(repo, graftstate)
3944 newnodes = statedata.get(b'newnodes')
3951 newnodes = statedata.get(b'newnodes')
3945 if newnodes is None:
3952 if newnodes is None:
3946 # and old graft state which does not have all the data required to abort
3953 # and old graft state which does not have all the data required to abort
3947 # the graft
3954 # the graft
3948 raise error.Abort(_(b"cannot abort using an old graftstate"))
3955 raise error.Abort(_(b"cannot abort using an old graftstate"))
3949
3956
3950 # changeset from which graft operation was started
3957 # changeset from which graft operation was started
3951 if len(newnodes) > 0:
3958 if len(newnodes) > 0:
3952 startctx = repo[newnodes[0]].p1()
3959 startctx = repo[newnodes[0]].p1()
3953 else:
3960 else:
3954 startctx = repo[b'.']
3961 startctx = repo[b'.']
3955 # whether to strip or not
3962 # whether to strip or not
3956 cleanup = False
3963 cleanup = False
3957 from . import hg
3964 from . import hg
3958
3965
3959 if newnodes:
3966 if newnodes:
3960 newnodes = [repo[r].rev() for r in newnodes]
3967 newnodes = [repo[r].rev() for r in newnodes]
3961 cleanup = True
3968 cleanup = True
3962 # checking that none of the newnodes turned public or is public
3969 # checking that none of the newnodes turned public or is public
3963 immutable = [c for c in newnodes if not repo[c].mutable()]
3970 immutable = [c for c in newnodes if not repo[c].mutable()]
3964 if immutable:
3971 if immutable:
3965 repo.ui.warn(
3972 repo.ui.warn(
3966 _(b"cannot clean up public changesets %s\n")
3973 _(b"cannot clean up public changesets %s\n")
3967 % b', '.join(bytes(repo[r]) for r in immutable),
3974 % b', '.join(bytes(repo[r]) for r in immutable),
3968 hint=_(b"see 'hg help phases' for details"),
3975 hint=_(b"see 'hg help phases' for details"),
3969 )
3976 )
3970 cleanup = False
3977 cleanup = False
3971
3978
3972 # checking that no new nodes are created on top of grafted revs
3979 # checking that no new nodes are created on top of grafted revs
3973 desc = set(repo.changelog.descendants(newnodes))
3980 desc = set(repo.changelog.descendants(newnodes))
3974 if desc - set(newnodes):
3981 if desc - set(newnodes):
3975 repo.ui.warn(
3982 repo.ui.warn(
3976 _(
3983 _(
3977 b"new changesets detected on destination "
3984 b"new changesets detected on destination "
3978 b"branch, can't strip\n"
3985 b"branch, can't strip\n"
3979 )
3986 )
3980 )
3987 )
3981 cleanup = False
3988 cleanup = False
3982
3989
3983 if cleanup:
3990 if cleanup:
3984 with repo.wlock(), repo.lock():
3991 with repo.wlock(), repo.lock():
3985 hg.updaterepo(repo, startctx.node(), overwrite=True)
3992 hg.updaterepo(repo, startctx.node(), overwrite=True)
3986 # stripping the new nodes created
3993 # stripping the new nodes created
3987 strippoints = [
3994 strippoints = [
3988 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3995 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3989 ]
3996 ]
3990 repair.strip(repo.ui, repo, strippoints, backup=False)
3997 repair.strip(repo.ui, repo, strippoints, backup=False)
3991
3998
3992 if not cleanup:
3999 if not cleanup:
3993 # we don't update to the startnode if we can't strip
4000 # we don't update to the startnode if we can't strip
3994 startctx = repo[b'.']
4001 startctx = repo[b'.']
3995 hg.updaterepo(repo, startctx.node(), overwrite=True)
4002 hg.updaterepo(repo, startctx.node(), overwrite=True)
3996
4003
3997 ui.status(_(b"graft aborted\n"))
4004 ui.status(_(b"graft aborted\n"))
3998 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4005 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3999 graftstate.delete()
4006 graftstate.delete()
4000 return 0
4007 return 0
4001
4008
4002
4009
4003 def readgraftstate(repo, graftstate):
4010 def readgraftstate(repo, graftstate):
4004 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4011 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4005 """read the graft state file and return a dict of the data stored in it"""
4012 """read the graft state file and return a dict of the data stored in it"""
4006 try:
4013 try:
4007 return graftstate.read()
4014 return graftstate.read()
4008 except error.CorruptedState:
4015 except error.CorruptedState:
4009 nodes = repo.vfs.read(b'graftstate').splitlines()
4016 nodes = repo.vfs.read(b'graftstate').splitlines()
4010 return {b'nodes': nodes}
4017 return {b'nodes': nodes}
4011
4018
4012
4019
4013 def hgabortgraft(ui, repo):
4020 def hgabortgraft(ui, repo):
4014 """ abort logic for aborting graft using 'hg abort'"""
4021 """ abort logic for aborting graft using 'hg abort'"""
4015 with repo.wlock():
4022 with repo.wlock():
4016 graftstate = statemod.cmdstate(repo, b'graftstate')
4023 graftstate = statemod.cmdstate(repo, b'graftstate')
4017 return abortgraft(ui, repo, graftstate)
4024 return abortgraft(ui, repo, graftstate)
@@ -1,7838 +1,7838 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' for x in l])
562 formats.append([b'%s' for x in 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 selactions = [k for k in [b'delete', b'rename', b'list'] if opts.get(k)]
1229 selactions = [k for k in [b'delete', b'rename', b'list'] if opts.get(k)]
1230 if len(selactions) > 1:
1230 if len(selactions) > 1:
1231 raise error.Abort(
1231 raise error.Abort(
1232 _(b'--%s and --%s are incompatible') % tuple(selactions[:2])
1232 _(b'--%s and --%s are incompatible') % tuple(selactions[:2])
1233 )
1233 )
1234 if selactions:
1234 if selactions:
1235 action = selactions[0]
1235 action = selactions[0]
1236 elif names or rev:
1236 elif names or rev:
1237 action = b'add'
1237 action = b'add'
1238 elif inactive:
1238 elif inactive:
1239 action = b'inactive' # meaning deactivate
1239 action = b'inactive' # meaning deactivate
1240 else:
1240 else:
1241 action = b'list'
1241 action = b'list'
1242
1242
1243 if rev and action in {b'delete', b'rename', b'list'}:
1243 if rev and action in {b'delete', b'rename', b'list'}:
1244 raise error.Abort(_(b"--rev is incompatible with --%s") % action)
1244 raise error.Abort(_(b"--rev is incompatible with --%s") % action)
1245 if inactive and action in {b'delete', b'list'}:
1245 if inactive and action in {b'delete', b'list'}:
1246 raise error.Abort(_(b"--inactive is incompatible with --%s") % action)
1246 raise error.Abort(_(b"--inactive is incompatible with --%s") % action)
1247 if not names and action in {b'add', b'delete'}:
1247 if not names and action in {b'add', b'delete'}:
1248 raise error.Abort(_(b"bookmark name required"))
1248 raise error.Abort(_(b"bookmark name required"))
1249
1249
1250 if action in {b'add', b'delete', b'rename', b'inactive'}:
1250 if action in {b'add', b'delete', b'rename', b'inactive'}:
1251 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1251 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1252 if action == b'delete':
1252 if action == b'delete':
1253 names = pycompat.maplist(repo._bookmarks.expandname, names)
1253 names = pycompat.maplist(repo._bookmarks.expandname, names)
1254 bookmarks.delete(repo, tr, names)
1254 bookmarks.delete(repo, tr, names)
1255 elif action == b'rename':
1255 elif action == b'rename':
1256 if not names:
1256 if not names:
1257 raise error.Abort(_(b"new bookmark name required"))
1257 raise error.Abort(_(b"new bookmark name required"))
1258 elif len(names) > 1:
1258 elif len(names) > 1:
1259 raise error.Abort(_(b"only one new bookmark name allowed"))
1259 raise error.Abort(_(b"only one new bookmark name allowed"))
1260 oldname = repo._bookmarks.expandname(opts[b'rename'])
1260 oldname = repo._bookmarks.expandname(opts[b'rename'])
1261 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1261 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1262 elif action == b'add':
1262 elif action == b'add':
1263 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1263 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1264 elif action == b'inactive':
1264 elif action == b'inactive':
1265 if len(repo._bookmarks) == 0:
1265 if len(repo._bookmarks) == 0:
1266 ui.status(_(b"no bookmarks set\n"))
1266 ui.status(_(b"no bookmarks set\n"))
1267 elif not repo._activebookmark:
1267 elif not repo._activebookmark:
1268 ui.status(_(b"no active bookmark\n"))
1268 ui.status(_(b"no active bookmark\n"))
1269 else:
1269 else:
1270 bookmarks.deactivate(repo)
1270 bookmarks.deactivate(repo)
1271 elif action == b'list':
1271 elif action == b'list':
1272 names = pycompat.maplist(repo._bookmarks.expandname, names)
1272 names = pycompat.maplist(repo._bookmarks.expandname, names)
1273 with ui.formatter(b'bookmarks', opts) as fm:
1273 with ui.formatter(b'bookmarks', opts) as fm:
1274 bookmarks.printbookmarks(ui, repo, fm, names)
1274 bookmarks.printbookmarks(ui, repo, fm, names)
1275 else:
1275 else:
1276 raise error.ProgrammingError(b'invalid action: %s' % action)
1276 raise error.ProgrammingError(b'invalid action: %s' % action)
1277
1277
1278
1278
1279 @command(
1279 @command(
1280 b'branch',
1280 b'branch',
1281 [
1281 [
1282 (
1282 (
1283 b'f',
1283 b'f',
1284 b'force',
1284 b'force',
1285 None,
1285 None,
1286 _(b'set branch name even if it shadows an existing branch'),
1286 _(b'set branch name even if it shadows an existing branch'),
1287 ),
1287 ),
1288 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1288 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1289 (
1289 (
1290 b'r',
1290 b'r',
1291 b'rev',
1291 b'rev',
1292 [],
1292 [],
1293 _(b'change branches of the given revs (EXPERIMENTAL)'),
1293 _(b'change branches of the given revs (EXPERIMENTAL)'),
1294 ),
1294 ),
1295 ],
1295 ],
1296 _(b'[-fC] [NAME]'),
1296 _(b'[-fC] [NAME]'),
1297 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1297 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1298 )
1298 )
1299 def branch(ui, repo, label=None, **opts):
1299 def branch(ui, repo, label=None, **opts):
1300 """set or show the current branch name
1300 """set or show the current branch name
1301
1301
1302 .. note::
1302 .. note::
1303
1303
1304 Branch names are permanent and global. Use :hg:`bookmark` to create a
1304 Branch names are permanent and global. Use :hg:`bookmark` to create a
1305 light-weight bookmark instead. See :hg:`help glossary` for more
1305 light-weight bookmark instead. See :hg:`help glossary` for more
1306 information about named branches and bookmarks.
1306 information about named branches and bookmarks.
1307
1307
1308 With no argument, show the current branch name. With one argument,
1308 With no argument, show the current branch name. With one argument,
1309 set the working directory branch name (the branch will not exist
1309 set the working directory branch name (the branch will not exist
1310 in the repository until the next commit). Standard practice
1310 in the repository until the next commit). Standard practice
1311 recommends that primary development take place on the 'default'
1311 recommends that primary development take place on the 'default'
1312 branch.
1312 branch.
1313
1313
1314 Unless -f/--force is specified, branch will not let you set a
1314 Unless -f/--force is specified, branch will not let you set a
1315 branch name that already exists.
1315 branch name that already exists.
1316
1316
1317 Use -C/--clean to reset the working directory branch to that of
1317 Use -C/--clean to reset the working directory branch to that of
1318 the parent of the working directory, negating a previous branch
1318 the parent of the working directory, negating a previous branch
1319 change.
1319 change.
1320
1320
1321 Use the command :hg:`update` to switch to an existing branch. Use
1321 Use the command :hg:`update` to switch to an existing branch. Use
1322 :hg:`commit --close-branch` to mark this branch head as closed.
1322 :hg:`commit --close-branch` to mark this branch head as closed.
1323 When all heads of a branch are closed, the branch will be
1323 When all heads of a branch are closed, the branch will be
1324 considered closed.
1324 considered closed.
1325
1325
1326 Returns 0 on success.
1326 Returns 0 on success.
1327 """
1327 """
1328 opts = pycompat.byteskwargs(opts)
1328 opts = pycompat.byteskwargs(opts)
1329 revs = opts.get(b'rev')
1329 revs = opts.get(b'rev')
1330 if label:
1330 if label:
1331 label = label.strip()
1331 label = label.strip()
1332
1332
1333 if not opts.get(b'clean') and not label:
1333 if not opts.get(b'clean') and not label:
1334 if revs:
1334 if revs:
1335 raise error.Abort(_(b"no branch name specified for the revisions"))
1335 raise error.Abort(_(b"no branch name specified for the revisions"))
1336 ui.write(b"%s\n" % repo.dirstate.branch())
1336 ui.write(b"%s\n" % repo.dirstate.branch())
1337 return
1337 return
1338
1338
1339 with repo.wlock():
1339 with repo.wlock():
1340 if opts.get(b'clean'):
1340 if opts.get(b'clean'):
1341 label = repo[b'.'].branch()
1341 label = repo[b'.'].branch()
1342 repo.dirstate.setbranch(label)
1342 repo.dirstate.setbranch(label)
1343 ui.status(_(b'reset working directory to branch %s\n') % label)
1343 ui.status(_(b'reset working directory to branch %s\n') % label)
1344 elif label:
1344 elif label:
1345
1345
1346 scmutil.checknewlabel(repo, label, b'branch')
1346 scmutil.checknewlabel(repo, label, b'branch')
1347 if revs:
1347 if revs:
1348 return cmdutil.changebranch(ui, repo, revs, label)
1348 return cmdutil.changebranch(ui, repo, revs, label)
1349
1349
1350 if not opts.get(b'force') and label in repo.branchmap():
1350 if not opts.get(b'force') and label in repo.branchmap():
1351 if label not in [p.branch() for p in repo[None].parents()]:
1351 if label not in [p.branch() for p in repo[None].parents()]:
1352 raise error.Abort(
1352 raise error.Abort(
1353 _(b'a branch of the same name already exists'),
1353 _(b'a branch of the same name already exists'),
1354 # i18n: "it" refers to an existing branch
1354 # i18n: "it" refers to an existing branch
1355 hint=_(b"use 'hg update' to switch to it"),
1355 hint=_(b"use 'hg update' to switch to it"),
1356 )
1356 )
1357
1357
1358 repo.dirstate.setbranch(label)
1358 repo.dirstate.setbranch(label)
1359 ui.status(_(b'marked working directory as branch %s\n') % label)
1359 ui.status(_(b'marked working directory as branch %s\n') % label)
1360
1360
1361 # find any open named branches aside from default
1361 # find any open named branches aside from default
1362 for n, h, t, c in repo.branchmap().iterbranches():
1362 for n, h, t, c in repo.branchmap().iterbranches():
1363 if n != b"default" and not c:
1363 if n != b"default" and not c:
1364 return 0
1364 return 0
1365 ui.status(
1365 ui.status(
1366 _(
1366 _(
1367 b'(branches are permanent and global, '
1367 b'(branches are permanent and global, '
1368 b'did you want a bookmark?)\n'
1368 b'did you want a bookmark?)\n'
1369 )
1369 )
1370 )
1370 )
1371
1371
1372
1372
1373 @command(
1373 @command(
1374 b'branches',
1374 b'branches',
1375 [
1375 [
1376 (
1376 (
1377 b'a',
1377 b'a',
1378 b'active',
1378 b'active',
1379 False,
1379 False,
1380 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1380 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1381 ),
1381 ),
1382 (b'c', b'closed', False, _(b'show normal and closed branches')),
1382 (b'c', b'closed', False, _(b'show normal and closed branches')),
1383 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1383 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1384 ]
1384 ]
1385 + formatteropts,
1385 + formatteropts,
1386 _(b'[-c]'),
1386 _(b'[-c]'),
1387 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1387 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1388 intents={INTENT_READONLY},
1388 intents={INTENT_READONLY},
1389 )
1389 )
1390 def branches(ui, repo, active=False, closed=False, **opts):
1390 def branches(ui, repo, active=False, closed=False, **opts):
1391 """list repository named branches
1391 """list repository named branches
1392
1392
1393 List the repository's named branches, indicating which ones are
1393 List the repository's named branches, indicating which ones are
1394 inactive. If -c/--closed is specified, also list branches which have
1394 inactive. If -c/--closed is specified, also list branches which have
1395 been marked closed (see :hg:`commit --close-branch`).
1395 been marked closed (see :hg:`commit --close-branch`).
1396
1396
1397 Use the command :hg:`update` to switch to an existing branch.
1397 Use the command :hg:`update` to switch to an existing branch.
1398
1398
1399 .. container:: verbose
1399 .. container:: verbose
1400
1400
1401 Template:
1401 Template:
1402
1402
1403 The following keywords are supported in addition to the common template
1403 The following keywords are supported in addition to the common template
1404 keywords and functions such as ``{branch}``. See also
1404 keywords and functions such as ``{branch}``. See also
1405 :hg:`help templates`.
1405 :hg:`help templates`.
1406
1406
1407 :active: Boolean. True if the branch is active.
1407 :active: Boolean. True if the branch is active.
1408 :closed: Boolean. True if the branch is closed.
1408 :closed: Boolean. True if the branch is closed.
1409 :current: Boolean. True if it is the current branch.
1409 :current: Boolean. True if it is the current branch.
1410
1410
1411 Returns 0.
1411 Returns 0.
1412 """
1412 """
1413
1413
1414 opts = pycompat.byteskwargs(opts)
1414 opts = pycompat.byteskwargs(opts)
1415 revs = opts.get(b'rev')
1415 revs = opts.get(b'rev')
1416 selectedbranches = None
1416 selectedbranches = None
1417 if revs:
1417 if revs:
1418 revs = scmutil.revrange(repo, revs)
1418 revs = scmutil.revrange(repo, revs)
1419 getbi = repo.revbranchcache().branchinfo
1419 getbi = repo.revbranchcache().branchinfo
1420 selectedbranches = {getbi(r)[0] for r in revs}
1420 selectedbranches = {getbi(r)[0] for r in revs}
1421
1421
1422 ui.pager(b'branches')
1422 ui.pager(b'branches')
1423 fm = ui.formatter(b'branches', opts)
1423 fm = ui.formatter(b'branches', opts)
1424 hexfunc = fm.hexfunc
1424 hexfunc = fm.hexfunc
1425
1425
1426 allheads = set(repo.heads())
1426 allheads = set(repo.heads())
1427 branches = []
1427 branches = []
1428 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1428 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1429 if selectedbranches is not None and tag not in selectedbranches:
1429 if selectedbranches is not None and tag not in selectedbranches:
1430 continue
1430 continue
1431 isactive = False
1431 isactive = False
1432 if not isclosed:
1432 if not isclosed:
1433 openheads = set(repo.branchmap().iteropen(heads))
1433 openheads = set(repo.branchmap().iteropen(heads))
1434 isactive = bool(openheads & allheads)
1434 isactive = bool(openheads & allheads)
1435 branches.append((tag, repo[tip], isactive, not isclosed))
1435 branches.append((tag, repo[tip], isactive, not isclosed))
1436 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1436 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1437
1437
1438 for tag, ctx, isactive, isopen in branches:
1438 for tag, ctx, isactive, isopen in branches:
1439 if active and not isactive:
1439 if active and not isactive:
1440 continue
1440 continue
1441 if isactive:
1441 if isactive:
1442 label = b'branches.active'
1442 label = b'branches.active'
1443 notice = b''
1443 notice = b''
1444 elif not isopen:
1444 elif not isopen:
1445 if not closed:
1445 if not closed:
1446 continue
1446 continue
1447 label = b'branches.closed'
1447 label = b'branches.closed'
1448 notice = _(b' (closed)')
1448 notice = _(b' (closed)')
1449 else:
1449 else:
1450 label = b'branches.inactive'
1450 label = b'branches.inactive'
1451 notice = _(b' (inactive)')
1451 notice = _(b' (inactive)')
1452 current = tag == repo.dirstate.branch()
1452 current = tag == repo.dirstate.branch()
1453 if current:
1453 if current:
1454 label = b'branches.current'
1454 label = b'branches.current'
1455
1455
1456 fm.startitem()
1456 fm.startitem()
1457 fm.write(b'branch', b'%s', tag, label=label)
1457 fm.write(b'branch', b'%s', tag, label=label)
1458 rev = ctx.rev()
1458 rev = ctx.rev()
1459 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1459 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1460 fmt = b' ' * padsize + b' %d:%s'
1460 fmt = b' ' * padsize + b' %d:%s'
1461 fm.condwrite(
1461 fm.condwrite(
1462 not ui.quiet,
1462 not ui.quiet,
1463 b'rev node',
1463 b'rev node',
1464 fmt,
1464 fmt,
1465 rev,
1465 rev,
1466 hexfunc(ctx.node()),
1466 hexfunc(ctx.node()),
1467 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1467 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1468 )
1468 )
1469 fm.context(ctx=ctx)
1469 fm.context(ctx=ctx)
1470 fm.data(active=isactive, closed=not isopen, current=current)
1470 fm.data(active=isactive, closed=not isopen, current=current)
1471 if not ui.quiet:
1471 if not ui.quiet:
1472 fm.plain(notice)
1472 fm.plain(notice)
1473 fm.plain(b'\n')
1473 fm.plain(b'\n')
1474 fm.end()
1474 fm.end()
1475
1475
1476
1476
1477 @command(
1477 @command(
1478 b'bundle',
1478 b'bundle',
1479 [
1479 [
1480 (
1480 (
1481 b'f',
1481 b'f',
1482 b'force',
1482 b'force',
1483 None,
1483 None,
1484 _(b'run even when the destination is unrelated'),
1484 _(b'run even when the destination is unrelated'),
1485 ),
1485 ),
1486 (
1486 (
1487 b'r',
1487 b'r',
1488 b'rev',
1488 b'rev',
1489 [],
1489 [],
1490 _(b'a changeset intended to be added to the destination'),
1490 _(b'a changeset intended to be added to the destination'),
1491 _(b'REV'),
1491 _(b'REV'),
1492 ),
1492 ),
1493 (
1493 (
1494 b'b',
1494 b'b',
1495 b'branch',
1495 b'branch',
1496 [],
1496 [],
1497 _(b'a specific branch you would like to bundle'),
1497 _(b'a specific branch you would like to bundle'),
1498 _(b'BRANCH'),
1498 _(b'BRANCH'),
1499 ),
1499 ),
1500 (
1500 (
1501 b'',
1501 b'',
1502 b'base',
1502 b'base',
1503 [],
1503 [],
1504 _(b'a base changeset assumed to be available at the destination'),
1504 _(b'a base changeset assumed to be available at the destination'),
1505 _(b'REV'),
1505 _(b'REV'),
1506 ),
1506 ),
1507 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1507 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1508 (
1508 (
1509 b't',
1509 b't',
1510 b'type',
1510 b'type',
1511 b'bzip2',
1511 b'bzip2',
1512 _(b'bundle compression type to use'),
1512 _(b'bundle compression type to use'),
1513 _(b'TYPE'),
1513 _(b'TYPE'),
1514 ),
1514 ),
1515 ]
1515 ]
1516 + remoteopts,
1516 + remoteopts,
1517 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1517 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1518 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1518 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1519 )
1519 )
1520 def bundle(ui, repo, fname, dest=None, **opts):
1520 def bundle(ui, repo, fname, dest=None, **opts):
1521 """create a bundle file
1521 """create a bundle file
1522
1522
1523 Generate a bundle file containing data to be transferred to another
1523 Generate a bundle file containing data to be transferred to another
1524 repository.
1524 repository.
1525
1525
1526 To create a bundle containing all changesets, use -a/--all
1526 To create a bundle containing all changesets, use -a/--all
1527 (or --base null). Otherwise, hg assumes the destination will have
1527 (or --base null). Otherwise, hg assumes the destination will have
1528 all the nodes you specify with --base parameters. Otherwise, hg
1528 all the nodes you specify with --base parameters. Otherwise, hg
1529 will assume the repository has all the nodes in destination, or
1529 will assume the repository has all the nodes in destination, or
1530 default-push/default if no destination is specified, where destination
1530 default-push/default if no destination is specified, where destination
1531 is the repository you provide through DEST option.
1531 is the repository you provide through DEST option.
1532
1532
1533 You can change bundle format with the -t/--type option. See
1533 You can change bundle format with the -t/--type option. See
1534 :hg:`help bundlespec` for documentation on this format. By default,
1534 :hg:`help bundlespec` for documentation on this format. By default,
1535 the most appropriate format is used and compression defaults to
1535 the most appropriate format is used and compression defaults to
1536 bzip2.
1536 bzip2.
1537
1537
1538 The bundle file can then be transferred using conventional means
1538 The bundle file can then be transferred using conventional means
1539 and applied to another repository with the unbundle or pull
1539 and applied to another repository with the unbundle or pull
1540 command. This is useful when direct push and pull are not
1540 command. This is useful when direct push and pull are not
1541 available or when exporting an entire repository is undesirable.
1541 available or when exporting an entire repository is undesirable.
1542
1542
1543 Applying bundles preserves all changeset contents including
1543 Applying bundles preserves all changeset contents including
1544 permissions, copy/rename information, and revision history.
1544 permissions, copy/rename information, and revision history.
1545
1545
1546 Returns 0 on success, 1 if no changes found.
1546 Returns 0 on success, 1 if no changes found.
1547 """
1547 """
1548 opts = pycompat.byteskwargs(opts)
1548 opts = pycompat.byteskwargs(opts)
1549 revs = None
1549 revs = None
1550 if b'rev' in opts:
1550 if b'rev' in opts:
1551 revstrings = opts[b'rev']
1551 revstrings = opts[b'rev']
1552 revs = scmutil.revrange(repo, revstrings)
1552 revs = scmutil.revrange(repo, revstrings)
1553 if revstrings and not revs:
1553 if revstrings and not revs:
1554 raise error.Abort(_(b'no commits to bundle'))
1554 raise error.Abort(_(b'no commits to bundle'))
1555
1555
1556 bundletype = opts.get(b'type', b'bzip2').lower()
1556 bundletype = opts.get(b'type', b'bzip2').lower()
1557 try:
1557 try:
1558 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1558 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1559 except error.UnsupportedBundleSpecification as e:
1559 except error.UnsupportedBundleSpecification as e:
1560 raise error.Abort(
1560 raise error.Abort(
1561 pycompat.bytestr(e),
1561 pycompat.bytestr(e),
1562 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1562 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1563 )
1563 )
1564 cgversion = bundlespec.contentopts[b"cg.version"]
1564 cgversion = bundlespec.contentopts[b"cg.version"]
1565
1565
1566 # Packed bundles are a pseudo bundle format for now.
1566 # Packed bundles are a pseudo bundle format for now.
1567 if cgversion == b's1':
1567 if cgversion == b's1':
1568 raise error.Abort(
1568 raise error.Abort(
1569 _(b'packed bundles cannot be produced by "hg bundle"'),
1569 _(b'packed bundles cannot be produced by "hg bundle"'),
1570 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1570 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1571 )
1571 )
1572
1572
1573 if opts.get(b'all'):
1573 if opts.get(b'all'):
1574 if dest:
1574 if dest:
1575 raise error.Abort(
1575 raise error.Abort(
1576 _(b"--all is incompatible with specifying a destination")
1576 _(b"--all is incompatible with specifying a destination")
1577 )
1577 )
1578 if opts.get(b'base'):
1578 if opts.get(b'base'):
1579 ui.warn(_(b"ignoring --base because --all was specified\n"))
1579 ui.warn(_(b"ignoring --base because --all was specified\n"))
1580 base = [nullrev]
1580 base = [nullrev]
1581 else:
1581 else:
1582 base = scmutil.revrange(repo, opts.get(b'base'))
1582 base = scmutil.revrange(repo, opts.get(b'base'))
1583 if cgversion not in changegroup.supportedoutgoingversions(repo):
1583 if cgversion not in changegroup.supportedoutgoingversions(repo):
1584 raise error.Abort(
1584 raise error.Abort(
1585 _(b"repository does not support bundle version %s") % cgversion
1585 _(b"repository does not support bundle version %s") % cgversion
1586 )
1586 )
1587
1587
1588 if base:
1588 if base:
1589 if dest:
1589 if dest:
1590 raise error.Abort(
1590 raise error.Abort(
1591 _(b"--base is incompatible with specifying a destination")
1591 _(b"--base is incompatible with specifying a destination")
1592 )
1592 )
1593 common = [repo[rev].node() for rev in base]
1593 common = [repo[rev].node() for rev in base]
1594 heads = [repo[r].node() for r in revs] if revs else None
1594 heads = [repo[r].node() for r in revs] if revs else None
1595 outgoing = discovery.outgoing(repo, common, heads)
1595 outgoing = discovery.outgoing(repo, common, heads)
1596 else:
1596 else:
1597 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1597 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1598 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1598 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1599 other = hg.peer(repo, opts, dest)
1599 other = hg.peer(repo, opts, dest)
1600 revs = [repo[r].hex() for r in revs]
1600 revs = [repo[r].hex() for r in revs]
1601 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1601 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1602 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1602 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1603 outgoing = discovery.findcommonoutgoing(
1603 outgoing = discovery.findcommonoutgoing(
1604 repo,
1604 repo,
1605 other,
1605 other,
1606 onlyheads=heads,
1606 onlyheads=heads,
1607 force=opts.get(b'force'),
1607 force=opts.get(b'force'),
1608 portable=True,
1608 portable=True,
1609 )
1609 )
1610
1610
1611 if not outgoing.missing:
1611 if not outgoing.missing:
1612 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1612 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1613 return 1
1613 return 1
1614
1614
1615 if cgversion == b'01': # bundle1
1615 if cgversion == b'01': # bundle1
1616 bversion = b'HG10' + bundlespec.wirecompression
1616 bversion = b'HG10' + bundlespec.wirecompression
1617 bcompression = None
1617 bcompression = None
1618 elif cgversion in (b'02', b'03'):
1618 elif cgversion in (b'02', b'03'):
1619 bversion = b'HG20'
1619 bversion = b'HG20'
1620 bcompression = bundlespec.wirecompression
1620 bcompression = bundlespec.wirecompression
1621 else:
1621 else:
1622 raise error.ProgrammingError(
1622 raise error.ProgrammingError(
1623 b'bundle: unexpected changegroup version %s' % cgversion
1623 b'bundle: unexpected changegroup version %s' % cgversion
1624 )
1624 )
1625
1625
1626 # TODO compression options should be derived from bundlespec parsing.
1626 # TODO compression options should be derived from bundlespec parsing.
1627 # This is a temporary hack to allow adjusting bundle compression
1627 # This is a temporary hack to allow adjusting bundle compression
1628 # level without a) formalizing the bundlespec changes to declare it
1628 # level without a) formalizing the bundlespec changes to declare it
1629 # b) introducing a command flag.
1629 # b) introducing a command flag.
1630 compopts = {}
1630 compopts = {}
1631 complevel = ui.configint(
1631 complevel = ui.configint(
1632 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1632 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1633 )
1633 )
1634 if complevel is None:
1634 if complevel is None:
1635 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1635 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1636 if complevel is not None:
1636 if complevel is not None:
1637 compopts[b'level'] = complevel
1637 compopts[b'level'] = complevel
1638
1638
1639 # Allow overriding the bundling of obsmarker in phases through
1639 # Allow overriding the bundling of obsmarker in phases through
1640 # configuration while we don't have a bundle version that include them
1640 # configuration while we don't have a bundle version that include them
1641 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1641 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1642 bundlespec.contentopts[b'obsolescence'] = True
1642 bundlespec.contentopts[b'obsolescence'] = True
1643 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1643 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1644 bundlespec.contentopts[b'phases'] = True
1644 bundlespec.contentopts[b'phases'] = True
1645
1645
1646 bundle2.writenewbundle(
1646 bundle2.writenewbundle(
1647 ui,
1647 ui,
1648 repo,
1648 repo,
1649 b'bundle',
1649 b'bundle',
1650 fname,
1650 fname,
1651 bversion,
1651 bversion,
1652 outgoing,
1652 outgoing,
1653 bundlespec.contentopts,
1653 bundlespec.contentopts,
1654 compression=bcompression,
1654 compression=bcompression,
1655 compopts=compopts,
1655 compopts=compopts,
1656 )
1656 )
1657
1657
1658
1658
1659 @command(
1659 @command(
1660 b'cat',
1660 b'cat',
1661 [
1661 [
1662 (
1662 (
1663 b'o',
1663 b'o',
1664 b'output',
1664 b'output',
1665 b'',
1665 b'',
1666 _(b'print output to file with formatted name'),
1666 _(b'print output to file with formatted name'),
1667 _(b'FORMAT'),
1667 _(b'FORMAT'),
1668 ),
1668 ),
1669 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1669 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1670 (b'', b'decode', None, _(b'apply any matching decode filter')),
1670 (b'', b'decode', None, _(b'apply any matching decode filter')),
1671 ]
1671 ]
1672 + walkopts
1672 + walkopts
1673 + formatteropts,
1673 + formatteropts,
1674 _(b'[OPTION]... FILE...'),
1674 _(b'[OPTION]... FILE...'),
1675 helpcategory=command.CATEGORY_FILE_CONTENTS,
1675 helpcategory=command.CATEGORY_FILE_CONTENTS,
1676 inferrepo=True,
1676 inferrepo=True,
1677 intents={INTENT_READONLY},
1677 intents={INTENT_READONLY},
1678 )
1678 )
1679 def cat(ui, repo, file1, *pats, **opts):
1679 def cat(ui, repo, file1, *pats, **opts):
1680 """output the current or given revision of files
1680 """output the current or given revision of files
1681
1681
1682 Print the specified files as they were at the given revision. If
1682 Print the specified files as they were at the given revision. If
1683 no revision is given, the parent of the working directory is used.
1683 no revision is given, the parent of the working directory is used.
1684
1684
1685 Output may be to a file, in which case the name of the file is
1685 Output may be to a file, in which case the name of the file is
1686 given using a template string. See :hg:`help templates`. In addition
1686 given using a template string. See :hg:`help templates`. In addition
1687 to the common template keywords, the following formatting rules are
1687 to the common template keywords, the following formatting rules are
1688 supported:
1688 supported:
1689
1689
1690 :``%%``: literal "%" character
1690 :``%%``: literal "%" character
1691 :``%s``: basename of file being printed
1691 :``%s``: basename of file being printed
1692 :``%d``: dirname of file being printed, or '.' if in repository root
1692 :``%d``: dirname of file being printed, or '.' if in repository root
1693 :``%p``: root-relative path name of file being printed
1693 :``%p``: root-relative path name of file being printed
1694 :``%H``: changeset hash (40 hexadecimal digits)
1694 :``%H``: changeset hash (40 hexadecimal digits)
1695 :``%R``: changeset revision number
1695 :``%R``: changeset revision number
1696 :``%h``: short-form changeset hash (12 hexadecimal digits)
1696 :``%h``: short-form changeset hash (12 hexadecimal digits)
1697 :``%r``: zero-padded changeset revision number
1697 :``%r``: zero-padded changeset revision number
1698 :``%b``: basename of the exporting repository
1698 :``%b``: basename of the exporting repository
1699 :``\\``: literal "\\" character
1699 :``\\``: literal "\\" character
1700
1700
1701 .. container:: verbose
1701 .. container:: verbose
1702
1702
1703 Template:
1703 Template:
1704
1704
1705 The following keywords are supported in addition to the common template
1705 The following keywords are supported in addition to the common template
1706 keywords and functions. See also :hg:`help templates`.
1706 keywords and functions. See also :hg:`help templates`.
1707
1707
1708 :data: String. File content.
1708 :data: String. File content.
1709 :path: String. Repository-absolute path of the file.
1709 :path: String. Repository-absolute path of the file.
1710
1710
1711 Returns 0 on success.
1711 Returns 0 on success.
1712 """
1712 """
1713 opts = pycompat.byteskwargs(opts)
1713 opts = pycompat.byteskwargs(opts)
1714 rev = opts.get(b'rev')
1714 rev = opts.get(b'rev')
1715 if rev:
1715 if rev:
1716 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1716 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1717 ctx = scmutil.revsingle(repo, rev)
1717 ctx = scmutil.revsingle(repo, rev)
1718 m = scmutil.match(ctx, (file1,) + pats, opts)
1718 m = scmutil.match(ctx, (file1,) + pats, opts)
1719 fntemplate = opts.pop(b'output', b'')
1719 fntemplate = opts.pop(b'output', b'')
1720 if cmdutil.isstdiofilename(fntemplate):
1720 if cmdutil.isstdiofilename(fntemplate):
1721 fntemplate = b''
1721 fntemplate = b''
1722
1722
1723 if fntemplate:
1723 if fntemplate:
1724 fm = formatter.nullformatter(ui, b'cat', opts)
1724 fm = formatter.nullformatter(ui, b'cat', opts)
1725 else:
1725 else:
1726 ui.pager(b'cat')
1726 ui.pager(b'cat')
1727 fm = ui.formatter(b'cat', opts)
1727 fm = ui.formatter(b'cat', opts)
1728 with fm:
1728 with fm:
1729 return cmdutil.cat(
1729 return cmdutil.cat(
1730 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1730 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1731 )
1731 )
1732
1732
1733
1733
1734 @command(
1734 @command(
1735 b'clone',
1735 b'clone',
1736 [
1736 [
1737 (
1737 (
1738 b'U',
1738 b'U',
1739 b'noupdate',
1739 b'noupdate',
1740 None,
1740 None,
1741 _(
1741 _(
1742 b'the clone will include an empty working '
1742 b'the clone will include an empty working '
1743 b'directory (only a repository)'
1743 b'directory (only a repository)'
1744 ),
1744 ),
1745 ),
1745 ),
1746 (
1746 (
1747 b'u',
1747 b'u',
1748 b'updaterev',
1748 b'updaterev',
1749 b'',
1749 b'',
1750 _(b'revision, tag, or branch to check out'),
1750 _(b'revision, tag, or branch to check out'),
1751 _(b'REV'),
1751 _(b'REV'),
1752 ),
1752 ),
1753 (
1753 (
1754 b'r',
1754 b'r',
1755 b'rev',
1755 b'rev',
1756 [],
1756 [],
1757 _(
1757 _(
1758 b'do not clone everything, but include this changeset'
1758 b'do not clone everything, but include this changeset'
1759 b' and its ancestors'
1759 b' and its ancestors'
1760 ),
1760 ),
1761 _(b'REV'),
1761 _(b'REV'),
1762 ),
1762 ),
1763 (
1763 (
1764 b'b',
1764 b'b',
1765 b'branch',
1765 b'branch',
1766 [],
1766 [],
1767 _(
1767 _(
1768 b'do not clone everything, but include this branch\'s'
1768 b'do not clone everything, but include this branch\'s'
1769 b' changesets and their ancestors'
1769 b' changesets and their ancestors'
1770 ),
1770 ),
1771 _(b'BRANCH'),
1771 _(b'BRANCH'),
1772 ),
1772 ),
1773 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1773 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1774 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1774 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1775 (b'', b'stream', None, _(b'clone with minimal data processing')),
1775 (b'', b'stream', None, _(b'clone with minimal data processing')),
1776 ]
1776 ]
1777 + remoteopts,
1777 + remoteopts,
1778 _(b'[OPTION]... SOURCE [DEST]'),
1778 _(b'[OPTION]... SOURCE [DEST]'),
1779 helpcategory=command.CATEGORY_REPO_CREATION,
1779 helpcategory=command.CATEGORY_REPO_CREATION,
1780 helpbasic=True,
1780 helpbasic=True,
1781 norepo=True,
1781 norepo=True,
1782 )
1782 )
1783 def clone(ui, source, dest=None, **opts):
1783 def clone(ui, source, dest=None, **opts):
1784 """make a copy of an existing repository
1784 """make a copy of an existing repository
1785
1785
1786 Create a copy of an existing repository in a new directory.
1786 Create a copy of an existing repository in a new directory.
1787
1787
1788 If no destination directory name is specified, it defaults to the
1788 If no destination directory name is specified, it defaults to the
1789 basename of the source.
1789 basename of the source.
1790
1790
1791 The location of the source is added to the new repository's
1791 The location of the source is added to the new repository's
1792 ``.hg/hgrc`` file, as the default to be used for future pulls.
1792 ``.hg/hgrc`` file, as the default to be used for future pulls.
1793
1793
1794 Only local paths and ``ssh://`` URLs are supported as
1794 Only local paths and ``ssh://`` URLs are supported as
1795 destinations. For ``ssh://`` destinations, no working directory or
1795 destinations. For ``ssh://`` destinations, no working directory or
1796 ``.hg/hgrc`` will be created on the remote side.
1796 ``.hg/hgrc`` will be created on the remote side.
1797
1797
1798 If the source repository has a bookmark called '@' set, that
1798 If the source repository has a bookmark called '@' set, that
1799 revision will be checked out in the new repository by default.
1799 revision will be checked out in the new repository by default.
1800
1800
1801 To check out a particular version, use -u/--update, or
1801 To check out a particular version, use -u/--update, or
1802 -U/--noupdate to create a clone with no working directory.
1802 -U/--noupdate to create a clone with no working directory.
1803
1803
1804 To pull only a subset of changesets, specify one or more revisions
1804 To pull only a subset of changesets, specify one or more revisions
1805 identifiers with -r/--rev or branches with -b/--branch. The
1805 identifiers with -r/--rev or branches with -b/--branch. The
1806 resulting clone will contain only the specified changesets and
1806 resulting clone will contain only the specified changesets and
1807 their ancestors. These options (or 'clone src#rev dest') imply
1807 their ancestors. These options (or 'clone src#rev dest') imply
1808 --pull, even for local source repositories.
1808 --pull, even for local source repositories.
1809
1809
1810 In normal clone mode, the remote normalizes repository data into a common
1810 In normal clone mode, the remote normalizes repository data into a common
1811 exchange format and the receiving end translates this data into its local
1811 exchange format and the receiving end translates this data into its local
1812 storage format. --stream activates a different clone mode that essentially
1812 storage format. --stream activates a different clone mode that essentially
1813 copies repository files from the remote with minimal data processing. This
1813 copies repository files from the remote with minimal data processing. This
1814 significantly reduces the CPU cost of a clone both remotely and locally.
1814 significantly reduces the CPU cost of a clone both remotely and locally.
1815 However, it often increases the transferred data size by 30-40%. This can
1815 However, it often increases the transferred data size by 30-40%. This can
1816 result in substantially faster clones where I/O throughput is plentiful,
1816 result in substantially faster clones where I/O throughput is plentiful,
1817 especially for larger repositories. A side-effect of --stream clones is
1817 especially for larger repositories. A side-effect of --stream clones is
1818 that storage settings and requirements on the remote are applied locally:
1818 that storage settings and requirements on the remote are applied locally:
1819 a modern client may inherit legacy or inefficient storage used by the
1819 a modern client may inherit legacy or inefficient storage used by the
1820 remote or a legacy Mercurial client may not be able to clone from a
1820 remote or a legacy Mercurial client may not be able to clone from a
1821 modern Mercurial remote.
1821 modern Mercurial remote.
1822
1822
1823 .. note::
1823 .. note::
1824
1824
1825 Specifying a tag will include the tagged changeset but not the
1825 Specifying a tag will include the tagged changeset but not the
1826 changeset containing the tag.
1826 changeset containing the tag.
1827
1827
1828 .. container:: verbose
1828 .. container:: verbose
1829
1829
1830 For efficiency, hardlinks are used for cloning whenever the
1830 For efficiency, hardlinks are used for cloning whenever the
1831 source and destination are on the same filesystem (note this
1831 source and destination are on the same filesystem (note this
1832 applies only to the repository data, not to the working
1832 applies only to the repository data, not to the working
1833 directory). Some filesystems, such as AFS, implement hardlinking
1833 directory). Some filesystems, such as AFS, implement hardlinking
1834 incorrectly, but do not report errors. In these cases, use the
1834 incorrectly, but do not report errors. In these cases, use the
1835 --pull option to avoid hardlinking.
1835 --pull option to avoid hardlinking.
1836
1836
1837 Mercurial will update the working directory to the first applicable
1837 Mercurial will update the working directory to the first applicable
1838 revision from this list:
1838 revision from this list:
1839
1839
1840 a) null if -U or the source repository has no changesets
1840 a) null if -U or the source repository has no changesets
1841 b) if -u . and the source repository is local, the first parent of
1841 b) if -u . and the source repository is local, the first parent of
1842 the source repository's working directory
1842 the source repository's working directory
1843 c) the changeset specified with -u (if a branch name, this means the
1843 c) the changeset specified with -u (if a branch name, this means the
1844 latest head of that branch)
1844 latest head of that branch)
1845 d) the changeset specified with -r
1845 d) the changeset specified with -r
1846 e) the tipmost head specified with -b
1846 e) the tipmost head specified with -b
1847 f) the tipmost head specified with the url#branch source syntax
1847 f) the tipmost head specified with the url#branch source syntax
1848 g) the revision marked with the '@' bookmark, if present
1848 g) the revision marked with the '@' bookmark, if present
1849 h) the tipmost head of the default branch
1849 h) the tipmost head of the default branch
1850 i) tip
1850 i) tip
1851
1851
1852 When cloning from servers that support it, Mercurial may fetch
1852 When cloning from servers that support it, Mercurial may fetch
1853 pre-generated data from a server-advertised URL or inline from the
1853 pre-generated data from a server-advertised URL or inline from the
1854 same stream. When this is done, hooks operating on incoming changesets
1854 same stream. When this is done, hooks operating on incoming changesets
1855 and changegroups may fire more than once, once for each pre-generated
1855 and changegroups may fire more than once, once for each pre-generated
1856 bundle and as well as for any additional remaining data. In addition,
1856 bundle and as well as for any additional remaining data. In addition,
1857 if an error occurs, the repository may be rolled back to a partial
1857 if an error occurs, the repository may be rolled back to a partial
1858 clone. This behavior may change in future releases.
1858 clone. This behavior may change in future releases.
1859 See :hg:`help -e clonebundles` for more.
1859 See :hg:`help -e clonebundles` for more.
1860
1860
1861 Examples:
1861 Examples:
1862
1862
1863 - clone a remote repository to a new directory named hg/::
1863 - clone a remote repository to a new directory named hg/::
1864
1864
1865 hg clone https://www.mercurial-scm.org/repo/hg/
1865 hg clone https://www.mercurial-scm.org/repo/hg/
1866
1866
1867 - create a lightweight local clone::
1867 - create a lightweight local clone::
1868
1868
1869 hg clone project/ project-feature/
1869 hg clone project/ project-feature/
1870
1870
1871 - clone from an absolute path on an ssh server (note double-slash)::
1871 - clone from an absolute path on an ssh server (note double-slash)::
1872
1872
1873 hg clone ssh://user@server//home/projects/alpha/
1873 hg clone ssh://user@server//home/projects/alpha/
1874
1874
1875 - do a streaming clone while checking out a specified version::
1875 - do a streaming clone while checking out a specified version::
1876
1876
1877 hg clone --stream http://server/repo -u 1.5
1877 hg clone --stream http://server/repo -u 1.5
1878
1878
1879 - create a repository without changesets after a particular revision::
1879 - create a repository without changesets after a particular revision::
1880
1880
1881 hg clone -r 04e544 experimental/ good/
1881 hg clone -r 04e544 experimental/ good/
1882
1882
1883 - clone (and track) a particular named branch::
1883 - clone (and track) a particular named branch::
1884
1884
1885 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1885 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1886
1886
1887 See :hg:`help urls` for details on specifying URLs.
1887 See :hg:`help urls` for details on specifying URLs.
1888
1888
1889 Returns 0 on success.
1889 Returns 0 on success.
1890 """
1890 """
1891 opts = pycompat.byteskwargs(opts)
1891 opts = pycompat.byteskwargs(opts)
1892 if opts.get(b'noupdate') and opts.get(b'updaterev'):
1892 if opts.get(b'noupdate') and opts.get(b'updaterev'):
1893 raise error.Abort(_(b"cannot specify both --noupdate and --updaterev"))
1893 raise error.Abort(_(b"cannot specify both --noupdate and --updaterev"))
1894
1894
1895 # --include/--exclude can come from narrow or sparse.
1895 # --include/--exclude can come from narrow or sparse.
1896 includepats, excludepats = None, None
1896 includepats, excludepats = None, None
1897
1897
1898 # hg.clone() differentiates between None and an empty set. So make sure
1898 # hg.clone() differentiates between None and an empty set. So make sure
1899 # patterns are sets if narrow is requested without patterns.
1899 # patterns are sets if narrow is requested without patterns.
1900 if opts.get(b'narrow'):
1900 if opts.get(b'narrow'):
1901 includepats = set()
1901 includepats = set()
1902 excludepats = set()
1902 excludepats = set()
1903
1903
1904 if opts.get(b'include'):
1904 if opts.get(b'include'):
1905 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1905 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1906 if opts.get(b'exclude'):
1906 if opts.get(b'exclude'):
1907 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1907 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1908
1908
1909 r = hg.clone(
1909 r = hg.clone(
1910 ui,
1910 ui,
1911 opts,
1911 opts,
1912 source,
1912 source,
1913 dest,
1913 dest,
1914 pull=opts.get(b'pull'),
1914 pull=opts.get(b'pull'),
1915 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1915 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1916 revs=opts.get(b'rev'),
1916 revs=opts.get(b'rev'),
1917 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1917 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1918 branch=opts.get(b'branch'),
1918 branch=opts.get(b'branch'),
1919 shareopts=opts.get(b'shareopts'),
1919 shareopts=opts.get(b'shareopts'),
1920 storeincludepats=includepats,
1920 storeincludepats=includepats,
1921 storeexcludepats=excludepats,
1921 storeexcludepats=excludepats,
1922 depth=opts.get(b'depth') or None,
1922 depth=opts.get(b'depth') or None,
1923 )
1923 )
1924
1924
1925 return r is None
1925 return r is None
1926
1926
1927
1927
1928 @command(
1928 @command(
1929 b'commit|ci',
1929 b'commit|ci',
1930 [
1930 [
1931 (
1931 (
1932 b'A',
1932 b'A',
1933 b'addremove',
1933 b'addremove',
1934 None,
1934 None,
1935 _(b'mark new/missing files as added/removed before committing'),
1935 _(b'mark new/missing files as added/removed before committing'),
1936 ),
1936 ),
1937 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1937 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1938 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1938 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1939 (b's', b'secret', None, _(b'use the secret phase for committing')),
1939 (b's', b'secret', None, _(b'use the secret phase for committing')),
1940 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1940 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1941 (
1941 (
1942 b'',
1942 b'',
1943 b'force-close-branch',
1943 b'force-close-branch',
1944 None,
1944 None,
1945 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1945 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1946 ),
1946 ),
1947 (b'i', b'interactive', None, _(b'use interactive mode')),
1947 (b'i', b'interactive', None, _(b'use interactive mode')),
1948 ]
1948 ]
1949 + walkopts
1949 + walkopts
1950 + commitopts
1950 + commitopts
1951 + commitopts2
1951 + commitopts2
1952 + subrepoopts,
1952 + subrepoopts,
1953 _(b'[OPTION]... [FILE]...'),
1953 _(b'[OPTION]... [FILE]...'),
1954 helpcategory=command.CATEGORY_COMMITTING,
1954 helpcategory=command.CATEGORY_COMMITTING,
1955 helpbasic=True,
1955 helpbasic=True,
1956 inferrepo=True,
1956 inferrepo=True,
1957 )
1957 )
1958 def commit(ui, repo, *pats, **opts):
1958 def commit(ui, repo, *pats, **opts):
1959 """commit the specified files or all outstanding changes
1959 """commit the specified files or all outstanding changes
1960
1960
1961 Commit changes to the given files into the repository. Unlike a
1961 Commit changes to the given files into the repository. Unlike a
1962 centralized SCM, this operation is a local operation. See
1962 centralized SCM, this operation is a local operation. See
1963 :hg:`push` for a way to actively distribute your changes.
1963 :hg:`push` for a way to actively distribute your changes.
1964
1964
1965 If a list of files is omitted, all changes reported by :hg:`status`
1965 If a list of files is omitted, all changes reported by :hg:`status`
1966 will be committed.
1966 will be committed.
1967
1967
1968 If you are committing the result of a merge, do not provide any
1968 If you are committing the result of a merge, do not provide any
1969 filenames or -I/-X filters.
1969 filenames or -I/-X filters.
1970
1970
1971 If no commit message is specified, Mercurial starts your
1971 If no commit message is specified, Mercurial starts your
1972 configured editor where you can enter a message. In case your
1972 configured editor where you can enter a message. In case your
1973 commit fails, you will find a backup of your message in
1973 commit fails, you will find a backup of your message in
1974 ``.hg/last-message.txt``.
1974 ``.hg/last-message.txt``.
1975
1975
1976 The --close-branch flag can be used to mark the current branch
1976 The --close-branch flag can be used to mark the current branch
1977 head closed. When all heads of a branch are closed, the branch
1977 head closed. When all heads of a branch are closed, the branch
1978 will be considered closed and no longer listed.
1978 will be considered closed and no longer listed.
1979
1979
1980 The --amend flag can be used to amend the parent of the
1980 The --amend flag can be used to amend the parent of the
1981 working directory with a new commit that contains the changes
1981 working directory with a new commit that contains the changes
1982 in the parent in addition to those currently reported by :hg:`status`,
1982 in the parent in addition to those currently reported by :hg:`status`,
1983 if there are any. The old commit is stored in a backup bundle in
1983 if there are any. The old commit is stored in a backup bundle in
1984 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1984 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1985 on how to restore it).
1985 on how to restore it).
1986
1986
1987 Message, user and date are taken from the amended commit unless
1987 Message, user and date are taken from the amended commit unless
1988 specified. When a message isn't specified on the command line,
1988 specified. When a message isn't specified on the command line,
1989 the editor will open with the message of the amended commit.
1989 the editor will open with the message of the amended commit.
1990
1990
1991 It is not possible to amend public changesets (see :hg:`help phases`)
1991 It is not possible to amend public changesets (see :hg:`help phases`)
1992 or changesets that have children.
1992 or changesets that have children.
1993
1993
1994 See :hg:`help dates` for a list of formats valid for -d/--date.
1994 See :hg:`help dates` for a list of formats valid for -d/--date.
1995
1995
1996 Returns 0 on success, 1 if nothing changed.
1996 Returns 0 on success, 1 if nothing changed.
1997
1997
1998 .. container:: verbose
1998 .. container:: verbose
1999
1999
2000 Examples:
2000 Examples:
2001
2001
2002 - commit all files ending in .py::
2002 - commit all files ending in .py::
2003
2003
2004 hg commit --include "set:**.py"
2004 hg commit --include "set:**.py"
2005
2005
2006 - commit all non-binary files::
2006 - commit all non-binary files::
2007
2007
2008 hg commit --exclude "set:binary()"
2008 hg commit --exclude "set:binary()"
2009
2009
2010 - amend the current commit and set the date to now::
2010 - amend the current commit and set the date to now::
2011
2011
2012 hg commit --amend --date now
2012 hg commit --amend --date now
2013 """
2013 """
2014 with repo.wlock(), repo.lock():
2014 with repo.wlock(), repo.lock():
2015 return _docommit(ui, repo, *pats, **opts)
2015 return _docommit(ui, repo, *pats, **opts)
2016
2016
2017
2017
2018 def _docommit(ui, repo, *pats, **opts):
2018 def _docommit(ui, repo, *pats, **opts):
2019 if opts.get('interactive'):
2019 if opts.get('interactive'):
2020 opts.pop('interactive')
2020 opts.pop('interactive')
2021 ret = cmdutil.dorecord(
2021 ret = cmdutil.dorecord(
2022 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2022 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2023 )
2023 )
2024 # ret can be 0 (no changes to record) or the value returned by
2024 # ret can be 0 (no changes to record) or the value returned by
2025 # commit(), 1 if nothing changed or None on success.
2025 # commit(), 1 if nothing changed or None on success.
2026 return 1 if ret == 0 else ret
2026 return 1 if ret == 0 else ret
2027
2027
2028 opts = pycompat.byteskwargs(opts)
2028 opts = pycompat.byteskwargs(opts)
2029 if opts.get(b'subrepos'):
2029 if opts.get(b'subrepos'):
2030 if opts.get(b'amend'):
2030 if opts.get(b'amend'):
2031 raise error.Abort(_(b'cannot amend with --subrepos'))
2031 raise error.Abort(_(b'cannot amend with --subrepos'))
2032 # Let --subrepos on the command line override config setting.
2032 # Let --subrepos on the command line override config setting.
2033 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2033 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2034
2034
2035 cmdutil.checkunfinished(repo, commit=True)
2035 cmdutil.checkunfinished(repo, commit=True)
2036
2036
2037 branch = repo[None].branch()
2037 branch = repo[None].branch()
2038 bheads = repo.branchheads(branch)
2038 bheads = repo.branchheads(branch)
2039
2039
2040 extra = {}
2040 extra = {}
2041 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2041 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2042 extra[b'close'] = b'1'
2042 extra[b'close'] = b'1'
2043
2043
2044 if repo[b'.'].closesbranch():
2044 if repo[b'.'].closesbranch():
2045 raise error.Abort(
2045 raise error.Abort(
2046 _(b'current revision is already a branch closing head')
2046 _(b'current revision is already a branch closing head')
2047 )
2047 )
2048 elif not bheads:
2048 elif not bheads:
2049 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2049 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2050 elif (
2050 elif (
2051 branch == repo[b'.'].branch()
2051 branch == repo[b'.'].branch()
2052 and repo[b'.'].node() not in bheads
2052 and repo[b'.'].node() not in bheads
2053 and not opts.get(b'force_close_branch')
2053 and not opts.get(b'force_close_branch')
2054 ):
2054 ):
2055 hint = _(
2055 hint = _(
2056 b'use --force-close-branch to close branch from a non-head'
2056 b'use --force-close-branch to close branch from a non-head'
2057 b' changeset'
2057 b' changeset'
2058 )
2058 )
2059 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2059 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2060 elif opts.get(b'amend'):
2060 elif opts.get(b'amend'):
2061 if (
2061 if (
2062 repo[b'.'].p1().branch() != branch
2062 repo[b'.'].p1().branch() != branch
2063 and repo[b'.'].p2().branch() != branch
2063 and repo[b'.'].p2().branch() != branch
2064 ):
2064 ):
2065 raise error.Abort(_(b'can only close branch heads'))
2065 raise error.Abort(_(b'can only close branch heads'))
2066
2066
2067 if opts.get(b'amend'):
2067 if opts.get(b'amend'):
2068 if ui.configbool(b'ui', b'commitsubrepos'):
2068 if ui.configbool(b'ui', b'commitsubrepos'):
2069 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2069 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2070
2070
2071 old = repo[b'.']
2071 old = repo[b'.']
2072 rewriteutil.precheck(repo, [old.rev()], b'amend')
2072 rewriteutil.precheck(repo, [old.rev()], b'amend')
2073
2073
2074 # Currently histedit gets confused if an amend happens while histedit
2074 # Currently histedit gets confused if an amend happens while histedit
2075 # is in progress. Since we have a checkunfinished command, we are
2075 # is in progress. Since we have a checkunfinished command, we are
2076 # temporarily honoring it.
2076 # temporarily honoring it.
2077 #
2077 #
2078 # Note: eventually this guard will be removed. Please do not expect
2078 # Note: eventually this guard will be removed. Please do not expect
2079 # this behavior to remain.
2079 # this behavior to remain.
2080 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2080 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2081 cmdutil.checkunfinished(repo)
2081 cmdutil.checkunfinished(repo)
2082
2082
2083 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2083 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2084 if node == old.node():
2084 if node == old.node():
2085 ui.status(_(b"nothing changed\n"))
2085 ui.status(_(b"nothing changed\n"))
2086 return 1
2086 return 1
2087 else:
2087 else:
2088
2088
2089 def commitfunc(ui, repo, message, match, opts):
2089 def commitfunc(ui, repo, message, match, opts):
2090 overrides = {}
2090 overrides = {}
2091 if opts.get(b'secret'):
2091 if opts.get(b'secret'):
2092 overrides[(b'phases', b'new-commit')] = b'secret'
2092 overrides[(b'phases', b'new-commit')] = b'secret'
2093
2093
2094 baseui = repo.baseui
2094 baseui = repo.baseui
2095 with baseui.configoverride(overrides, b'commit'):
2095 with baseui.configoverride(overrides, b'commit'):
2096 with ui.configoverride(overrides, b'commit'):
2096 with ui.configoverride(overrides, b'commit'):
2097 editform = cmdutil.mergeeditform(
2097 editform = cmdutil.mergeeditform(
2098 repo[None], b'commit.normal'
2098 repo[None], b'commit.normal'
2099 )
2099 )
2100 editor = cmdutil.getcommiteditor(
2100 editor = cmdutil.getcommiteditor(
2101 editform=editform, **pycompat.strkwargs(opts)
2101 editform=editform, **pycompat.strkwargs(opts)
2102 )
2102 )
2103 return repo.commit(
2103 return repo.commit(
2104 message,
2104 message,
2105 opts.get(b'user'),
2105 opts.get(b'user'),
2106 opts.get(b'date'),
2106 opts.get(b'date'),
2107 match,
2107 match,
2108 editor=editor,
2108 editor=editor,
2109 extra=extra,
2109 extra=extra,
2110 )
2110 )
2111
2111
2112 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2112 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2113
2113
2114 if not node:
2114 if not node:
2115 stat = cmdutil.postcommitstatus(repo, pats, opts)
2115 stat = cmdutil.postcommitstatus(repo, pats, opts)
2116 if stat.deleted:
2116 if stat.deleted:
2117 ui.status(
2117 ui.status(
2118 _(
2118 _(
2119 b"nothing changed (%d missing files, see "
2119 b"nothing changed (%d missing files, see "
2120 b"'hg status')\n"
2120 b"'hg status')\n"
2121 )
2121 )
2122 % len(stat.deleted)
2122 % len(stat.deleted)
2123 )
2123 )
2124 else:
2124 else:
2125 ui.status(_(b"nothing changed\n"))
2125 ui.status(_(b"nothing changed\n"))
2126 return 1
2126 return 1
2127
2127
2128 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2128 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2129
2129
2130 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2130 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2131 status(
2131 status(
2132 ui,
2132 ui,
2133 repo,
2133 repo,
2134 modified=True,
2134 modified=True,
2135 added=True,
2135 added=True,
2136 removed=True,
2136 removed=True,
2137 deleted=True,
2137 deleted=True,
2138 unknown=True,
2138 unknown=True,
2139 subrepos=opts.get(b'subrepos'),
2139 subrepos=opts.get(b'subrepos'),
2140 )
2140 )
2141
2141
2142
2142
2143 @command(
2143 @command(
2144 b'config|showconfig|debugconfig',
2144 b'config|showconfig|debugconfig',
2145 [
2145 [
2146 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2146 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2147 (b'e', b'edit', None, _(b'edit user config')),
2147 (b'e', b'edit', None, _(b'edit user config')),
2148 (b'l', b'local', None, _(b'edit repository config')),
2148 (b'l', b'local', None, _(b'edit repository config')),
2149 (b'g', b'global', None, _(b'edit global config')),
2149 (b'g', b'global', None, _(b'edit global config')),
2150 ]
2150 ]
2151 + formatteropts,
2151 + formatteropts,
2152 _(b'[-u] [NAME]...'),
2152 _(b'[-u] [NAME]...'),
2153 helpcategory=command.CATEGORY_HELP,
2153 helpcategory=command.CATEGORY_HELP,
2154 optionalrepo=True,
2154 optionalrepo=True,
2155 intents={INTENT_READONLY},
2155 intents={INTENT_READONLY},
2156 )
2156 )
2157 def config(ui, repo, *values, **opts):
2157 def config(ui, repo, *values, **opts):
2158 """show combined config settings from all hgrc files
2158 """show combined config settings from all hgrc files
2159
2159
2160 With no arguments, print names and values of all config items.
2160 With no arguments, print names and values of all config items.
2161
2161
2162 With one argument of the form section.name, print just the value
2162 With one argument of the form section.name, print just the value
2163 of that config item.
2163 of that config item.
2164
2164
2165 With multiple arguments, print names and values of all config
2165 With multiple arguments, print names and values of all config
2166 items with matching section names or section.names.
2166 items with matching section names or section.names.
2167
2167
2168 With --edit, start an editor on the user-level config file. With
2168 With --edit, start an editor on the user-level config file. With
2169 --global, edit the system-wide config file. With --local, edit the
2169 --global, edit the system-wide config file. With --local, edit the
2170 repository-level config file.
2170 repository-level config file.
2171
2171
2172 With --debug, the source (filename and line number) is printed
2172 With --debug, the source (filename and line number) is printed
2173 for each config item.
2173 for each config item.
2174
2174
2175 See :hg:`help config` for more information about config files.
2175 See :hg:`help config` for more information about config files.
2176
2176
2177 .. container:: verbose
2177 .. container:: verbose
2178
2178
2179 Template:
2179 Template:
2180
2180
2181 The following keywords are supported. See also :hg:`help templates`.
2181 The following keywords are supported. See also :hg:`help templates`.
2182
2182
2183 :name: String. Config name.
2183 :name: String. Config name.
2184 :source: String. Filename and line number where the item is defined.
2184 :source: String. Filename and line number where the item is defined.
2185 :value: String. Config value.
2185 :value: String. Config value.
2186
2186
2187 Returns 0 on success, 1 if NAME does not exist.
2187 Returns 0 on success, 1 if NAME does not exist.
2188
2188
2189 """
2189 """
2190
2190
2191 opts = pycompat.byteskwargs(opts)
2191 opts = pycompat.byteskwargs(opts)
2192 if opts.get(b'edit') or opts.get(b'local') or opts.get(b'global'):
2192 if opts.get(b'edit') or opts.get(b'local') or opts.get(b'global'):
2193 if opts.get(b'local') and opts.get(b'global'):
2193 if opts.get(b'local') and opts.get(b'global'):
2194 raise error.Abort(_(b"can't use --local and --global together"))
2194 raise error.Abort(_(b"can't use --local and --global together"))
2195
2195
2196 if opts.get(b'local'):
2196 if opts.get(b'local'):
2197 if not repo:
2197 if not repo:
2198 raise error.Abort(_(b"can't use --local outside a repository"))
2198 raise error.Abort(_(b"can't use --local outside a repository"))
2199 paths = [repo.vfs.join(b'hgrc')]
2199 paths = [repo.vfs.join(b'hgrc')]
2200 elif opts.get(b'global'):
2200 elif opts.get(b'global'):
2201 paths = rcutil.systemrcpath()
2201 paths = rcutil.systemrcpath()
2202 else:
2202 else:
2203 paths = rcutil.userrcpath()
2203 paths = rcutil.userrcpath()
2204
2204
2205 for f in paths:
2205 for f in paths:
2206 if os.path.exists(f):
2206 if os.path.exists(f):
2207 break
2207 break
2208 else:
2208 else:
2209 if opts.get(b'global'):
2209 if opts.get(b'global'):
2210 samplehgrc = uimod.samplehgrcs[b'global']
2210 samplehgrc = uimod.samplehgrcs[b'global']
2211 elif opts.get(b'local'):
2211 elif opts.get(b'local'):
2212 samplehgrc = uimod.samplehgrcs[b'local']
2212 samplehgrc = uimod.samplehgrcs[b'local']
2213 else:
2213 else:
2214 samplehgrc = uimod.samplehgrcs[b'user']
2214 samplehgrc = uimod.samplehgrcs[b'user']
2215
2215
2216 f = paths[0]
2216 f = paths[0]
2217 fp = open(f, b"wb")
2217 fp = open(f, b"wb")
2218 fp.write(util.tonativeeol(samplehgrc))
2218 fp.write(util.tonativeeol(samplehgrc))
2219 fp.close()
2219 fp.close()
2220
2220
2221 editor = ui.geteditor()
2221 editor = ui.geteditor()
2222 ui.system(
2222 ui.system(
2223 b"%s \"%s\"" % (editor, f),
2223 b"%s \"%s\"" % (editor, f),
2224 onerr=error.Abort,
2224 onerr=error.Abort,
2225 errprefix=_(b"edit failed"),
2225 errprefix=_(b"edit failed"),
2226 blockedtag=b'config_edit',
2226 blockedtag=b'config_edit',
2227 )
2227 )
2228 return
2228 return
2229 ui.pager(b'config')
2229 ui.pager(b'config')
2230 fm = ui.formatter(b'config', opts)
2230 fm = ui.formatter(b'config', opts)
2231 for t, f in rcutil.rccomponents():
2231 for t, f in rcutil.rccomponents():
2232 if t == b'path':
2232 if t == b'path':
2233 ui.debug(b'read config from: %s\n' % f)
2233 ui.debug(b'read config from: %s\n' % f)
2234 elif t == b'items':
2234 elif t == b'items':
2235 for section, name, value, source in f:
2235 for section, name, value, source in f:
2236 ui.debug(b'set config by: %s\n' % source)
2236 ui.debug(b'set config by: %s\n' % source)
2237 else:
2237 else:
2238 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2238 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2239 untrusted = bool(opts.get(b'untrusted'))
2239 untrusted = bool(opts.get(b'untrusted'))
2240
2240
2241 selsections = selentries = []
2241 selsections = selentries = []
2242 if values:
2242 if values:
2243 selsections = [v for v in values if b'.' not in v]
2243 selsections = [v for v in values if b'.' not in v]
2244 selentries = [v for v in values if b'.' in v]
2244 selentries = [v for v in values if b'.' in v]
2245 uniquesel = len(selentries) == 1 and not selsections
2245 uniquesel = len(selentries) == 1 and not selsections
2246 selsections = set(selsections)
2246 selsections = set(selsections)
2247 selentries = set(selentries)
2247 selentries = set(selentries)
2248
2248
2249 matched = False
2249 matched = False
2250 for section, name, value in ui.walkconfig(untrusted=untrusted):
2250 for section, name, value in ui.walkconfig(untrusted=untrusted):
2251 source = ui.configsource(section, name, untrusted)
2251 source = ui.configsource(section, name, untrusted)
2252 value = pycompat.bytestr(value)
2252 value = pycompat.bytestr(value)
2253 defaultvalue = ui.configdefault(section, name)
2253 defaultvalue = ui.configdefault(section, name)
2254 if fm.isplain():
2254 if fm.isplain():
2255 source = source or b'none'
2255 source = source or b'none'
2256 value = value.replace(b'\n', b'\\n')
2256 value = value.replace(b'\n', b'\\n')
2257 entryname = section + b'.' + name
2257 entryname = section + b'.' + name
2258 if values and not (section in selsections or entryname in selentries):
2258 if values and not (section in selsections or entryname in selentries):
2259 continue
2259 continue
2260 fm.startitem()
2260 fm.startitem()
2261 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2261 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2262 if uniquesel:
2262 if uniquesel:
2263 fm.data(name=entryname)
2263 fm.data(name=entryname)
2264 fm.write(b'value', b'%s\n', value)
2264 fm.write(b'value', b'%s\n', value)
2265 else:
2265 else:
2266 fm.write(b'name value', b'%s=%s\n', entryname, value)
2266 fm.write(b'name value', b'%s=%s\n', entryname, value)
2267 if formatter.isprintable(defaultvalue):
2267 if formatter.isprintable(defaultvalue):
2268 fm.data(defaultvalue=defaultvalue)
2268 fm.data(defaultvalue=defaultvalue)
2269 elif isinstance(defaultvalue, list) and all(
2269 elif isinstance(defaultvalue, list) and all(
2270 formatter.isprintable(e) for e in defaultvalue
2270 formatter.isprintable(e) for e in defaultvalue
2271 ):
2271 ):
2272 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2272 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2273 # TODO: no idea how to process unsupported defaultvalue types
2273 # TODO: no idea how to process unsupported defaultvalue types
2274 matched = True
2274 matched = True
2275 fm.end()
2275 fm.end()
2276 if matched:
2276 if matched:
2277 return 0
2277 return 0
2278 return 1
2278 return 1
2279
2279
2280
2280
2281 @command(
2281 @command(
2282 b'continue',
2282 b'continue',
2283 dryrunopts,
2283 dryrunopts,
2284 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2284 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2285 helpbasic=True,
2285 helpbasic=True,
2286 )
2286 )
2287 def continuecmd(ui, repo, **opts):
2287 def continuecmd(ui, repo, **opts):
2288 """resumes an interrupted operation (EXPERIMENTAL)
2288 """resumes an interrupted operation (EXPERIMENTAL)
2289
2289
2290 Finishes a multistep operation like graft, histedit, rebase, merge,
2290 Finishes a multistep operation like graft, histedit, rebase, merge,
2291 and unshelve if they are in an interrupted state.
2291 and unshelve if they are in an interrupted state.
2292
2292
2293 use --dry-run/-n to dry run the command.
2293 use --dry-run/-n to dry run the command.
2294 """
2294 """
2295 dryrun = opts.get('dry_run')
2295 dryrun = opts.get('dry_run')
2296 contstate = cmdutil.getunfinishedstate(repo)
2296 contstate = cmdutil.getunfinishedstate(repo)
2297 if not contstate:
2297 if not contstate:
2298 raise error.Abort(_(b'no operation in progress'))
2298 raise error.Abort(_(b'no operation in progress'))
2299 if not contstate.continuefunc:
2299 if not contstate.continuefunc:
2300 raise error.Abort(
2300 raise error.Abort(
2301 (
2301 (
2302 _(b"%s in progress but does not support 'hg continue'")
2302 _(b"%s in progress but does not support 'hg continue'")
2303 % (contstate._opname)
2303 % (contstate._opname)
2304 ),
2304 ),
2305 hint=contstate.continuemsg(),
2305 hint=contstate.continuemsg(),
2306 )
2306 )
2307 if dryrun:
2307 if dryrun:
2308 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2308 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2309 return
2309 return
2310 return contstate.continuefunc(ui, repo)
2310 return contstate.continuefunc(ui, repo)
2311
2311
2312
2312
2313 @command(
2313 @command(
2314 b'copy|cp',
2314 b'copy|cp',
2315 [
2315 [
2316 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2316 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2317 (
2317 (
2318 b'f',
2318 b'f',
2319 b'force',
2319 b'force',
2320 None,
2320 None,
2321 _(b'forcibly copy over an existing managed file'),
2321 _(b'forcibly copy over an existing managed file'),
2322 ),
2322 ),
2323 ]
2323 ]
2324 + walkopts
2324 + walkopts
2325 + dryrunopts,
2325 + dryrunopts,
2326 _(b'[OPTION]... SOURCE... DEST'),
2326 _(b'[OPTION]... SOURCE... DEST'),
2327 helpcategory=command.CATEGORY_FILE_CONTENTS,
2327 helpcategory=command.CATEGORY_FILE_CONTENTS,
2328 )
2328 )
2329 def copy(ui, repo, *pats, **opts):
2329 def copy(ui, repo, *pats, **opts):
2330 """mark files as copied for the next commit
2330 """mark files as copied for the next commit
2331
2331
2332 Mark dest as having copies of source files. If dest is a
2332 Mark dest as having copies of source files. If dest is a
2333 directory, copies are put in that directory. If dest is a file,
2333 directory, copies are put in that directory. If dest is a file,
2334 the source must be a single file.
2334 the source must be a single file.
2335
2335
2336 By default, this command copies the contents of files as they
2336 By default, this command copies the contents of files as they
2337 exist in the working directory. If invoked with -A/--after, the
2337 exist in the working directory. If invoked with -A/--after, the
2338 operation is recorded, but no copying is performed.
2338 operation is recorded, but no copying is performed.
2339
2339
2340 This command takes effect with the next commit. To undo a copy
2340 This command takes effect with the next commit. To undo a copy
2341 before that, see :hg:`revert`.
2341 before that, see :hg:`revert`.
2342
2342
2343 Returns 0 on success, 1 if errors are encountered.
2343 Returns 0 on success, 1 if errors are encountered.
2344 """
2344 """
2345 opts = pycompat.byteskwargs(opts)
2345 opts = pycompat.byteskwargs(opts)
2346 with repo.wlock(False):
2346 with repo.wlock(False):
2347 return cmdutil.copy(ui, repo, pats, opts)
2347 return cmdutil.copy(ui, repo, pats, opts)
2348
2348
2349
2349
2350 @command(
2350 @command(
2351 b'debugcommands',
2351 b'debugcommands',
2352 [],
2352 [],
2353 _(b'[COMMAND]'),
2353 _(b'[COMMAND]'),
2354 helpcategory=command.CATEGORY_HELP,
2354 helpcategory=command.CATEGORY_HELP,
2355 norepo=True,
2355 norepo=True,
2356 )
2356 )
2357 def debugcommands(ui, cmd=b'', *args):
2357 def debugcommands(ui, cmd=b'', *args):
2358 """list all available commands and options"""
2358 """list all available commands and options"""
2359 for cmd, vals in sorted(pycompat.iteritems(table)):
2359 for cmd, vals in sorted(pycompat.iteritems(table)):
2360 cmd = cmd.split(b'|')[0]
2360 cmd = cmd.split(b'|')[0]
2361 opts = b', '.join([i[1] for i in vals[1]])
2361 opts = b', '.join([i[1] for i in vals[1]])
2362 ui.write(b'%s: %s\n' % (cmd, opts))
2362 ui.write(b'%s: %s\n' % (cmd, opts))
2363
2363
2364
2364
2365 @command(
2365 @command(
2366 b'debugcomplete',
2366 b'debugcomplete',
2367 [(b'o', b'options', None, _(b'show the command options'))],
2367 [(b'o', b'options', None, _(b'show the command options'))],
2368 _(b'[-o] CMD'),
2368 _(b'[-o] CMD'),
2369 helpcategory=command.CATEGORY_HELP,
2369 helpcategory=command.CATEGORY_HELP,
2370 norepo=True,
2370 norepo=True,
2371 )
2371 )
2372 def debugcomplete(ui, cmd=b'', **opts):
2372 def debugcomplete(ui, cmd=b'', **opts):
2373 """returns the completion list associated with the given command"""
2373 """returns the completion list associated with the given command"""
2374
2374
2375 if opts.get('options'):
2375 if opts.get('options'):
2376 options = []
2376 options = []
2377 otables = [globalopts]
2377 otables = [globalopts]
2378 if cmd:
2378 if cmd:
2379 aliases, entry = cmdutil.findcmd(cmd, table, False)
2379 aliases, entry = cmdutil.findcmd(cmd, table, False)
2380 otables.append(entry[1])
2380 otables.append(entry[1])
2381 for t in otables:
2381 for t in otables:
2382 for o in t:
2382 for o in t:
2383 if b"(DEPRECATED)" in o[3]:
2383 if b"(DEPRECATED)" in o[3]:
2384 continue
2384 continue
2385 if o[0]:
2385 if o[0]:
2386 options.append(b'-%s' % o[0])
2386 options.append(b'-%s' % o[0])
2387 options.append(b'--%s' % o[1])
2387 options.append(b'--%s' % o[1])
2388 ui.write(b"%s\n" % b"\n".join(options))
2388 ui.write(b"%s\n" % b"\n".join(options))
2389 return
2389 return
2390
2390
2391 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2391 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2392 if ui.verbose:
2392 if ui.verbose:
2393 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2393 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2394 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2394 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2395
2395
2396
2396
2397 @command(
2397 @command(
2398 b'diff',
2398 b'diff',
2399 [
2399 [
2400 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2400 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2401 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2401 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2402 ]
2402 ]
2403 + diffopts
2403 + diffopts
2404 + diffopts2
2404 + diffopts2
2405 + walkopts
2405 + walkopts
2406 + subrepoopts,
2406 + subrepoopts,
2407 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2407 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2408 helpcategory=command.CATEGORY_FILE_CONTENTS,
2408 helpcategory=command.CATEGORY_FILE_CONTENTS,
2409 helpbasic=True,
2409 helpbasic=True,
2410 inferrepo=True,
2410 inferrepo=True,
2411 intents={INTENT_READONLY},
2411 intents={INTENT_READONLY},
2412 )
2412 )
2413 def diff(ui, repo, *pats, **opts):
2413 def diff(ui, repo, *pats, **opts):
2414 """diff repository (or selected files)
2414 """diff repository (or selected files)
2415
2415
2416 Show differences between revisions for the specified files.
2416 Show differences between revisions for the specified files.
2417
2417
2418 Differences between files are shown using the unified diff format.
2418 Differences between files are shown using the unified diff format.
2419
2419
2420 .. note::
2420 .. note::
2421
2421
2422 :hg:`diff` may generate unexpected results for merges, as it will
2422 :hg:`diff` may generate unexpected results for merges, as it will
2423 default to comparing against the working directory's first
2423 default to comparing against the working directory's first
2424 parent changeset if no revisions are specified.
2424 parent changeset if no revisions are specified.
2425
2425
2426 When two revision arguments are given, then changes are shown
2426 When two revision arguments are given, then changes are shown
2427 between those revisions. If only one revision is specified then
2427 between those revisions. If only one revision is specified then
2428 that revision is compared to the working directory, and, when no
2428 that revision is compared to the working directory, and, when no
2429 revisions are specified, the working directory files are compared
2429 revisions are specified, the working directory files are compared
2430 to its first parent.
2430 to its first parent.
2431
2431
2432 Alternatively you can specify -c/--change with a revision to see
2432 Alternatively you can specify -c/--change with a revision to see
2433 the changes in that changeset relative to its first parent.
2433 the changes in that changeset relative to its first parent.
2434
2434
2435 Without the -a/--text option, diff will avoid generating diffs of
2435 Without the -a/--text option, diff will avoid generating diffs of
2436 files it detects as binary. With -a, diff will generate a diff
2436 files it detects as binary. With -a, diff will generate a diff
2437 anyway, probably with undesirable results.
2437 anyway, probably with undesirable results.
2438
2438
2439 Use the -g/--git option to generate diffs in the git extended diff
2439 Use the -g/--git option to generate diffs in the git extended diff
2440 format. For more information, read :hg:`help diffs`.
2440 format. For more information, read :hg:`help diffs`.
2441
2441
2442 .. container:: verbose
2442 .. container:: verbose
2443
2443
2444 Examples:
2444 Examples:
2445
2445
2446 - compare a file in the current working directory to its parent::
2446 - compare a file in the current working directory to its parent::
2447
2447
2448 hg diff foo.c
2448 hg diff foo.c
2449
2449
2450 - compare two historical versions of a directory, with rename info::
2450 - compare two historical versions of a directory, with rename info::
2451
2451
2452 hg diff --git -r 1.0:1.2 lib/
2452 hg diff --git -r 1.0:1.2 lib/
2453
2453
2454 - get change stats relative to the last change on some date::
2454 - get change stats relative to the last change on some date::
2455
2455
2456 hg diff --stat -r "date('may 2')"
2456 hg diff --stat -r "date('may 2')"
2457
2457
2458 - diff all newly-added files that contain a keyword::
2458 - diff all newly-added files that contain a keyword::
2459
2459
2460 hg diff "set:added() and grep(GNU)"
2460 hg diff "set:added() and grep(GNU)"
2461
2461
2462 - compare a revision and its parents::
2462 - compare a revision and its parents::
2463
2463
2464 hg diff -c 9353 # compare against first parent
2464 hg diff -c 9353 # compare against first parent
2465 hg diff -r 9353^:9353 # same using revset syntax
2465 hg diff -r 9353^:9353 # same using revset syntax
2466 hg diff -r 9353^2:9353 # compare against the second parent
2466 hg diff -r 9353^2:9353 # compare against the second parent
2467
2467
2468 Returns 0 on success.
2468 Returns 0 on success.
2469 """
2469 """
2470
2470
2471 opts = pycompat.byteskwargs(opts)
2471 opts = pycompat.byteskwargs(opts)
2472 revs = opts.get(b'rev')
2472 revs = opts.get(b'rev')
2473 change = opts.get(b'change')
2473 change = opts.get(b'change')
2474 stat = opts.get(b'stat')
2474 stat = opts.get(b'stat')
2475 reverse = opts.get(b'reverse')
2475 reverse = opts.get(b'reverse')
2476
2476
2477 if revs and change:
2477 if revs and change:
2478 msg = _(b'cannot specify --rev and --change at the same time')
2478 msg = _(b'cannot specify --rev and --change at the same time')
2479 raise error.Abort(msg)
2479 raise error.Abort(msg)
2480 elif change:
2480 elif change:
2481 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2481 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2482 ctx2 = scmutil.revsingle(repo, change, None)
2482 ctx2 = scmutil.revsingle(repo, change, None)
2483 ctx1 = ctx2.p1()
2483 ctx1 = ctx2.p1()
2484 else:
2484 else:
2485 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2485 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2486 ctx1, ctx2 = scmutil.revpair(repo, revs)
2486 ctx1, ctx2 = scmutil.revpair(repo, revs)
2487 node1, node2 = ctx1.node(), ctx2.node()
2487 node1, node2 = ctx1.node(), ctx2.node()
2488
2488
2489 if reverse:
2489 if reverse:
2490 node1, node2 = node2, node1
2490 node1, node2 = node2, node1
2491
2491
2492 diffopts = patch.diffallopts(ui, opts)
2492 diffopts = patch.diffallopts(ui, opts)
2493 m = scmutil.match(ctx2, pats, opts)
2493 m = scmutil.match(ctx2, pats, opts)
2494 m = repo.narrowmatch(m)
2494 m = repo.narrowmatch(m)
2495 ui.pager(b'diff')
2495 ui.pager(b'diff')
2496 logcmdutil.diffordiffstat(
2496 logcmdutil.diffordiffstat(
2497 ui,
2497 ui,
2498 repo,
2498 repo,
2499 diffopts,
2499 diffopts,
2500 node1,
2500 node1,
2501 node2,
2501 node2,
2502 m,
2502 m,
2503 stat=stat,
2503 stat=stat,
2504 listsubrepos=opts.get(b'subrepos'),
2504 listsubrepos=opts.get(b'subrepos'),
2505 root=opts.get(b'root'),
2505 root=opts.get(b'root'),
2506 )
2506 )
2507
2507
2508
2508
2509 @command(
2509 @command(
2510 b'export',
2510 b'export',
2511 [
2511 [
2512 (
2512 (
2513 b'B',
2513 b'B',
2514 b'bookmark',
2514 b'bookmark',
2515 b'',
2515 b'',
2516 _(b'export changes only reachable by given bookmark'),
2516 _(b'export changes only reachable by given bookmark'),
2517 _(b'BOOKMARK'),
2517 _(b'BOOKMARK'),
2518 ),
2518 ),
2519 (
2519 (
2520 b'o',
2520 b'o',
2521 b'output',
2521 b'output',
2522 b'',
2522 b'',
2523 _(b'print output to file with formatted name'),
2523 _(b'print output to file with formatted name'),
2524 _(b'FORMAT'),
2524 _(b'FORMAT'),
2525 ),
2525 ),
2526 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2526 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2527 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2527 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2528 ]
2528 ]
2529 + diffopts
2529 + diffopts
2530 + formatteropts,
2530 + formatteropts,
2531 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2531 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2532 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2532 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2533 helpbasic=True,
2533 helpbasic=True,
2534 intents={INTENT_READONLY},
2534 intents={INTENT_READONLY},
2535 )
2535 )
2536 def export(ui, repo, *changesets, **opts):
2536 def export(ui, repo, *changesets, **opts):
2537 """dump the header and diffs for one or more changesets
2537 """dump the header and diffs for one or more changesets
2538
2538
2539 Print the changeset header and diffs for one or more revisions.
2539 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.
2540 If no revision is given, the parent of the working directory is used.
2541
2541
2542 The information shown in the changeset header is: author, date,
2542 The information shown in the changeset header is: author, date,
2543 branch name (if non-default), changeset hash, parent(s) and commit
2543 branch name (if non-default), changeset hash, parent(s) and commit
2544 comment.
2544 comment.
2545
2545
2546 .. note::
2546 .. note::
2547
2547
2548 :hg:`export` may generate unexpected diff output for merge
2548 :hg:`export` may generate unexpected diff output for merge
2549 changesets, as it will compare the merge changeset against its
2549 changesets, as it will compare the merge changeset against its
2550 first parent only.
2550 first parent only.
2551
2551
2552 Output may be to a file, in which case the name of the file is
2552 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
2553 given using a template string. See :hg:`help templates`. In addition
2554 to the common template keywords, the following formatting rules are
2554 to the common template keywords, the following formatting rules are
2555 supported:
2555 supported:
2556
2556
2557 :``%%``: literal "%" character
2557 :``%%``: literal "%" character
2558 :``%H``: changeset hash (40 hexadecimal digits)
2558 :``%H``: changeset hash (40 hexadecimal digits)
2559 :``%N``: number of patches being generated
2559 :``%N``: number of patches being generated
2560 :``%R``: changeset revision number
2560 :``%R``: changeset revision number
2561 :``%b``: basename of the exporting repository
2561 :``%b``: basename of the exporting repository
2562 :``%h``: short-form changeset hash (12 hexadecimal digits)
2562 :``%h``: short-form changeset hash (12 hexadecimal digits)
2563 :``%m``: first line of the commit message (only alphanumeric characters)
2563 :``%m``: first line of the commit message (only alphanumeric characters)
2564 :``%n``: zero-padded sequence number, starting at 1
2564 :``%n``: zero-padded sequence number, starting at 1
2565 :``%r``: zero-padded changeset revision number
2565 :``%r``: zero-padded changeset revision number
2566 :``\\``: literal "\\" character
2566 :``\\``: literal "\\" character
2567
2567
2568 Without the -a/--text option, export will avoid generating diffs
2568 Without the -a/--text option, export will avoid generating diffs
2569 of files it detects as binary. With -a, export will generate a
2569 of files it detects as binary. With -a, export will generate a
2570 diff anyway, probably with undesirable results.
2570 diff anyway, probably with undesirable results.
2571
2571
2572 With -B/--bookmark changesets reachable by the given bookmark are
2572 With -B/--bookmark changesets reachable by the given bookmark are
2573 selected.
2573 selected.
2574
2574
2575 Use the -g/--git option to generate diffs in the git extended diff
2575 Use the -g/--git option to generate diffs in the git extended diff
2576 format. See :hg:`help diffs` for more information.
2576 format. See :hg:`help diffs` for more information.
2577
2577
2578 With the --switch-parent option, the diff will be against the
2578 With the --switch-parent option, the diff will be against the
2579 second parent. It can be useful to review a merge.
2579 second parent. It can be useful to review a merge.
2580
2580
2581 .. container:: verbose
2581 .. container:: verbose
2582
2582
2583 Template:
2583 Template:
2584
2584
2585 The following keywords are supported in addition to the common template
2585 The following keywords are supported in addition to the common template
2586 keywords and functions. See also :hg:`help templates`.
2586 keywords and functions. See also :hg:`help templates`.
2587
2587
2588 :diff: String. Diff content.
2588 :diff: String. Diff content.
2589 :parents: List of strings. Parent nodes of the changeset.
2589 :parents: List of strings. Parent nodes of the changeset.
2590
2590
2591 Examples:
2591 Examples:
2592
2592
2593 - use export and import to transplant a bugfix to the current
2593 - use export and import to transplant a bugfix to the current
2594 branch::
2594 branch::
2595
2595
2596 hg export -r 9353 | hg import -
2596 hg export -r 9353 | hg import -
2597
2597
2598 - export all the changesets between two revisions to a file with
2598 - export all the changesets between two revisions to a file with
2599 rename information::
2599 rename information::
2600
2600
2601 hg export --git -r 123:150 > changes.txt
2601 hg export --git -r 123:150 > changes.txt
2602
2602
2603 - split outgoing changes into a series of patches with
2603 - split outgoing changes into a series of patches with
2604 descriptive names::
2604 descriptive names::
2605
2605
2606 hg export -r "outgoing()" -o "%n-%m.patch"
2606 hg export -r "outgoing()" -o "%n-%m.patch"
2607
2607
2608 Returns 0 on success.
2608 Returns 0 on success.
2609 """
2609 """
2610 opts = pycompat.byteskwargs(opts)
2610 opts = pycompat.byteskwargs(opts)
2611 bookmark = opts.get(b'bookmark')
2611 bookmark = opts.get(b'bookmark')
2612 changesets += tuple(opts.get(b'rev', []))
2612 changesets += tuple(opts.get(b'rev', []))
2613
2613
2614 if bookmark and changesets:
2614 if bookmark and changesets:
2615 raise error.Abort(_(b"-r and -B are mutually exclusive"))
2615 raise error.Abort(_(b"-r and -B are mutually exclusive"))
2616
2616
2617 if bookmark:
2617 if bookmark:
2618 if bookmark not in repo._bookmarks:
2618 if bookmark not in repo._bookmarks:
2619 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2619 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2620
2620
2621 revs = scmutil.bookmarkrevs(repo, bookmark)
2621 revs = scmutil.bookmarkrevs(repo, bookmark)
2622 else:
2622 else:
2623 if not changesets:
2623 if not changesets:
2624 changesets = [b'.']
2624 changesets = [b'.']
2625
2625
2626 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2626 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2627 revs = scmutil.revrange(repo, changesets)
2627 revs = scmutil.revrange(repo, changesets)
2628
2628
2629 if not revs:
2629 if not revs:
2630 raise error.Abort(_(b"export requires at least one changeset"))
2630 raise error.Abort(_(b"export requires at least one changeset"))
2631 if len(revs) > 1:
2631 if len(revs) > 1:
2632 ui.note(_(b'exporting patches:\n'))
2632 ui.note(_(b'exporting patches:\n'))
2633 else:
2633 else:
2634 ui.note(_(b'exporting patch:\n'))
2634 ui.note(_(b'exporting patch:\n'))
2635
2635
2636 fntemplate = opts.get(b'output')
2636 fntemplate = opts.get(b'output')
2637 if cmdutil.isstdiofilename(fntemplate):
2637 if cmdutil.isstdiofilename(fntemplate):
2638 fntemplate = b''
2638 fntemplate = b''
2639
2639
2640 if fntemplate:
2640 if fntemplate:
2641 fm = formatter.nullformatter(ui, b'export', opts)
2641 fm = formatter.nullformatter(ui, b'export', opts)
2642 else:
2642 else:
2643 ui.pager(b'export')
2643 ui.pager(b'export')
2644 fm = ui.formatter(b'export', opts)
2644 fm = ui.formatter(b'export', opts)
2645 with fm:
2645 with fm:
2646 cmdutil.export(
2646 cmdutil.export(
2647 repo,
2647 repo,
2648 revs,
2648 revs,
2649 fm,
2649 fm,
2650 fntemplate=fntemplate,
2650 fntemplate=fntemplate,
2651 switch_parent=opts.get(b'switch_parent'),
2651 switch_parent=opts.get(b'switch_parent'),
2652 opts=patch.diffallopts(ui, opts),
2652 opts=patch.diffallopts(ui, opts),
2653 )
2653 )
2654
2654
2655
2655
2656 @command(
2656 @command(
2657 b'files',
2657 b'files',
2658 [
2658 [
2659 (
2659 (
2660 b'r',
2660 b'r',
2661 b'rev',
2661 b'rev',
2662 b'',
2662 b'',
2663 _(b'search the repository as it is in REV'),
2663 _(b'search the repository as it is in REV'),
2664 _(b'REV'),
2664 _(b'REV'),
2665 ),
2665 ),
2666 (
2666 (
2667 b'0',
2667 b'0',
2668 b'print0',
2668 b'print0',
2669 None,
2669 None,
2670 _(b'end filenames with NUL, for use with xargs'),
2670 _(b'end filenames with NUL, for use with xargs'),
2671 ),
2671 ),
2672 ]
2672 ]
2673 + walkopts
2673 + walkopts
2674 + formatteropts
2674 + formatteropts
2675 + subrepoopts,
2675 + subrepoopts,
2676 _(b'[OPTION]... [FILE]...'),
2676 _(b'[OPTION]... [FILE]...'),
2677 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2677 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2678 intents={INTENT_READONLY},
2678 intents={INTENT_READONLY},
2679 )
2679 )
2680 def files(ui, repo, *pats, **opts):
2680 def files(ui, repo, *pats, **opts):
2681 """list tracked files
2681 """list tracked files
2682
2682
2683 Print files under Mercurial control in the working directory or
2683 Print files under Mercurial control in the working directory or
2684 specified revision for given files (excluding removed files).
2684 specified revision for given files (excluding removed files).
2685 Files can be specified as filenames or filesets.
2685 Files can be specified as filenames or filesets.
2686
2686
2687 If no files are given to match, this command prints the names
2687 If no files are given to match, this command prints the names
2688 of all files under Mercurial control.
2688 of all files under Mercurial control.
2689
2689
2690 .. container:: verbose
2690 .. container:: verbose
2691
2691
2692 Template:
2692 Template:
2693
2693
2694 The following keywords are supported in addition to the common template
2694 The following keywords are supported in addition to the common template
2695 keywords and functions. See also :hg:`help templates`.
2695 keywords and functions. See also :hg:`help templates`.
2696
2696
2697 :flags: String. Character denoting file's symlink and executable bits.
2697 :flags: String. Character denoting file's symlink and executable bits.
2698 :path: String. Repository-absolute path of the file.
2698 :path: String. Repository-absolute path of the file.
2699 :size: Integer. Size of the file in bytes.
2699 :size: Integer. Size of the file in bytes.
2700
2700
2701 Examples:
2701 Examples:
2702
2702
2703 - list all files under the current directory::
2703 - list all files under the current directory::
2704
2704
2705 hg files .
2705 hg files .
2706
2706
2707 - shows sizes and flags for current revision::
2707 - shows sizes and flags for current revision::
2708
2708
2709 hg files -vr .
2709 hg files -vr .
2710
2710
2711 - list all files named README::
2711 - list all files named README::
2712
2712
2713 hg files -I "**/README"
2713 hg files -I "**/README"
2714
2714
2715 - list all binary files::
2715 - list all binary files::
2716
2716
2717 hg files "set:binary()"
2717 hg files "set:binary()"
2718
2718
2719 - find files containing a regular expression::
2719 - find files containing a regular expression::
2720
2720
2721 hg files "set:grep('bob')"
2721 hg files "set:grep('bob')"
2722
2722
2723 - search tracked file contents with xargs and grep::
2723 - search tracked file contents with xargs and grep::
2724
2724
2725 hg files -0 | xargs -0 grep foo
2725 hg files -0 | xargs -0 grep foo
2726
2726
2727 See :hg:`help patterns` and :hg:`help filesets` for more information
2727 See :hg:`help patterns` and :hg:`help filesets` for more information
2728 on specifying file patterns.
2728 on specifying file patterns.
2729
2729
2730 Returns 0 if a match is found, 1 otherwise.
2730 Returns 0 if a match is found, 1 otherwise.
2731
2731
2732 """
2732 """
2733
2733
2734 opts = pycompat.byteskwargs(opts)
2734 opts = pycompat.byteskwargs(opts)
2735 rev = opts.get(b'rev')
2735 rev = opts.get(b'rev')
2736 if rev:
2736 if rev:
2737 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2737 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2738 ctx = scmutil.revsingle(repo, rev, None)
2738 ctx = scmutil.revsingle(repo, rev, None)
2739
2739
2740 end = b'\n'
2740 end = b'\n'
2741 if opts.get(b'print0'):
2741 if opts.get(b'print0'):
2742 end = b'\0'
2742 end = b'\0'
2743 fmt = b'%s' + end
2743 fmt = b'%s' + end
2744
2744
2745 m = scmutil.match(ctx, pats, opts)
2745 m = scmutil.match(ctx, pats, opts)
2746 ui.pager(b'files')
2746 ui.pager(b'files')
2747 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2747 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2748 with ui.formatter(b'files', opts) as fm:
2748 with ui.formatter(b'files', opts) as fm:
2749 return cmdutil.files(
2749 return cmdutil.files(
2750 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2750 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2751 )
2751 )
2752
2752
2753
2753
2754 @command(
2754 @command(
2755 b'forget',
2755 b'forget',
2756 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2756 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2757 + walkopts
2757 + walkopts
2758 + dryrunopts,
2758 + dryrunopts,
2759 _(b'[OPTION]... FILE...'),
2759 _(b'[OPTION]... FILE...'),
2760 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2760 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2761 helpbasic=True,
2761 helpbasic=True,
2762 inferrepo=True,
2762 inferrepo=True,
2763 )
2763 )
2764 def forget(ui, repo, *pats, **opts):
2764 def forget(ui, repo, *pats, **opts):
2765 """forget the specified files on the next commit
2765 """forget the specified files on the next commit
2766
2766
2767 Mark the specified files so they will no longer be tracked
2767 Mark the specified files so they will no longer be tracked
2768 after the next commit.
2768 after the next commit.
2769
2769
2770 This only removes files from the current branch, not from the
2770 This only removes files from the current branch, not from the
2771 entire project history, and it does not delete them from the
2771 entire project history, and it does not delete them from the
2772 working directory.
2772 working directory.
2773
2773
2774 To delete the file from the working directory, see :hg:`remove`.
2774 To delete the file from the working directory, see :hg:`remove`.
2775
2775
2776 To undo a forget before the next commit, see :hg:`add`.
2776 To undo a forget before the next commit, see :hg:`add`.
2777
2777
2778 .. container:: verbose
2778 .. container:: verbose
2779
2779
2780 Examples:
2780 Examples:
2781
2781
2782 - forget newly-added binary files::
2782 - forget newly-added binary files::
2783
2783
2784 hg forget "set:added() and binary()"
2784 hg forget "set:added() and binary()"
2785
2785
2786 - forget files that would be excluded by .hgignore::
2786 - forget files that would be excluded by .hgignore::
2787
2787
2788 hg forget "set:hgignore()"
2788 hg forget "set:hgignore()"
2789
2789
2790 Returns 0 on success.
2790 Returns 0 on success.
2791 """
2791 """
2792
2792
2793 opts = pycompat.byteskwargs(opts)
2793 opts = pycompat.byteskwargs(opts)
2794 if not pats:
2794 if not pats:
2795 raise error.Abort(_(b'no files specified'))
2795 raise error.Abort(_(b'no files specified'))
2796
2796
2797 m = scmutil.match(repo[None], pats, opts)
2797 m = scmutil.match(repo[None], pats, opts)
2798 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2798 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2799 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2799 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2800 rejected = cmdutil.forget(
2800 rejected = cmdutil.forget(
2801 ui,
2801 ui,
2802 repo,
2802 repo,
2803 m,
2803 m,
2804 prefix=b"",
2804 prefix=b"",
2805 uipathfn=uipathfn,
2805 uipathfn=uipathfn,
2806 explicitonly=False,
2806 explicitonly=False,
2807 dryrun=dryrun,
2807 dryrun=dryrun,
2808 interactive=interactive,
2808 interactive=interactive,
2809 )[0]
2809 )[0]
2810 return rejected and 1 or 0
2810 return rejected and 1 or 0
2811
2811
2812
2812
2813 @command(
2813 @command(
2814 b'graft',
2814 b'graft',
2815 [
2815 [
2816 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2816 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2817 (
2817 (
2818 b'',
2818 b'',
2819 b'base',
2819 b'base',
2820 b'',
2820 b'',
2821 _(b'base revision when doing the graft merge (ADVANCED)'),
2821 _(b'base revision when doing the graft merge (ADVANCED)'),
2822 _(b'REV'),
2822 _(b'REV'),
2823 ),
2823 ),
2824 (b'c', b'continue', False, _(b'resume interrupted graft')),
2824 (b'c', b'continue', False, _(b'resume interrupted graft')),
2825 (b'', b'stop', False, _(b'stop interrupted graft')),
2825 (b'', b'stop', False, _(b'stop interrupted graft')),
2826 (b'', b'abort', False, _(b'abort interrupted graft')),
2826 (b'', b'abort', False, _(b'abort interrupted graft')),
2827 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2827 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2828 (b'', b'log', None, _(b'append graft info to log message')),
2828 (b'', b'log', None, _(b'append graft info to log message')),
2829 (
2829 (
2830 b'',
2830 b'',
2831 b'no-commit',
2831 b'no-commit',
2832 None,
2832 None,
2833 _(b"don't commit, just apply the changes in working directory"),
2833 _(b"don't commit, just apply the changes in working directory"),
2834 ),
2834 ),
2835 (b'f', b'force', False, _(b'force graft')),
2835 (b'f', b'force', False, _(b'force graft')),
2836 (
2836 (
2837 b'D',
2837 b'D',
2838 b'currentdate',
2838 b'currentdate',
2839 False,
2839 False,
2840 _(b'record the current date as commit date'),
2840 _(b'record the current date as commit date'),
2841 ),
2841 ),
2842 (
2842 (
2843 b'U',
2843 b'U',
2844 b'currentuser',
2844 b'currentuser',
2845 False,
2845 False,
2846 _(b'record the current user as committer'),
2846 _(b'record the current user as committer'),
2847 ),
2847 ),
2848 ]
2848 ]
2849 + commitopts2
2849 + commitopts2
2850 + mergetoolopts
2850 + mergetoolopts
2851 + dryrunopts,
2851 + dryrunopts,
2852 _(b'[OPTION]... [-r REV]... REV...'),
2852 _(b'[OPTION]... [-r REV]... REV...'),
2853 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2853 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2854 )
2854 )
2855 def graft(ui, repo, *revs, **opts):
2855 def graft(ui, repo, *revs, **opts):
2856 '''copy changes from other branches onto the current branch
2856 '''copy changes from other branches onto the current branch
2857
2857
2858 This command uses Mercurial's merge logic to copy individual
2858 This command uses Mercurial's merge logic to copy individual
2859 changes from other branches without merging branches in the
2859 changes from other branches without merging branches in the
2860 history graph. This is sometimes known as 'backporting' or
2860 history graph. This is sometimes known as 'backporting' or
2861 'cherry-picking'. By default, graft will copy user, date, and
2861 'cherry-picking'. By default, graft will copy user, date, and
2862 description from the source changesets.
2862 description from the source changesets.
2863
2863
2864 Changesets that are ancestors of the current revision, that have
2864 Changesets that are ancestors of the current revision, that have
2865 already been grafted, or that are merges will be skipped.
2865 already been grafted, or that are merges will be skipped.
2866
2866
2867 If --log is specified, log messages will have a comment appended
2867 If --log is specified, log messages will have a comment appended
2868 of the form::
2868 of the form::
2869
2869
2870 (grafted from CHANGESETHASH)
2870 (grafted from CHANGESETHASH)
2871
2871
2872 If --force is specified, revisions will be grafted even if they
2872 If --force is specified, revisions will be grafted even if they
2873 are already ancestors of, or have been grafted to, the destination.
2873 are already ancestors of, or have been grafted to, the destination.
2874 This is useful when the revisions have since been backed out.
2874 This is useful when the revisions have since been backed out.
2875
2875
2876 If a graft merge results in conflicts, the graft process is
2876 If a graft merge results in conflicts, the graft process is
2877 interrupted so that the current merge can be manually resolved.
2877 interrupted so that the current merge can be manually resolved.
2878 Once all conflicts are addressed, the graft process can be
2878 Once all conflicts are addressed, the graft process can be
2879 continued with the -c/--continue option.
2879 continued with the -c/--continue option.
2880
2880
2881 The -c/--continue option reapplies all the earlier options.
2881 The -c/--continue option reapplies all the earlier options.
2882
2882
2883 .. container:: verbose
2883 .. container:: verbose
2884
2884
2885 The --base option exposes more of how graft internally uses merge with a
2885 The --base option exposes more of how graft internally uses merge with a
2886 custom base revision. --base can be used to specify another ancestor than
2886 custom base revision. --base can be used to specify another ancestor than
2887 the first and only parent.
2887 the first and only parent.
2888
2888
2889 The command::
2889 The command::
2890
2890
2891 hg graft -r 345 --base 234
2891 hg graft -r 345 --base 234
2892
2892
2893 is thus pretty much the same as::
2893 is thus pretty much the same as::
2894
2894
2895 hg diff -r 234 -r 345 | hg import
2895 hg diff -r 234 -r 345 | hg import
2896
2896
2897 but using merge to resolve conflicts and track moved files.
2897 but using merge to resolve conflicts and track moved files.
2898
2898
2899 The result of a merge can thus be backported as a single commit by
2899 The result of a merge can thus be backported as a single commit by
2900 specifying one of the merge parents as base, and thus effectively
2900 specifying one of the merge parents as base, and thus effectively
2901 grafting the changes from the other side.
2901 grafting the changes from the other side.
2902
2902
2903 It is also possible to collapse multiple changesets and clean up history
2903 It is also possible to collapse multiple changesets and clean up history
2904 by specifying another ancestor as base, much like rebase --collapse
2904 by specifying another ancestor as base, much like rebase --collapse
2905 --keep.
2905 --keep.
2906
2906
2907 The commit message can be tweaked after the fact using commit --amend .
2907 The commit message can be tweaked after the fact using commit --amend .
2908
2908
2909 For using non-ancestors as the base to backout changes, see the backout
2909 For using non-ancestors as the base to backout changes, see the backout
2910 command and the hidden --parent option.
2910 command and the hidden --parent option.
2911
2911
2912 .. container:: verbose
2912 .. container:: verbose
2913
2913
2914 Examples:
2914 Examples:
2915
2915
2916 - copy a single change to the stable branch and edit its description::
2916 - copy a single change to the stable branch and edit its description::
2917
2917
2918 hg update stable
2918 hg update stable
2919 hg graft --edit 9393
2919 hg graft --edit 9393
2920
2920
2921 - graft a range of changesets with one exception, updating dates::
2921 - graft a range of changesets with one exception, updating dates::
2922
2922
2923 hg graft -D "2085::2093 and not 2091"
2923 hg graft -D "2085::2093 and not 2091"
2924
2924
2925 - continue a graft after resolving conflicts::
2925 - continue a graft after resolving conflicts::
2926
2926
2927 hg graft -c
2927 hg graft -c
2928
2928
2929 - show the source of a grafted changeset::
2929 - show the source of a grafted changeset::
2930
2930
2931 hg log --debug -r .
2931 hg log --debug -r .
2932
2932
2933 - show revisions sorted by date::
2933 - show revisions sorted by date::
2934
2934
2935 hg log -r "sort(all(), date)"
2935 hg log -r "sort(all(), date)"
2936
2936
2937 - backport the result of a merge as a single commit::
2937 - backport the result of a merge as a single commit::
2938
2938
2939 hg graft -r 123 --base 123^
2939 hg graft -r 123 --base 123^
2940
2940
2941 - land a feature branch as one changeset::
2941 - land a feature branch as one changeset::
2942
2942
2943 hg up -cr default
2943 hg up -cr default
2944 hg graft -r featureX --base "ancestor('featureX', 'default')"
2944 hg graft -r featureX --base "ancestor('featureX', 'default')"
2945
2945
2946 See :hg:`help revisions` for more about specifying revisions.
2946 See :hg:`help revisions` for more about specifying revisions.
2947
2947
2948 Returns 0 on successful completion.
2948 Returns 0 on successful completion.
2949 '''
2949 '''
2950 with repo.wlock():
2950 with repo.wlock():
2951 return _dograft(ui, repo, *revs, **opts)
2951 return _dograft(ui, repo, *revs, **opts)
2952
2952
2953
2953
2954 def _dograft(ui, repo, *revs, **opts):
2954 def _dograft(ui, repo, *revs, **opts):
2955 opts = pycompat.byteskwargs(opts)
2955 opts = pycompat.byteskwargs(opts)
2956 if revs and opts.get(b'rev'):
2956 if revs and opts.get(b'rev'):
2957 ui.warn(
2957 ui.warn(
2958 _(
2958 _(
2959 b'warning: inconsistent use of --rev might give unexpected '
2959 b'warning: inconsistent use of --rev might give unexpected '
2960 b'revision ordering!\n'
2960 b'revision ordering!\n'
2961 )
2961 )
2962 )
2962 )
2963
2963
2964 revs = list(revs)
2964 revs = list(revs)
2965 revs.extend(opts.get(b'rev'))
2965 revs.extend(opts.get(b'rev'))
2966 basectx = None
2966 basectx = None
2967 if opts.get(b'base'):
2967 if opts.get(b'base'):
2968 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2968 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2969 # a dict of data to be stored in state file
2969 # a dict of data to be stored in state file
2970 statedata = {}
2970 statedata = {}
2971 # list of new nodes created by ongoing graft
2971 # list of new nodes created by ongoing graft
2972 statedata[b'newnodes'] = []
2972 statedata[b'newnodes'] = []
2973
2973
2974 if opts.get(b'user') and opts.get(b'currentuser'):
2974 if opts.get(b'user') and opts.get(b'currentuser'):
2975 raise error.Abort(_(b'--user and --currentuser are mutually exclusive'))
2975 raise error.Abort(_(b'--user and --currentuser are mutually exclusive'))
2976 if opts.get(b'date') and opts.get(b'currentdate'):
2976 if opts.get(b'date') and opts.get(b'currentdate'):
2977 raise error.Abort(_(b'--date and --currentdate are mutually exclusive'))
2977 raise error.Abort(_(b'--date and --currentdate are mutually exclusive'))
2978 if not opts.get(b'user') and opts.get(b'currentuser'):
2978 if not opts.get(b'user') and opts.get(b'currentuser'):
2979 opts[b'user'] = ui.username()
2979 opts[b'user'] = ui.username()
2980 if not opts.get(b'date') and opts.get(b'currentdate'):
2980 if not opts.get(b'date') and opts.get(b'currentdate'):
2981 opts[b'date'] = b"%d %d" % dateutil.makedate()
2981 opts[b'date'] = b"%d %d" % dateutil.makedate()
2982
2982
2983 editor = cmdutil.getcommiteditor(
2983 editor = cmdutil.getcommiteditor(
2984 editform=b'graft', **pycompat.strkwargs(opts)
2984 editform=b'graft', **pycompat.strkwargs(opts)
2985 )
2985 )
2986
2986
2987 cont = False
2987 cont = False
2988 if opts.get(b'no_commit'):
2988 if opts.get(b'no_commit'):
2989 if opts.get(b'edit'):
2989 if opts.get(b'edit'):
2990 raise error.Abort(
2990 raise error.Abort(
2991 _(b"cannot specify --no-commit and --edit together")
2991 _(b"cannot specify --no-commit and --edit together")
2992 )
2992 )
2993 if opts.get(b'currentuser'):
2993 if opts.get(b'currentuser'):
2994 raise error.Abort(
2994 raise error.Abort(
2995 _(b"cannot specify --no-commit and --currentuser together")
2995 _(b"cannot specify --no-commit and --currentuser together")
2996 )
2996 )
2997 if opts.get(b'currentdate'):
2997 if opts.get(b'currentdate'):
2998 raise error.Abort(
2998 raise error.Abort(
2999 _(b"cannot specify --no-commit and --currentdate together")
2999 _(b"cannot specify --no-commit and --currentdate together")
3000 )
3000 )
3001 if opts.get(b'log'):
3001 if opts.get(b'log'):
3002 raise error.Abort(
3002 raise error.Abort(
3003 _(b"cannot specify --no-commit and --log together")
3003 _(b"cannot specify --no-commit and --log together")
3004 )
3004 )
3005
3005
3006 graftstate = statemod.cmdstate(repo, b'graftstate')
3006 graftstate = statemod.cmdstate(repo, b'graftstate')
3007
3007
3008 if opts.get(b'stop'):
3008 if opts.get(b'stop'):
3009 if opts.get(b'continue'):
3009 if opts.get(b'continue'):
3010 raise error.Abort(
3010 raise error.Abort(
3011 _(b"cannot use '--continue' and '--stop' together")
3011 _(b"cannot use '--continue' and '--stop' together")
3012 )
3012 )
3013 if opts.get(b'abort'):
3013 if opts.get(b'abort'):
3014 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3014 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3015
3015
3016 if any(
3016 if any(
3017 (
3017 (
3018 opts.get(b'edit'),
3018 opts.get(b'edit'),
3019 opts.get(b'log'),
3019 opts.get(b'log'),
3020 opts.get(b'user'),
3020 opts.get(b'user'),
3021 opts.get(b'date'),
3021 opts.get(b'date'),
3022 opts.get(b'currentdate'),
3022 opts.get(b'currentdate'),
3023 opts.get(b'currentuser'),
3023 opts.get(b'currentuser'),
3024 opts.get(b'rev'),
3024 opts.get(b'rev'),
3025 )
3025 )
3026 ):
3026 ):
3027 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3027 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3028 return _stopgraft(ui, repo, graftstate)
3028 return _stopgraft(ui, repo, graftstate)
3029 elif opts.get(b'abort'):
3029 elif opts.get(b'abort'):
3030 if opts.get(b'continue'):
3030 if opts.get(b'continue'):
3031 raise error.Abort(
3031 raise error.Abort(
3032 _(b"cannot use '--continue' and '--abort' together")
3032 _(b"cannot use '--continue' and '--abort' together")
3033 )
3033 )
3034 if any(
3034 if any(
3035 (
3035 (
3036 opts.get(b'edit'),
3036 opts.get(b'edit'),
3037 opts.get(b'log'),
3037 opts.get(b'log'),
3038 opts.get(b'user'),
3038 opts.get(b'user'),
3039 opts.get(b'date'),
3039 opts.get(b'date'),
3040 opts.get(b'currentdate'),
3040 opts.get(b'currentdate'),
3041 opts.get(b'currentuser'),
3041 opts.get(b'currentuser'),
3042 opts.get(b'rev'),
3042 opts.get(b'rev'),
3043 )
3043 )
3044 ):
3044 ):
3045 raise error.Abort(
3045 raise error.Abort(
3046 _(b"cannot specify any other flag with '--abort'")
3046 _(b"cannot specify any other flag with '--abort'")
3047 )
3047 )
3048
3048
3049 return cmdutil.abortgraft(ui, repo, graftstate)
3049 return cmdutil.abortgraft(ui, repo, graftstate)
3050 elif opts.get(b'continue'):
3050 elif opts.get(b'continue'):
3051 cont = True
3051 cont = True
3052 if revs:
3052 if revs:
3053 raise error.Abort(_(b"can't specify --continue and revisions"))
3053 raise error.Abort(_(b"can't specify --continue and revisions"))
3054 # read in unfinished revisions
3054 # read in unfinished revisions
3055 if graftstate.exists():
3055 if graftstate.exists():
3056 statedata = cmdutil.readgraftstate(repo, graftstate)
3056 statedata = cmdutil.readgraftstate(repo, graftstate)
3057 if statedata.get(b'date'):
3057 if statedata.get(b'date'):
3058 opts[b'date'] = statedata[b'date']
3058 opts[b'date'] = statedata[b'date']
3059 if statedata.get(b'user'):
3059 if statedata.get(b'user'):
3060 opts[b'user'] = statedata[b'user']
3060 opts[b'user'] = statedata[b'user']
3061 if statedata.get(b'log'):
3061 if statedata.get(b'log'):
3062 opts[b'log'] = True
3062 opts[b'log'] = True
3063 if statedata.get(b'no_commit'):
3063 if statedata.get(b'no_commit'):
3064 opts[b'no_commit'] = statedata.get(b'no_commit')
3064 opts[b'no_commit'] = statedata.get(b'no_commit')
3065 nodes = statedata[b'nodes']
3065 nodes = statedata[b'nodes']
3066 revs = [repo[node].rev() for node in nodes]
3066 revs = [repo[node].rev() for node in nodes]
3067 else:
3067 else:
3068 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3068 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3069 else:
3069 else:
3070 if not revs:
3070 if not revs:
3071 raise error.Abort(_(b'no revisions specified'))
3071 raise error.Abort(_(b'no revisions specified'))
3072 cmdutil.checkunfinished(repo)
3072 cmdutil.checkunfinished(repo)
3073 cmdutil.bailifchanged(repo)
3073 cmdutil.bailifchanged(repo)
3074 revs = scmutil.revrange(repo, revs)
3074 revs = scmutil.revrange(repo, revs)
3075
3075
3076 skipped = set()
3076 skipped = set()
3077 if basectx is None:
3077 if basectx is None:
3078 # check for merges
3078 # check for merges
3079 for rev in repo.revs(b'%ld and merge()', revs):
3079 for rev in repo.revs(b'%ld and merge()', revs):
3080 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3080 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3081 skipped.add(rev)
3081 skipped.add(rev)
3082 revs = [r for r in revs if r not in skipped]
3082 revs = [r for r in revs if r not in skipped]
3083 if not revs:
3083 if not revs:
3084 return -1
3084 return -1
3085 if basectx is not None and len(revs) != 1:
3085 if basectx is not None and len(revs) != 1:
3086 raise error.Abort(_(b'only one revision allowed with --base '))
3086 raise error.Abort(_(b'only one revision allowed with --base '))
3087
3087
3088 # Don't check in the --continue case, in effect retaining --force across
3088 # Don't check in the --continue case, in effect retaining --force across
3089 # --continues. That's because without --force, any revisions we decided to
3089 # --continues. That's because without --force, any revisions we decided to
3090 # skip would have been filtered out here, so they wouldn't have made their
3090 # skip would have been filtered out here, so they wouldn't have made their
3091 # way to the graftstate. With --force, any revisions we would have otherwise
3091 # way to the graftstate. With --force, any revisions we would have otherwise
3092 # skipped would not have been filtered out, and if they hadn't been applied
3092 # skipped would not have been filtered out, and if they hadn't been applied
3093 # already, they'd have been in the graftstate.
3093 # already, they'd have been in the graftstate.
3094 if not (cont or opts.get(b'force')) and basectx is None:
3094 if not (cont or opts.get(b'force')) and basectx is None:
3095 # check for ancestors of dest branch
3095 # check for ancestors of dest branch
3096 crev = repo[b'.'].rev()
3096 crev = repo[b'.'].rev()
3097 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3097 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3098 # XXX make this lazy in the future
3098 # XXX make this lazy in the future
3099 # don't mutate while iterating, create a copy
3099 # don't mutate while iterating, create a copy
3100 for rev in list(revs):
3100 for rev in list(revs):
3101 if rev in ancestors:
3101 if rev in ancestors:
3102 ui.warn(
3102 ui.warn(
3103 _(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev])
3103 _(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev])
3104 )
3104 )
3105 # XXX remove on list is slow
3105 # XXX remove on list is slow
3106 revs.remove(rev)
3106 revs.remove(rev)
3107 if not revs:
3107 if not revs:
3108 return -1
3108 return -1
3109
3109
3110 # analyze revs for earlier grafts
3110 # analyze revs for earlier grafts
3111 ids = {}
3111 ids = {}
3112 for ctx in repo.set(b"%ld", revs):
3112 for ctx in repo.set(b"%ld", revs):
3113 ids[ctx.hex()] = ctx.rev()
3113 ids[ctx.hex()] = ctx.rev()
3114 n = ctx.extra().get(b'source')
3114 n = ctx.extra().get(b'source')
3115 if n:
3115 if n:
3116 ids[n] = ctx.rev()
3116 ids[n] = ctx.rev()
3117
3117
3118 # check ancestors for earlier grafts
3118 # check ancestors for earlier grafts
3119 ui.debug(b'scanning for duplicate grafts\n')
3119 ui.debug(b'scanning for duplicate grafts\n')
3120
3120
3121 # The only changesets we can be sure doesn't contain grafts of any
3121 # The only changesets we can be sure doesn't contain grafts of any
3122 # revs, are the ones that are common ancestors of *all* revs:
3122 # revs, are the ones that are common ancestors of *all* revs:
3123 for rev in repo.revs(b'only(%d,ancestor(%ld))', crev, revs):
3123 for rev in repo.revs(b'only(%d,ancestor(%ld))', crev, revs):
3124 ctx = repo[rev]
3124 ctx = repo[rev]
3125 n = ctx.extra().get(b'source')
3125 n = ctx.extra().get(b'source')
3126 if n in ids:
3126 if n in ids:
3127 try:
3127 try:
3128 r = repo[n].rev()
3128 r = repo[n].rev()
3129 except error.RepoLookupError:
3129 except error.RepoLookupError:
3130 r = None
3130 r = None
3131 if r in revs:
3131 if r in revs:
3132 ui.warn(
3132 ui.warn(
3133 _(
3133 _(
3134 b'skipping revision %d:%s '
3134 b'skipping revision %d:%s '
3135 b'(already grafted to %d:%s)\n'
3135 b'(already grafted to %d:%s)\n'
3136 )
3136 )
3137 % (r, repo[r], rev, ctx)
3137 % (r, repo[r], rev, ctx)
3138 )
3138 )
3139 revs.remove(r)
3139 revs.remove(r)
3140 elif ids[n] in revs:
3140 elif ids[n] in revs:
3141 if r is None:
3141 if r is None:
3142 ui.warn(
3142 ui.warn(
3143 _(
3143 _(
3144 b'skipping already grafted revision %d:%s '
3144 b'skipping already grafted revision %d:%s '
3145 b'(%d:%s also has unknown origin %s)\n'
3145 b'(%d:%s also has unknown origin %s)\n'
3146 )
3146 )
3147 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3147 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3148 )
3148 )
3149 else:
3149 else:
3150 ui.warn(
3150 ui.warn(
3151 _(
3151 _(
3152 b'skipping already grafted revision %d:%s '
3152 b'skipping already grafted revision %d:%s '
3153 b'(%d:%s also has origin %d:%s)\n'
3153 b'(%d:%s also has origin %d:%s)\n'
3154 )
3154 )
3155 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3155 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3156 )
3156 )
3157 revs.remove(ids[n])
3157 revs.remove(ids[n])
3158 elif ctx.hex() in ids:
3158 elif ctx.hex() in ids:
3159 r = ids[ctx.hex()]
3159 r = ids[ctx.hex()]
3160 if r in revs:
3160 if r in revs:
3161 ui.warn(
3161 ui.warn(
3162 _(
3162 _(
3163 b'skipping already grafted revision %d:%s '
3163 b'skipping already grafted revision %d:%s '
3164 b'(was grafted from %d:%s)\n'
3164 b'(was grafted from %d:%s)\n'
3165 )
3165 )
3166 % (r, repo[r], rev, ctx)
3166 % (r, repo[r], rev, ctx)
3167 )
3167 )
3168 revs.remove(r)
3168 revs.remove(r)
3169 if not revs:
3169 if not revs:
3170 return -1
3170 return -1
3171
3171
3172 if opts.get(b'no_commit'):
3172 if opts.get(b'no_commit'):
3173 statedata[b'no_commit'] = True
3173 statedata[b'no_commit'] = True
3174 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3174 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3175 desc = b'%d:%s "%s"' % (
3175 desc = b'%d:%s "%s"' % (
3176 ctx.rev(),
3176 ctx.rev(),
3177 ctx,
3177 ctx,
3178 ctx.description().split(b'\n', 1)[0],
3178 ctx.description().split(b'\n', 1)[0],
3179 )
3179 )
3180 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3180 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3181 if names:
3181 if names:
3182 desc += b' (%s)' % b' '.join(names)
3182 desc += b' (%s)' % b' '.join(names)
3183 ui.status(_(b'grafting %s\n') % desc)
3183 ui.status(_(b'grafting %s\n') % desc)
3184 if opts.get(b'dry_run'):
3184 if opts.get(b'dry_run'):
3185 continue
3185 continue
3186
3186
3187 source = ctx.extra().get(b'source')
3187 source = ctx.extra().get(b'source')
3188 extra = {}
3188 extra = {}
3189 if source:
3189 if source:
3190 extra[b'source'] = source
3190 extra[b'source'] = source
3191 extra[b'intermediate-source'] = ctx.hex()
3191 extra[b'intermediate-source'] = ctx.hex()
3192 else:
3192 else:
3193 extra[b'source'] = ctx.hex()
3193 extra[b'source'] = ctx.hex()
3194 user = ctx.user()
3194 user = ctx.user()
3195 if opts.get(b'user'):
3195 if opts.get(b'user'):
3196 user = opts[b'user']
3196 user = opts[b'user']
3197 statedata[b'user'] = user
3197 statedata[b'user'] = user
3198 date = ctx.date()
3198 date = ctx.date()
3199 if opts.get(b'date'):
3199 if opts.get(b'date'):
3200 date = opts[b'date']
3200 date = opts[b'date']
3201 statedata[b'date'] = date
3201 statedata[b'date'] = date
3202 message = ctx.description()
3202 message = ctx.description()
3203 if opts.get(b'log'):
3203 if opts.get(b'log'):
3204 message += b'\n(grafted from %s)' % ctx.hex()
3204 message += b'\n(grafted from %s)' % ctx.hex()
3205 statedata[b'log'] = True
3205 statedata[b'log'] = True
3206
3206
3207 # we don't merge the first commit when continuing
3207 # we don't merge the first commit when continuing
3208 if not cont:
3208 if not cont:
3209 # perform the graft merge with p1(rev) as 'ancestor'
3209 # perform the graft merge with p1(rev) as 'ancestor'
3210 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3210 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3211 base = ctx.p1() if basectx is None else basectx
3211 base = ctx.p1() if basectx is None else basectx
3212 with ui.configoverride(overrides, b'graft'):
3212 with ui.configoverride(overrides, b'graft'):
3213 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3213 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3214 # report any conflicts
3214 # report any conflicts
3215 if stats.unresolvedcount > 0:
3215 if stats.unresolvedcount > 0:
3216 # write out state for --continue
3216 # write out state for --continue
3217 nodes = [repo[rev].hex() for rev in revs[pos:]]
3217 nodes = [repo[rev].hex() for rev in revs[pos:]]
3218 statedata[b'nodes'] = nodes
3218 statedata[b'nodes'] = nodes
3219 stateversion = 1
3219 stateversion = 1
3220 graftstate.save(stateversion, statedata)
3220 graftstate.save(stateversion, statedata)
3221 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3221 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3222 raise error.Abort(
3222 raise error.Abort(
3223 _(b"unresolved conflicts, can't continue"), hint=hint
3223 _(b"unresolved conflicts, can't continue"), hint=hint
3224 )
3224 )
3225 else:
3225 else:
3226 cont = False
3226 cont = False
3227
3227
3228 # commit if --no-commit is false
3228 # commit if --no-commit is false
3229 if not opts.get(b'no_commit'):
3229 if not opts.get(b'no_commit'):
3230 node = repo.commit(
3230 node = repo.commit(
3231 text=message, user=user, date=date, extra=extra, editor=editor
3231 text=message, user=user, date=date, extra=extra, editor=editor
3232 )
3232 )
3233 if node is None:
3233 if node is None:
3234 ui.warn(
3234 ui.warn(
3235 _(b'note: graft of %d:%s created no changes to commit\n')
3235 _(b'note: graft of %d:%s created no changes to commit\n')
3236 % (ctx.rev(), ctx)
3236 % (ctx.rev(), ctx)
3237 )
3237 )
3238 # checking that newnodes exist because old state files won't have it
3238 # checking that newnodes exist because old state files won't have it
3239 elif statedata.get(b'newnodes') is not None:
3239 elif statedata.get(b'newnodes') is not None:
3240 statedata[b'newnodes'].append(node)
3240 statedata[b'newnodes'].append(node)
3241
3241
3242 # remove state when we complete successfully
3242 # remove state when we complete successfully
3243 if not opts.get(b'dry_run'):
3243 if not opts.get(b'dry_run'):
3244 graftstate.delete()
3244 graftstate.delete()
3245
3245
3246 return 0
3246 return 0
3247
3247
3248
3248
3249 def _stopgraft(ui, repo, graftstate):
3249 def _stopgraft(ui, repo, graftstate):
3250 """stop the interrupted graft"""
3250 """stop the interrupted graft"""
3251 if not graftstate.exists():
3251 if not graftstate.exists():
3252 raise error.Abort(_(b"no interrupted graft found"))
3252 raise error.Abort(_(b"no interrupted graft found"))
3253 pctx = repo[b'.']
3253 pctx = repo[b'.']
3254 hg.updaterepo(repo, pctx.node(), overwrite=True)
3254 hg.updaterepo(repo, pctx.node(), overwrite=True)
3255 graftstate.delete()
3255 graftstate.delete()
3256 ui.status(_(b"stopped the interrupted graft\n"))
3256 ui.status(_(b"stopped the interrupted graft\n"))
3257 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3257 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3258 return 0
3258 return 0
3259
3259
3260
3260
3261 statemod.addunfinished(
3261 statemod.addunfinished(
3262 b'graft',
3262 b'graft',
3263 fname=b'graftstate',
3263 fname=b'graftstate',
3264 clearable=True,
3264 clearable=True,
3265 stopflag=True,
3265 stopflag=True,
3266 continueflag=True,
3266 continueflag=True,
3267 abortfunc=cmdutil.hgabortgraft,
3267 abortfunc=cmdutil.hgabortgraft,
3268 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3268 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3269 )
3269 )
3270
3270
3271
3271
3272 @command(
3272 @command(
3273 b'grep',
3273 b'grep',
3274 [
3274 [
3275 (b'0', b'print0', None, _(b'end fields with NUL')),
3275 (b'0', b'print0', None, _(b'end fields with NUL')),
3276 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3276 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3277 (
3277 (
3278 b'',
3278 b'',
3279 b'diff',
3279 b'diff',
3280 None,
3280 None,
3281 _(
3281 _(
3282 b'search revision differences for when the pattern was added '
3282 b'search revision differences for when the pattern was added '
3283 b'or removed'
3283 b'or removed'
3284 ),
3284 ),
3285 ),
3285 ),
3286 (b'a', b'text', None, _(b'treat all files as text')),
3286 (b'a', b'text', None, _(b'treat all files as text')),
3287 (
3287 (
3288 b'f',
3288 b'f',
3289 b'follow',
3289 b'follow',
3290 None,
3290 None,
3291 _(
3291 _(
3292 b'follow changeset history,'
3292 b'follow changeset history,'
3293 b' or file history across copies and renames'
3293 b' or file history across copies and renames'
3294 ),
3294 ),
3295 ),
3295 ),
3296 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3296 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3297 (
3297 (
3298 b'l',
3298 b'l',
3299 b'files-with-matches',
3299 b'files-with-matches',
3300 None,
3300 None,
3301 _(b'print only filenames and revisions that match'),
3301 _(b'print only filenames and revisions that match'),
3302 ),
3302 ),
3303 (b'n', b'line-number', None, _(b'print matching line numbers')),
3303 (b'n', b'line-number', None, _(b'print matching line numbers')),
3304 (
3304 (
3305 b'r',
3305 b'r',
3306 b'rev',
3306 b'rev',
3307 [],
3307 [],
3308 _(b'search files changed within revision range'),
3308 _(b'search files changed within revision range'),
3309 _(b'REV'),
3309 _(b'REV'),
3310 ),
3310 ),
3311 (
3311 (
3312 b'',
3312 b'',
3313 b'all-files',
3313 b'all-files',
3314 None,
3314 None,
3315 _(
3315 _(
3316 b'include all files in the changeset while grepping (DEPRECATED)'
3316 b'include all files in the changeset while grepping (DEPRECATED)'
3317 ),
3317 ),
3318 ),
3318 ),
3319 (b'u', b'user', None, _(b'list the author (long with -v)')),
3319 (b'u', b'user', None, _(b'list the author (long with -v)')),
3320 (b'd', b'date', None, _(b'list the date (short with -q)')),
3320 (b'd', b'date', None, _(b'list the date (short with -q)')),
3321 ]
3321 ]
3322 + formatteropts
3322 + formatteropts
3323 + walkopts,
3323 + walkopts,
3324 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3324 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3325 helpcategory=command.CATEGORY_FILE_CONTENTS,
3325 helpcategory=command.CATEGORY_FILE_CONTENTS,
3326 inferrepo=True,
3326 inferrepo=True,
3327 intents={INTENT_READONLY},
3327 intents={INTENT_READONLY},
3328 )
3328 )
3329 def grep(ui, repo, pattern, *pats, **opts):
3329 def grep(ui, repo, pattern, *pats, **opts):
3330 """search for a pattern in specified files
3330 """search for a pattern in specified files
3331
3331
3332 Search the working directory or revision history for a regular
3332 Search the working directory or revision history for a regular
3333 expression in the specified files for the entire repository.
3333 expression in the specified files for the entire repository.
3334
3334
3335 By default, grep searches the repository files in the working
3335 By default, grep searches the repository files in the working
3336 directory and prints the files where it finds a match. To specify
3336 directory and prints the files where it finds a match. To specify
3337 historical revisions instead of the working directory, use the
3337 historical revisions instead of the working directory, use the
3338 --rev flag.
3338 --rev flag.
3339
3339
3340 To search instead historical revision differences that contains a
3340 To search instead historical revision differences that contains a
3341 change in match status ("-" for a match that becomes a non-match,
3341 change in match status ("-" for a match that becomes a non-match,
3342 or "+" for a non-match that becomes a match), use the --diff flag.
3342 or "+" for a non-match that becomes a match), use the --diff flag.
3343
3343
3344 PATTERN can be any Python (roughly Perl-compatible) regular
3344 PATTERN can be any Python (roughly Perl-compatible) regular
3345 expression.
3345 expression.
3346
3346
3347 If no FILEs are specified and the --rev flag isn't supplied, all
3347 If no FILEs are specified and the --rev flag isn't supplied, all
3348 files in the working directory are searched. When using the --rev
3348 files in the working directory are searched. When using the --rev
3349 flag and specifying FILEs, use the --follow argument to also
3349 flag and specifying FILEs, use the --follow argument to also
3350 follow the specified FILEs across renames and copies.
3350 follow the specified FILEs across renames and copies.
3351
3351
3352 .. container:: verbose
3352 .. container:: verbose
3353
3353
3354 Template:
3354 Template:
3355
3355
3356 The following keywords are supported in addition to the common template
3356 The following keywords are supported in addition to the common template
3357 keywords and functions. See also :hg:`help templates`.
3357 keywords and functions. See also :hg:`help templates`.
3358
3358
3359 :change: String. Character denoting insertion ``+`` or removal ``-``.
3359 :change: String. Character denoting insertion ``+`` or removal ``-``.
3360 Available if ``--diff`` is specified.
3360 Available if ``--diff`` is specified.
3361 :lineno: Integer. Line number of the match.
3361 :lineno: Integer. Line number of the match.
3362 :path: String. Repository-absolute path of the file.
3362 :path: String. Repository-absolute path of the file.
3363 :texts: List of text chunks.
3363 :texts: List of text chunks.
3364
3364
3365 And each entry of ``{texts}`` provides the following sub-keywords.
3365 And each entry of ``{texts}`` provides the following sub-keywords.
3366
3366
3367 :matched: Boolean. True if the chunk matches the specified pattern.
3367 :matched: Boolean. True if the chunk matches the specified pattern.
3368 :text: String. Chunk content.
3368 :text: String. Chunk content.
3369
3369
3370 See :hg:`help templates.operators` for the list expansion syntax.
3370 See :hg:`help templates.operators` for the list expansion syntax.
3371
3371
3372 Returns 0 if a match is found, 1 otherwise.
3372 Returns 0 if a match is found, 1 otherwise.
3373
3373
3374 """
3374 """
3375 opts = pycompat.byteskwargs(opts)
3375 opts = pycompat.byteskwargs(opts)
3376 diff = opts.get(b'all') or opts.get(b'diff')
3376 diff = opts.get(b'all') or opts.get(b'diff')
3377 if diff and opts.get(b'all_files'):
3377 if diff and opts.get(b'all_files'):
3378 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3378 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3379 if opts.get(b'all_files') is None and not diff:
3379 if opts.get(b'all_files') is None and not diff:
3380 opts[b'all_files'] = True
3380 opts[b'all_files'] = True
3381 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3381 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3382 all_files = opts.get(b'all_files')
3382 all_files = opts.get(b'all_files')
3383 if plaingrep:
3383 if plaingrep:
3384 opts[b'rev'] = [b'wdir()']
3384 opts[b'rev'] = [b'wdir()']
3385
3385
3386 reflags = re.M
3386 reflags = re.M
3387 if opts.get(b'ignore_case'):
3387 if opts.get(b'ignore_case'):
3388 reflags |= re.I
3388 reflags |= re.I
3389 try:
3389 try:
3390 regexp = util.re.compile(pattern, reflags)
3390 regexp = util.re.compile(pattern, reflags)
3391 except re.error as inst:
3391 except re.error as inst:
3392 ui.warn(
3392 ui.warn(
3393 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3393 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3394 )
3394 )
3395 return 1
3395 return 1
3396 sep, eol = b':', b'\n'
3396 sep, eol = b':', b'\n'
3397 if opts.get(b'print0'):
3397 if opts.get(b'print0'):
3398 sep = eol = b'\0'
3398 sep = eol = b'\0'
3399
3399
3400 getfile = util.lrucachefunc(repo.file)
3400 getfile = util.lrucachefunc(repo.file)
3401
3401
3402 def matchlines(body):
3402 def matchlines(body):
3403 begin = 0
3403 begin = 0
3404 linenum = 0
3404 linenum = 0
3405 while begin < len(body):
3405 while begin < len(body):
3406 match = regexp.search(body, begin)
3406 match = regexp.search(body, begin)
3407 if not match:
3407 if not match:
3408 break
3408 break
3409 mstart, mend = match.span()
3409 mstart, mend = match.span()
3410 linenum += body.count(b'\n', begin, mstart) + 1
3410 linenum += body.count(b'\n', begin, mstart) + 1
3411 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3411 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3412 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3412 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3413 lend = begin - 1
3413 lend = begin - 1
3414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3415
3415
3416 class linestate(object):
3416 class linestate(object):
3417 def __init__(self, line, linenum, colstart, colend):
3417 def __init__(self, line, linenum, colstart, colend):
3418 self.line = line
3418 self.line = line
3419 self.linenum = linenum
3419 self.linenum = linenum
3420 self.colstart = colstart
3420 self.colstart = colstart
3421 self.colend = colend
3421 self.colend = colend
3422
3422
3423 def __hash__(self):
3423 def __hash__(self):
3424 return hash((self.linenum, self.line))
3424 return hash((self.linenum, self.line))
3425
3425
3426 def __eq__(self, other):
3426 def __eq__(self, other):
3427 return self.line == other.line
3427 return self.line == other.line
3428
3428
3429 def findpos(self):
3429 def findpos(self):
3430 """Iterate all (start, end) indices of matches"""
3430 """Iterate all (start, end) indices of matches"""
3431 yield self.colstart, self.colend
3431 yield self.colstart, self.colend
3432 p = self.colend
3432 p = self.colend
3433 while p < len(self.line):
3433 while p < len(self.line):
3434 m = regexp.search(self.line, p)
3434 m = regexp.search(self.line, p)
3435 if not m:
3435 if not m:
3436 break
3436 break
3437 yield m.span()
3437 yield m.span()
3438 p = m.end()
3438 p = m.end()
3439
3439
3440 matches = {}
3440 matches = {}
3441 copies = {}
3441 copies = {}
3442
3442
3443 def grepbody(fn, rev, body):
3443 def grepbody(fn, rev, body):
3444 matches[rev].setdefault(fn, [])
3444 matches[rev].setdefault(fn, [])
3445 m = matches[rev][fn]
3445 m = matches[rev][fn]
3446 if body is None:
3446 if body is None:
3447 return
3447 return
3448
3448
3449 for lnum, cstart, cend, line in matchlines(body):
3449 for lnum, cstart, cend, line in matchlines(body):
3450 s = linestate(line, lnum, cstart, cend)
3450 s = linestate(line, lnum, cstart, cend)
3451 m.append(s)
3451 m.append(s)
3452
3452
3453 def difflinestates(a, b):
3453 def difflinestates(a, b):
3454 sm = difflib.SequenceMatcher(None, a, b)
3454 sm = difflib.SequenceMatcher(None, a, b)
3455 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3455 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3456 if tag == 'insert':
3456 if tag == 'insert':
3457 for i in pycompat.xrange(blo, bhi):
3457 for i in pycompat.xrange(blo, bhi):
3458 yield (b'+', b[i])
3458 yield (b'+', b[i])
3459 elif tag == 'delete':
3459 elif tag == 'delete':
3460 for i in pycompat.xrange(alo, ahi):
3460 for i in pycompat.xrange(alo, ahi):
3461 yield (b'-', a[i])
3461 yield (b'-', a[i])
3462 elif tag == 'replace':
3462 elif tag == 'replace':
3463 for i in pycompat.xrange(alo, ahi):
3463 for i in pycompat.xrange(alo, ahi):
3464 yield (b'-', a[i])
3464 yield (b'-', a[i])
3465 for i in pycompat.xrange(blo, bhi):
3465 for i in pycompat.xrange(blo, bhi):
3466 yield (b'+', b[i])
3466 yield (b'+', b[i])
3467
3467
3468 uipathfn = scmutil.getuipathfn(repo)
3468 uipathfn = scmutil.getuipathfn(repo)
3469
3469
3470 def display(fm, fn, ctx, pstates, states):
3470 def display(fm, fn, ctx, pstates, states):
3471 rev = scmutil.intrev(ctx)
3471 rev = scmutil.intrev(ctx)
3472 if fm.isplain():
3472 if fm.isplain():
3473 formatuser = ui.shortuser
3473 formatuser = ui.shortuser
3474 else:
3474 else:
3475 formatuser = pycompat.bytestr
3475 formatuser = pycompat.bytestr
3476 if ui.quiet:
3476 if ui.quiet:
3477 datefmt = b'%Y-%m-%d'
3477 datefmt = b'%Y-%m-%d'
3478 else:
3478 else:
3479 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3479 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3480 found = False
3480 found = False
3481
3481
3482 @util.cachefunc
3482 @util.cachefunc
3483 def binary():
3483 def binary():
3484 flog = getfile(fn)
3484 flog = getfile(fn)
3485 try:
3485 try:
3486 return stringutil.binary(flog.read(ctx.filenode(fn)))
3486 return stringutil.binary(flog.read(ctx.filenode(fn)))
3487 except error.WdirUnsupported:
3487 except error.WdirUnsupported:
3488 return ctx[fn].isbinary()
3488 return ctx[fn].isbinary()
3489
3489
3490 fieldnamemap = {b'linenumber': b'lineno'}
3490 fieldnamemap = {b'linenumber': b'lineno'}
3491 if diff:
3491 if diff:
3492 iter = difflinestates(pstates, states)
3492 iter = difflinestates(pstates, states)
3493 else:
3493 else:
3494 iter = [(b'', l) for l in states]
3494 iter = [(b'', l) for l in states]
3495 for change, l in iter:
3495 for change, l in iter:
3496 fm.startitem()
3496 fm.startitem()
3497 fm.context(ctx=ctx)
3497 fm.context(ctx=ctx)
3498 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3498 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3499 fm.plain(uipathfn(fn), label=b'grep.filename')
3499 fm.plain(uipathfn(fn), label=b'grep.filename')
3500
3500
3501 cols = [
3501 cols = [
3502 (b'rev', b'%d', rev, not plaingrep, b''),
3502 (b'rev', b'%d', rev, not plaingrep, b''),
3503 (
3503 (
3504 b'linenumber',
3504 b'linenumber',
3505 b'%d',
3505 b'%d',
3506 l.linenum,
3506 l.linenum,
3507 opts.get(b'line_number'),
3507 opts.get(b'line_number'),
3508 b'',
3508 b'',
3509 ),
3509 ),
3510 ]
3510 ]
3511 if diff:
3511 if diff:
3512 cols.append(
3512 cols.append(
3513 (
3513 (
3514 b'change',
3514 b'change',
3515 b'%s',
3515 b'%s',
3516 change,
3516 change,
3517 True,
3517 True,
3518 b'grep.inserted '
3518 b'grep.inserted '
3519 if change == b'+'
3519 if change == b'+'
3520 else b'grep.deleted ',
3520 else b'grep.deleted ',
3521 )
3521 )
3522 )
3522 )
3523 cols.extend(
3523 cols.extend(
3524 [
3524 [
3525 (
3525 (
3526 b'user',
3526 b'user',
3527 b'%s',
3527 b'%s',
3528 formatuser(ctx.user()),
3528 formatuser(ctx.user()),
3529 opts.get(b'user'),
3529 opts.get(b'user'),
3530 b'',
3530 b'',
3531 ),
3531 ),
3532 (
3532 (
3533 b'date',
3533 b'date',
3534 b'%s',
3534 b'%s',
3535 fm.formatdate(ctx.date(), datefmt),
3535 fm.formatdate(ctx.date(), datefmt),
3536 opts.get(b'date'),
3536 opts.get(b'date'),
3537 b'',
3537 b'',
3538 ),
3538 ),
3539 ]
3539 ]
3540 )
3540 )
3541 for name, fmt, data, cond, extra_label in cols:
3541 for name, fmt, data, cond, extra_label in cols:
3542 if cond:
3542 if cond:
3543 fm.plain(sep, label=b'grep.sep')
3543 fm.plain(sep, label=b'grep.sep')
3544 field = fieldnamemap.get(name, name)
3544 field = fieldnamemap.get(name, name)
3545 label = extra_label + (b'grep.%s' % name)
3545 label = extra_label + (b'grep.%s' % name)
3546 fm.condwrite(cond, field, fmt, data, label=label)
3546 fm.condwrite(cond, field, fmt, data, label=label)
3547 if not opts.get(b'files_with_matches'):
3547 if not opts.get(b'files_with_matches'):
3548 fm.plain(sep, label=b'grep.sep')
3548 fm.plain(sep, label=b'grep.sep')
3549 if not opts.get(b'text') and binary():
3549 if not opts.get(b'text') and binary():
3550 fm.plain(_(b" Binary file matches"))
3550 fm.plain(_(b" Binary file matches"))
3551 else:
3551 else:
3552 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3552 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3553 fm.plain(eol)
3553 fm.plain(eol)
3554 found = True
3554 found = True
3555 if opts.get(b'files_with_matches'):
3555 if opts.get(b'files_with_matches'):
3556 break
3556 break
3557 return found
3557 return found
3558
3558
3559 def displaymatches(fm, l):
3559 def displaymatches(fm, l):
3560 p = 0
3560 p = 0
3561 for s, e in l.findpos():
3561 for s, e in l.findpos():
3562 if p < s:
3562 if p < s:
3563 fm.startitem()
3563 fm.startitem()
3564 fm.write(b'text', b'%s', l.line[p:s])
3564 fm.write(b'text', b'%s', l.line[p:s])
3565 fm.data(matched=False)
3565 fm.data(matched=False)
3566 fm.startitem()
3566 fm.startitem()
3567 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3567 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3568 fm.data(matched=True)
3568 fm.data(matched=True)
3569 p = e
3569 p = e
3570 if p < len(l.line):
3570 if p < len(l.line):
3571 fm.startitem()
3571 fm.startitem()
3572 fm.write(b'text', b'%s', l.line[p:])
3572 fm.write(b'text', b'%s', l.line[p:])
3573 fm.data(matched=False)
3573 fm.data(matched=False)
3574 fm.end()
3574 fm.end()
3575
3575
3576 skip = set()
3576 skip = set()
3577 revfiles = {}
3577 revfiles = {}
3578 match = scmutil.match(repo[None], pats, opts)
3578 match = scmutil.match(repo[None], pats, opts)
3579 found = False
3579 found = False
3580 follow = opts.get(b'follow')
3580 follow = opts.get(b'follow')
3581
3581
3582 getrenamed = scmutil.getrenamedfn(repo)
3582 getrenamed = scmutil.getrenamedfn(repo)
3583
3583
3584 def get_file_content(filename, filelog, filenode, context, revision):
3584 def get_file_content(filename, filelog, filenode, context, revision):
3585 try:
3585 try:
3586 content = filelog.read(filenode)
3586 content = filelog.read(filenode)
3587 except error.WdirUnsupported:
3587 except error.WdirUnsupported:
3588 content = context[filename].data()
3588 content = context[filename].data()
3589 except error.CensoredNodeError:
3589 except error.CensoredNodeError:
3590 content = None
3590 content = None
3591 ui.warn(
3591 ui.warn(
3592 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3592 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3593 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3593 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3594 )
3594 )
3595 return content
3595 return content
3596
3596
3597 def prep(ctx, fns):
3597 def prep(ctx, fns):
3598 rev = ctx.rev()
3598 rev = ctx.rev()
3599 pctx = ctx.p1()
3599 pctx = ctx.p1()
3600 parent = pctx.rev()
3600 parent = pctx.rev()
3601 matches.setdefault(rev, {})
3601 matches.setdefault(rev, {})
3602 matches.setdefault(parent, {})
3602 matches.setdefault(parent, {})
3603 files = revfiles.setdefault(rev, [])
3603 files = revfiles.setdefault(rev, [])
3604 for fn in fns:
3604 for fn in fns:
3605 flog = getfile(fn)
3605 flog = getfile(fn)
3606 try:
3606 try:
3607 fnode = ctx.filenode(fn)
3607 fnode = ctx.filenode(fn)
3608 except error.LookupError:
3608 except error.LookupError:
3609 continue
3609 continue
3610
3610
3611 copy = None
3611 copy = None
3612 if follow:
3612 if follow:
3613 copy = getrenamed(fn, rev)
3613 copy = getrenamed(fn, rev)
3614 if copy:
3614 if copy:
3615 copies.setdefault(rev, {})[fn] = copy
3615 copies.setdefault(rev, {})[fn] = copy
3616 if fn in skip:
3616 if fn in skip:
3617 skip.add(copy)
3617 skip.add(copy)
3618 if fn in skip:
3618 if fn in skip:
3619 continue
3619 continue
3620 files.append(fn)
3620 files.append(fn)
3621
3621
3622 if fn not in matches[rev]:
3622 if fn not in matches[rev]:
3623 content = get_file_content(fn, flog, fnode, ctx, rev)
3623 content = get_file_content(fn, flog, fnode, ctx, rev)
3624 grepbody(fn, rev, content)
3624 grepbody(fn, rev, content)
3625
3625
3626 pfn = copy or fn
3626 pfn = copy or fn
3627 if pfn not in matches[parent]:
3627 if pfn not in matches[parent]:
3628 try:
3628 try:
3629 pfnode = pctx.filenode(pfn)
3629 pfnode = pctx.filenode(pfn)
3630 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3630 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3631 grepbody(pfn, parent, pcontent)
3631 grepbody(pfn, parent, pcontent)
3632 except error.LookupError:
3632 except error.LookupError:
3633 pass
3633 pass
3634
3634
3635 ui.pager(b'grep')
3635 ui.pager(b'grep')
3636 fm = ui.formatter(b'grep', opts)
3636 fm = ui.formatter(b'grep', opts)
3637 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3637 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3638 rev = ctx.rev()
3638 rev = ctx.rev()
3639 parent = ctx.p1().rev()
3639 parent = ctx.p1().rev()
3640 for fn in sorted(revfiles.get(rev, [])):
3640 for fn in sorted(revfiles.get(rev, [])):
3641 states = matches[rev][fn]
3641 states = matches[rev][fn]
3642 copy = copies.get(rev, {}).get(fn)
3642 copy = copies.get(rev, {}).get(fn)
3643 if fn in skip:
3643 if fn in skip:
3644 if copy:
3644 if copy:
3645 skip.add(copy)
3645 skip.add(copy)
3646 continue
3646 continue
3647 pstates = matches.get(parent, {}).get(copy or fn, [])
3647 pstates = matches.get(parent, {}).get(copy or fn, [])
3648 if pstates or states:
3648 if pstates or states:
3649 r = display(fm, fn, ctx, pstates, states)
3649 r = display(fm, fn, ctx, pstates, states)
3650 found = found or r
3650 found = found or r
3651 if r and not diff and not all_files:
3651 if r and not diff and not all_files:
3652 skip.add(fn)
3652 skip.add(fn)
3653 if copy:
3653 if copy:
3654 skip.add(copy)
3654 skip.add(copy)
3655 del revfiles[rev]
3655 del revfiles[rev]
3656 # We will keep the matches dict for the duration of the window
3656 # We will keep the matches dict for the duration of the window
3657 # clear the matches dict once the window is over
3657 # clear the matches dict once the window is over
3658 if not revfiles:
3658 if not revfiles:
3659 matches.clear()
3659 matches.clear()
3660 fm.end()
3660 fm.end()
3661
3661
3662 return not found
3662 return not found
3663
3663
3664
3664
3665 @command(
3665 @command(
3666 b'heads',
3666 b'heads',
3667 [
3667 [
3668 (
3668 (
3669 b'r',
3669 b'r',
3670 b'rev',
3670 b'rev',
3671 b'',
3671 b'',
3672 _(b'show only heads which are descendants of STARTREV'),
3672 _(b'show only heads which are descendants of STARTREV'),
3673 _(b'STARTREV'),
3673 _(b'STARTREV'),
3674 ),
3674 ),
3675 (b't', b'topo', False, _(b'show topological heads only')),
3675 (b't', b'topo', False, _(b'show topological heads only')),
3676 (
3676 (
3677 b'a',
3677 b'a',
3678 b'active',
3678 b'active',
3679 False,
3679 False,
3680 _(b'show active branchheads only (DEPRECATED)'),
3680 _(b'show active branchheads only (DEPRECATED)'),
3681 ),
3681 ),
3682 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3682 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3683 ]
3683 ]
3684 + templateopts,
3684 + templateopts,
3685 _(b'[-ct] [-r STARTREV] [REV]...'),
3685 _(b'[-ct] [-r STARTREV] [REV]...'),
3686 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3686 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3687 intents={INTENT_READONLY},
3687 intents={INTENT_READONLY},
3688 )
3688 )
3689 def heads(ui, repo, *branchrevs, **opts):
3689 def heads(ui, repo, *branchrevs, **opts):
3690 """show branch heads
3690 """show branch heads
3691
3691
3692 With no arguments, show all open branch heads in the repository.
3692 With no arguments, show all open branch heads in the repository.
3693 Branch heads are changesets that have no descendants on the
3693 Branch heads are changesets that have no descendants on the
3694 same branch. They are where development generally takes place and
3694 same branch. They are where development generally takes place and
3695 are the usual targets for update and merge operations.
3695 are the usual targets for update and merge operations.
3696
3696
3697 If one or more REVs are given, only open branch heads on the
3697 If one or more REVs are given, only open branch heads on the
3698 branches associated with the specified changesets are shown. This
3698 branches associated with the specified changesets are shown. This
3699 means that you can use :hg:`heads .` to see the heads on the
3699 means that you can use :hg:`heads .` to see the heads on the
3700 currently checked-out branch.
3700 currently checked-out branch.
3701
3701
3702 If -c/--closed is specified, also show branch heads marked closed
3702 If -c/--closed is specified, also show branch heads marked closed
3703 (see :hg:`commit --close-branch`).
3703 (see :hg:`commit --close-branch`).
3704
3704
3705 If STARTREV is specified, only those heads that are descendants of
3705 If STARTREV is specified, only those heads that are descendants of
3706 STARTREV will be displayed.
3706 STARTREV will be displayed.
3707
3707
3708 If -t/--topo is specified, named branch mechanics will be ignored and only
3708 If -t/--topo is specified, named branch mechanics will be ignored and only
3709 topological heads (changesets with no children) will be shown.
3709 topological heads (changesets with no children) will be shown.
3710
3710
3711 Returns 0 if matching heads are found, 1 if not.
3711 Returns 0 if matching heads are found, 1 if not.
3712 """
3712 """
3713
3713
3714 opts = pycompat.byteskwargs(opts)
3714 opts = pycompat.byteskwargs(opts)
3715 start = None
3715 start = None
3716 rev = opts.get(b'rev')
3716 rev = opts.get(b'rev')
3717 if rev:
3717 if rev:
3718 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3718 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3719 start = scmutil.revsingle(repo, rev, None).node()
3719 start = scmutil.revsingle(repo, rev, None).node()
3720
3720
3721 if opts.get(b'topo'):
3721 if opts.get(b'topo'):
3722 heads = [repo[h] for h in repo.heads(start)]
3722 heads = [repo[h] for h in repo.heads(start)]
3723 else:
3723 else:
3724 heads = []
3724 heads = []
3725 for branch in repo.branchmap():
3725 for branch in repo.branchmap():
3726 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3726 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3727 heads = [repo[h] for h in heads]
3727 heads = [repo[h] for h in heads]
3728
3728
3729 if branchrevs:
3729 if branchrevs:
3730 branches = set(
3730 branches = set(
3731 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3731 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3732 )
3732 )
3733 heads = [h for h in heads if h.branch() in branches]
3733 heads = [h for h in heads if h.branch() in branches]
3734
3734
3735 if opts.get(b'active') and branchrevs:
3735 if opts.get(b'active') and branchrevs:
3736 dagheads = repo.heads(start)
3736 dagheads = repo.heads(start)
3737 heads = [h for h in heads if h.node() in dagheads]
3737 heads = [h for h in heads if h.node() in dagheads]
3738
3738
3739 if branchrevs:
3739 if branchrevs:
3740 haveheads = set(h.branch() for h in heads)
3740 haveheads = set(h.branch() for h in heads)
3741 if branches - haveheads:
3741 if branches - haveheads:
3742 headless = b', '.join(b for b in branches - haveheads)
3742 headless = b', '.join(b for b in branches - haveheads)
3743 msg = _(b'no open branch heads found on branches %s')
3743 msg = _(b'no open branch heads found on branches %s')
3744 if opts.get(b'rev'):
3744 if opts.get(b'rev'):
3745 msg += _(b' (started at %s)') % opts[b'rev']
3745 msg += _(b' (started at %s)') % opts[b'rev']
3746 ui.warn((msg + b'\n') % headless)
3746 ui.warn((msg + b'\n') % headless)
3747
3747
3748 if not heads:
3748 if not heads:
3749 return 1
3749 return 1
3750
3750
3751 ui.pager(b'heads')
3751 ui.pager(b'heads')
3752 heads = sorted(heads, key=lambda x: -(x.rev()))
3752 heads = sorted(heads, key=lambda x: -(x.rev()))
3753 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3753 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3754 for ctx in heads:
3754 for ctx in heads:
3755 displayer.show(ctx)
3755 displayer.show(ctx)
3756 displayer.close()
3756 displayer.close()
3757
3757
3758
3758
3759 @command(
3759 @command(
3760 b'help',
3760 b'help',
3761 [
3761 [
3762 (b'e', b'extension', None, _(b'show only help for extensions')),
3762 (b'e', b'extension', None, _(b'show only help for extensions')),
3763 (b'c', b'command', None, _(b'show only help for commands')),
3763 (b'c', b'command', None, _(b'show only help for commands')),
3764 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3764 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3765 (
3765 (
3766 b's',
3766 b's',
3767 b'system',
3767 b'system',
3768 [],
3768 [],
3769 _(b'show help for specific platform(s)'),
3769 _(b'show help for specific platform(s)'),
3770 _(b'PLATFORM'),
3770 _(b'PLATFORM'),
3771 ),
3771 ),
3772 ],
3772 ],
3773 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3773 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3774 helpcategory=command.CATEGORY_HELP,
3774 helpcategory=command.CATEGORY_HELP,
3775 norepo=True,
3775 norepo=True,
3776 intents={INTENT_READONLY},
3776 intents={INTENT_READONLY},
3777 )
3777 )
3778 def help_(ui, name=None, **opts):
3778 def help_(ui, name=None, **opts):
3779 """show help for a given topic or a help overview
3779 """show help for a given topic or a help overview
3780
3780
3781 With no arguments, print a list of commands with short help messages.
3781 With no arguments, print a list of commands with short help messages.
3782
3782
3783 Given a topic, extension, or command name, print help for that
3783 Given a topic, extension, or command name, print help for that
3784 topic.
3784 topic.
3785
3785
3786 Returns 0 if successful.
3786 Returns 0 if successful.
3787 """
3787 """
3788
3788
3789 keep = opts.get('system') or []
3789 keep = opts.get('system') or []
3790 if len(keep) == 0:
3790 if len(keep) == 0:
3791 if pycompat.sysplatform.startswith(b'win'):
3791 if pycompat.sysplatform.startswith(b'win'):
3792 keep.append(b'windows')
3792 keep.append(b'windows')
3793 elif pycompat.sysplatform == b'OpenVMS':
3793 elif pycompat.sysplatform == b'OpenVMS':
3794 keep.append(b'vms')
3794 keep.append(b'vms')
3795 elif pycompat.sysplatform == b'plan9':
3795 elif pycompat.sysplatform == b'plan9':
3796 keep.append(b'plan9')
3796 keep.append(b'plan9')
3797 else:
3797 else:
3798 keep.append(b'unix')
3798 keep.append(b'unix')
3799 keep.append(pycompat.sysplatform.lower())
3799 keep.append(pycompat.sysplatform.lower())
3800 if ui.verbose:
3800 if ui.verbose:
3801 keep.append(b'verbose')
3801 keep.append(b'verbose')
3802
3802
3803 commands = sys.modules[__name__]
3803 commands = sys.modules[__name__]
3804 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3804 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3805 ui.pager(b'help')
3805 ui.pager(b'help')
3806 ui.write(formatted)
3806 ui.write(formatted)
3807
3807
3808
3808
3809 @command(
3809 @command(
3810 b'identify|id',
3810 b'identify|id',
3811 [
3811 [
3812 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3812 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3813 (b'n', b'num', None, _(b'show local revision number')),
3813 (b'n', b'num', None, _(b'show local revision number')),
3814 (b'i', b'id', None, _(b'show global revision id')),
3814 (b'i', b'id', None, _(b'show global revision id')),
3815 (b'b', b'branch', None, _(b'show branch')),
3815 (b'b', b'branch', None, _(b'show branch')),
3816 (b't', b'tags', None, _(b'show tags')),
3816 (b't', b'tags', None, _(b'show tags')),
3817 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3817 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3818 ]
3818 ]
3819 + remoteopts
3819 + remoteopts
3820 + formatteropts,
3820 + formatteropts,
3821 _(b'[-nibtB] [-r REV] [SOURCE]'),
3821 _(b'[-nibtB] [-r REV] [SOURCE]'),
3822 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3822 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3823 optionalrepo=True,
3823 optionalrepo=True,
3824 intents={INTENT_READONLY},
3824 intents={INTENT_READONLY},
3825 )
3825 )
3826 def identify(
3826 def identify(
3827 ui,
3827 ui,
3828 repo,
3828 repo,
3829 source=None,
3829 source=None,
3830 rev=None,
3830 rev=None,
3831 num=None,
3831 num=None,
3832 id=None,
3832 id=None,
3833 branch=None,
3833 branch=None,
3834 tags=None,
3834 tags=None,
3835 bookmarks=None,
3835 bookmarks=None,
3836 **opts
3836 **opts
3837 ):
3837 ):
3838 """identify the working directory or specified revision
3838 """identify the working directory or specified revision
3839
3839
3840 Print a summary identifying the repository state at REV using one or
3840 Print a summary identifying the repository state at REV using one or
3841 two parent hash identifiers, followed by a "+" if the working
3841 two parent hash identifiers, followed by a "+" if the working
3842 directory has uncommitted changes, the branch name (if not default),
3842 directory has uncommitted changes, the branch name (if not default),
3843 a list of tags, and a list of bookmarks.
3843 a list of tags, and a list of bookmarks.
3844
3844
3845 When REV is not given, print a summary of the current state of the
3845 When REV is not given, print a summary of the current state of the
3846 repository including the working directory. Specify -r. to get information
3846 repository including the working directory. Specify -r. to get information
3847 of the working directory parent without scanning uncommitted changes.
3847 of the working directory parent without scanning uncommitted changes.
3848
3848
3849 Specifying a path to a repository root or Mercurial bundle will
3849 Specifying a path to a repository root or Mercurial bundle will
3850 cause lookup to operate on that repository/bundle.
3850 cause lookup to operate on that repository/bundle.
3851
3851
3852 .. container:: verbose
3852 .. container:: verbose
3853
3853
3854 Template:
3854 Template:
3855
3855
3856 The following keywords are supported in addition to the common template
3856 The following keywords are supported in addition to the common template
3857 keywords and functions. See also :hg:`help templates`.
3857 keywords and functions. See also :hg:`help templates`.
3858
3858
3859 :dirty: String. Character ``+`` denoting if the working directory has
3859 :dirty: String. Character ``+`` denoting if the working directory has
3860 uncommitted changes.
3860 uncommitted changes.
3861 :id: String. One or two nodes, optionally followed by ``+``.
3861 :id: String. One or two nodes, optionally followed by ``+``.
3862 :parents: List of strings. Parent nodes of the changeset.
3862 :parents: List of strings. Parent nodes of the changeset.
3863
3863
3864 Examples:
3864 Examples:
3865
3865
3866 - generate a build identifier for the working directory::
3866 - generate a build identifier for the working directory::
3867
3867
3868 hg id --id > build-id.dat
3868 hg id --id > build-id.dat
3869
3869
3870 - find the revision corresponding to a tag::
3870 - find the revision corresponding to a tag::
3871
3871
3872 hg id -n -r 1.3
3872 hg id -n -r 1.3
3873
3873
3874 - check the most recent revision of a remote repository::
3874 - check the most recent revision of a remote repository::
3875
3875
3876 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3876 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3877
3877
3878 See :hg:`log` for generating more information about specific revisions,
3878 See :hg:`log` for generating more information about specific revisions,
3879 including full hash identifiers.
3879 including full hash identifiers.
3880
3880
3881 Returns 0 if successful.
3881 Returns 0 if successful.
3882 """
3882 """
3883
3883
3884 opts = pycompat.byteskwargs(opts)
3884 opts = pycompat.byteskwargs(opts)
3885 if not repo and not source:
3885 if not repo and not source:
3886 raise error.Abort(
3886 raise error.Abort(
3887 _(b"there is no Mercurial repository here (.hg not found)")
3887 _(b"there is no Mercurial repository here (.hg not found)")
3888 )
3888 )
3889
3889
3890 default = not (num or id or branch or tags or bookmarks)
3890 default = not (num or id or branch or tags or bookmarks)
3891 output = []
3891 output = []
3892 revs = []
3892 revs = []
3893
3893
3894 if source:
3894 if source:
3895 source, branches = hg.parseurl(ui.expandpath(source))
3895 source, branches = hg.parseurl(ui.expandpath(source))
3896 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3896 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3897 repo = peer.local()
3897 repo = peer.local()
3898 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3898 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3899
3899
3900 fm = ui.formatter(b'identify', opts)
3900 fm = ui.formatter(b'identify', opts)
3901 fm.startitem()
3901 fm.startitem()
3902
3902
3903 if not repo:
3903 if not repo:
3904 if num or branch or tags:
3904 if num or branch or tags:
3905 raise error.Abort(
3905 raise error.Abort(
3906 _(b"can't query remote revision number, branch, or tags")
3906 _(b"can't query remote revision number, branch, or tags")
3907 )
3907 )
3908 if not rev and revs:
3908 if not rev and revs:
3909 rev = revs[0]
3909 rev = revs[0]
3910 if not rev:
3910 if not rev:
3911 rev = b"tip"
3911 rev = b"tip"
3912
3912
3913 remoterev = peer.lookup(rev)
3913 remoterev = peer.lookup(rev)
3914 hexrev = fm.hexfunc(remoterev)
3914 hexrev = fm.hexfunc(remoterev)
3915 if default or id:
3915 if default or id:
3916 output = [hexrev]
3916 output = [hexrev]
3917 fm.data(id=hexrev)
3917 fm.data(id=hexrev)
3918
3918
3919 @util.cachefunc
3919 @util.cachefunc
3920 def getbms():
3920 def getbms():
3921 bms = []
3921 bms = []
3922
3922
3923 if b'bookmarks' in peer.listkeys(b'namespaces'):
3923 if b'bookmarks' in peer.listkeys(b'namespaces'):
3924 hexremoterev = hex(remoterev)
3924 hexremoterev = hex(remoterev)
3925 bms = [
3925 bms = [
3926 bm
3926 bm
3927 for bm, bmr in pycompat.iteritems(
3927 for bm, bmr in pycompat.iteritems(
3928 peer.listkeys(b'bookmarks')
3928 peer.listkeys(b'bookmarks')
3929 )
3929 )
3930 if bmr == hexremoterev
3930 if bmr == hexremoterev
3931 ]
3931 ]
3932
3932
3933 return sorted(bms)
3933 return sorted(bms)
3934
3934
3935 if fm.isplain():
3935 if fm.isplain():
3936 if bookmarks:
3936 if bookmarks:
3937 output.extend(getbms())
3937 output.extend(getbms())
3938 elif default and not ui.quiet:
3938 elif default and not ui.quiet:
3939 # multiple bookmarks for a single parent separated by '/'
3939 # multiple bookmarks for a single parent separated by '/'
3940 bm = b'/'.join(getbms())
3940 bm = b'/'.join(getbms())
3941 if bm:
3941 if bm:
3942 output.append(bm)
3942 output.append(bm)
3943 else:
3943 else:
3944 fm.data(node=hex(remoterev))
3944 fm.data(node=hex(remoterev))
3945 if bookmarks or b'bookmarks' in fm.datahint():
3945 if bookmarks or b'bookmarks' in fm.datahint():
3946 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3946 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3947 else:
3947 else:
3948 if rev:
3948 if rev:
3949 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3949 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3950 ctx = scmutil.revsingle(repo, rev, None)
3950 ctx = scmutil.revsingle(repo, rev, None)
3951
3951
3952 if ctx.rev() is None:
3952 if ctx.rev() is None:
3953 ctx = repo[None]
3953 ctx = repo[None]
3954 parents = ctx.parents()
3954 parents = ctx.parents()
3955 taglist = []
3955 taglist = []
3956 for p in parents:
3956 for p in parents:
3957 taglist.extend(p.tags())
3957 taglist.extend(p.tags())
3958
3958
3959 dirty = b""
3959 dirty = b""
3960 if ctx.dirty(missing=True, merge=False, branch=False):
3960 if ctx.dirty(missing=True, merge=False, branch=False):
3961 dirty = b'+'
3961 dirty = b'+'
3962 fm.data(dirty=dirty)
3962 fm.data(dirty=dirty)
3963
3963
3964 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3964 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3965 if default or id:
3965 if default or id:
3966 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3966 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3967 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3967 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3968
3968
3969 if num:
3969 if num:
3970 numoutput = [b"%d" % p.rev() for p in parents]
3970 numoutput = [b"%d" % p.rev() for p in parents]
3971 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3971 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3972
3972
3973 fm.data(
3973 fm.data(
3974 parents=fm.formatlist(
3974 parents=fm.formatlist(
3975 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3975 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3976 )
3976 )
3977 )
3977 )
3978 else:
3978 else:
3979 hexoutput = fm.hexfunc(ctx.node())
3979 hexoutput = fm.hexfunc(ctx.node())
3980 if default or id:
3980 if default or id:
3981 output = [hexoutput]
3981 output = [hexoutput]
3982 fm.data(id=hexoutput)
3982 fm.data(id=hexoutput)
3983
3983
3984 if num:
3984 if num:
3985 output.append(pycompat.bytestr(ctx.rev()))
3985 output.append(pycompat.bytestr(ctx.rev()))
3986 taglist = ctx.tags()
3986 taglist = ctx.tags()
3987
3987
3988 if default and not ui.quiet:
3988 if default and not ui.quiet:
3989 b = ctx.branch()
3989 b = ctx.branch()
3990 if b != b'default':
3990 if b != b'default':
3991 output.append(b"(%s)" % b)
3991 output.append(b"(%s)" % b)
3992
3992
3993 # multiple tags for a single parent separated by '/'
3993 # multiple tags for a single parent separated by '/'
3994 t = b'/'.join(taglist)
3994 t = b'/'.join(taglist)
3995 if t:
3995 if t:
3996 output.append(t)
3996 output.append(t)
3997
3997
3998 # multiple bookmarks for a single parent separated by '/'
3998 # multiple bookmarks for a single parent separated by '/'
3999 bm = b'/'.join(ctx.bookmarks())
3999 bm = b'/'.join(ctx.bookmarks())
4000 if bm:
4000 if bm:
4001 output.append(bm)
4001 output.append(bm)
4002 else:
4002 else:
4003 if branch:
4003 if branch:
4004 output.append(ctx.branch())
4004 output.append(ctx.branch())
4005
4005
4006 if tags:
4006 if tags:
4007 output.extend(taglist)
4007 output.extend(taglist)
4008
4008
4009 if bookmarks:
4009 if bookmarks:
4010 output.extend(ctx.bookmarks())
4010 output.extend(ctx.bookmarks())
4011
4011
4012 fm.data(node=ctx.hex())
4012 fm.data(node=ctx.hex())
4013 fm.data(branch=ctx.branch())
4013 fm.data(branch=ctx.branch())
4014 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4014 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4015 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4015 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4016 fm.context(ctx=ctx)
4016 fm.context(ctx=ctx)
4017
4017
4018 fm.plain(b"%s\n" % b' '.join(output))
4018 fm.plain(b"%s\n" % b' '.join(output))
4019 fm.end()
4019 fm.end()
4020
4020
4021
4021
4022 @command(
4022 @command(
4023 b'import|patch',
4023 b'import|patch',
4024 [
4024 [
4025 (
4025 (
4026 b'p',
4026 b'p',
4027 b'strip',
4027 b'strip',
4028 1,
4028 1,
4029 _(
4029 _(
4030 b'directory strip option for patch. This has the same '
4030 b'directory strip option for patch. This has the same '
4031 b'meaning as the corresponding patch option'
4031 b'meaning as the corresponding patch option'
4032 ),
4032 ),
4033 _(b'NUM'),
4033 _(b'NUM'),
4034 ),
4034 ),
4035 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4035 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4036 (b'', b'secret', None, _(b'use the secret phase for committing')),
4036 (b'', b'secret', None, _(b'use the secret phase for committing')),
4037 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4037 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4038 (
4038 (
4039 b'f',
4039 b'f',
4040 b'force',
4040 b'force',
4041 None,
4041 None,
4042 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4042 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4043 ),
4043 ),
4044 (
4044 (
4045 b'',
4045 b'',
4046 b'no-commit',
4046 b'no-commit',
4047 None,
4047 None,
4048 _(b"don't commit, just update the working directory"),
4048 _(b"don't commit, just update the working directory"),
4049 ),
4049 ),
4050 (
4050 (
4051 b'',
4051 b'',
4052 b'bypass',
4052 b'bypass',
4053 None,
4053 None,
4054 _(b"apply patch without touching the working directory"),
4054 _(b"apply patch without touching the working directory"),
4055 ),
4055 ),
4056 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4056 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4057 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4057 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4058 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4058 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4059 (
4059 (
4060 b'',
4060 b'',
4061 b'import-branch',
4061 b'import-branch',
4062 None,
4062 None,
4063 _(b'use any branch information in patch (implied by --exact)'),
4063 _(b'use any branch information in patch (implied by --exact)'),
4064 ),
4064 ),
4065 ]
4065 ]
4066 + commitopts
4066 + commitopts
4067 + commitopts2
4067 + commitopts2
4068 + similarityopts,
4068 + similarityopts,
4069 _(b'[OPTION]... PATCH...'),
4069 _(b'[OPTION]... PATCH...'),
4070 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4070 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4071 )
4071 )
4072 def import_(ui, repo, patch1=None, *patches, **opts):
4072 def import_(ui, repo, patch1=None, *patches, **opts):
4073 """import an ordered set of patches
4073 """import an ordered set of patches
4074
4074
4075 Import a list of patches and commit them individually (unless
4075 Import a list of patches and commit them individually (unless
4076 --no-commit is specified).
4076 --no-commit is specified).
4077
4077
4078 To read a patch from standard input (stdin), use "-" as the patch
4078 To read a patch from standard input (stdin), use "-" as the patch
4079 name. If a URL is specified, the patch will be downloaded from
4079 name. If a URL is specified, the patch will be downloaded from
4080 there.
4080 there.
4081
4081
4082 Import first applies changes to the working directory (unless
4082 Import first applies changes to the working directory (unless
4083 --bypass is specified), import will abort if there are outstanding
4083 --bypass is specified), import will abort if there are outstanding
4084 changes.
4084 changes.
4085
4085
4086 Use --bypass to apply and commit patches directly to the
4086 Use --bypass to apply and commit patches directly to the
4087 repository, without affecting the working directory. Without
4087 repository, without affecting the working directory. Without
4088 --exact, patches will be applied on top of the working directory
4088 --exact, patches will be applied on top of the working directory
4089 parent revision.
4089 parent revision.
4090
4090
4091 You can import a patch straight from a mail message. Even patches
4091 You can import a patch straight from a mail message. Even patches
4092 as attachments work (to use the body part, it must have type
4092 as attachments work (to use the body part, it must have type
4093 text/plain or text/x-patch). From and Subject headers of email
4093 text/plain or text/x-patch). From and Subject headers of email
4094 message are used as default committer and commit message. All
4094 message are used as default committer and commit message. All
4095 text/plain body parts before first diff are added to the commit
4095 text/plain body parts before first diff are added to the commit
4096 message.
4096 message.
4097
4097
4098 If the imported patch was generated by :hg:`export`, user and
4098 If the imported patch was generated by :hg:`export`, user and
4099 description from patch override values from message headers and
4099 description from patch override values from message headers and
4100 body. Values given on command line with -m/--message and -u/--user
4100 body. Values given on command line with -m/--message and -u/--user
4101 override these.
4101 override these.
4102
4102
4103 If --exact is specified, import will set the working directory to
4103 If --exact is specified, import will set the working directory to
4104 the parent of each patch before applying it, and will abort if the
4104 the parent of each patch before applying it, and will abort if the
4105 resulting changeset has a different ID than the one recorded in
4105 resulting changeset has a different ID than the one recorded in
4106 the patch. This will guard against various ways that portable
4106 the patch. This will guard against various ways that portable
4107 patch formats and mail systems might fail to transfer Mercurial
4107 patch formats and mail systems might fail to transfer Mercurial
4108 data or metadata. See :hg:`bundle` for lossless transmission.
4108 data or metadata. See :hg:`bundle` for lossless transmission.
4109
4109
4110 Use --partial to ensure a changeset will be created from the patch
4110 Use --partial to ensure a changeset will be created from the patch
4111 even if some hunks fail to apply. Hunks that fail to apply will be
4111 even if some hunks fail to apply. Hunks that fail to apply will be
4112 written to a <target-file>.rej file. Conflicts can then be resolved
4112 written to a <target-file>.rej file. Conflicts can then be resolved
4113 by hand before :hg:`commit --amend` is run to update the created
4113 by hand before :hg:`commit --amend` is run to update the created
4114 changeset. This flag exists to let people import patches that
4114 changeset. This flag exists to let people import patches that
4115 partially apply without losing the associated metadata (author,
4115 partially apply without losing the associated metadata (author,
4116 date, description, ...).
4116 date, description, ...).
4117
4117
4118 .. note::
4118 .. note::
4119
4119
4120 When no hunks apply cleanly, :hg:`import --partial` will create
4120 When no hunks apply cleanly, :hg:`import --partial` will create
4121 an empty changeset, importing only the patch metadata.
4121 an empty changeset, importing only the patch metadata.
4122
4122
4123 With -s/--similarity, hg will attempt to discover renames and
4123 With -s/--similarity, hg will attempt to discover renames and
4124 copies in the patch in the same way as :hg:`addremove`.
4124 copies in the patch in the same way as :hg:`addremove`.
4125
4125
4126 It is possible to use external patch programs to perform the patch
4126 It is possible to use external patch programs to perform the patch
4127 by setting the ``ui.patch`` configuration option. For the default
4127 by setting the ``ui.patch`` configuration option. For the default
4128 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4128 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4129 See :hg:`help config` for more information about configuration
4129 See :hg:`help config` for more information about configuration
4130 files and how to use these options.
4130 files and how to use these options.
4131
4131
4132 See :hg:`help dates` for a list of formats valid for -d/--date.
4132 See :hg:`help dates` for a list of formats valid for -d/--date.
4133
4133
4134 .. container:: verbose
4134 .. container:: verbose
4135
4135
4136 Examples:
4136 Examples:
4137
4137
4138 - import a traditional patch from a website and detect renames::
4138 - import a traditional patch from a website and detect renames::
4139
4139
4140 hg import -s 80 http://example.com/bugfix.patch
4140 hg import -s 80 http://example.com/bugfix.patch
4141
4141
4142 - import a changeset from an hgweb server::
4142 - import a changeset from an hgweb server::
4143
4143
4144 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4144 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4145
4145
4146 - import all the patches in an Unix-style mbox::
4146 - import all the patches in an Unix-style mbox::
4147
4147
4148 hg import incoming-patches.mbox
4148 hg import incoming-patches.mbox
4149
4149
4150 - import patches from stdin::
4150 - import patches from stdin::
4151
4151
4152 hg import -
4152 hg import -
4153
4153
4154 - attempt to exactly restore an exported changeset (not always
4154 - attempt to exactly restore an exported changeset (not always
4155 possible)::
4155 possible)::
4156
4156
4157 hg import --exact proposed-fix.patch
4157 hg import --exact proposed-fix.patch
4158
4158
4159 - use an external tool to apply a patch which is too fuzzy for
4159 - use an external tool to apply a patch which is too fuzzy for
4160 the default internal tool.
4160 the default internal tool.
4161
4161
4162 hg import --config ui.patch="patch --merge" fuzzy.patch
4162 hg import --config ui.patch="patch --merge" fuzzy.patch
4163
4163
4164 - change the default fuzzing from 2 to a less strict 7
4164 - change the default fuzzing from 2 to a less strict 7
4165
4165
4166 hg import --config ui.fuzz=7 fuzz.patch
4166 hg import --config ui.fuzz=7 fuzz.patch
4167
4167
4168 Returns 0 on success, 1 on partial success (see --partial).
4168 Returns 0 on success, 1 on partial success (see --partial).
4169 """
4169 """
4170
4170
4171 opts = pycompat.byteskwargs(opts)
4171 opts = pycompat.byteskwargs(opts)
4172 if not patch1:
4172 if not patch1:
4173 raise error.Abort(_(b'need at least one patch to import'))
4173 raise error.Abort(_(b'need at least one patch to import'))
4174
4174
4175 patches = (patch1,) + patches
4175 patches = (patch1,) + patches
4176
4176
4177 date = opts.get(b'date')
4177 date = opts.get(b'date')
4178 if date:
4178 if date:
4179 opts[b'date'] = dateutil.parsedate(date)
4179 opts[b'date'] = dateutil.parsedate(date)
4180
4180
4181 exact = opts.get(b'exact')
4181 exact = opts.get(b'exact')
4182 update = not opts.get(b'bypass')
4182 update = not opts.get(b'bypass')
4183 if not update and opts.get(b'no_commit'):
4183 if not update and opts.get(b'no_commit'):
4184 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4184 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4185 if opts.get(b'secret') and opts.get(b'no_commit'):
4185 if opts.get(b'secret') and opts.get(b'no_commit'):
4186 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4186 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4187 try:
4187 try:
4188 sim = float(opts.get(b'similarity') or 0)
4188 sim = float(opts.get(b'similarity') or 0)
4189 except ValueError:
4189 except ValueError:
4190 raise error.Abort(_(b'similarity must be a number'))
4190 raise error.Abort(_(b'similarity must be a number'))
4191 if sim < 0 or sim > 100:
4191 if sim < 0 or sim > 100:
4192 raise error.Abort(_(b'similarity must be between 0 and 100'))
4192 raise error.Abort(_(b'similarity must be between 0 and 100'))
4193 if sim and not update:
4193 if sim and not update:
4194 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4194 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4195 if exact:
4195 if exact:
4196 if opts.get(b'edit'):
4196 if opts.get(b'edit'):
4197 raise error.Abort(_(b'cannot use --exact with --edit'))
4197 raise error.Abort(_(b'cannot use --exact with --edit'))
4198 if opts.get(b'prefix'):
4198 if opts.get(b'prefix'):
4199 raise error.Abort(_(b'cannot use --exact with --prefix'))
4199 raise error.Abort(_(b'cannot use --exact with --prefix'))
4200
4200
4201 base = opts[b"base"]
4201 base = opts[b"base"]
4202 msgs = []
4202 msgs = []
4203 ret = 0
4203 ret = 0
4204
4204
4205 with repo.wlock():
4205 with repo.wlock():
4206 if update:
4206 if update:
4207 cmdutil.checkunfinished(repo)
4207 cmdutil.checkunfinished(repo)
4208 if exact or not opts.get(b'force'):
4208 if exact or not opts.get(b'force'):
4209 cmdutil.bailifchanged(repo)
4209 cmdutil.bailifchanged(repo)
4210
4210
4211 if not opts.get(b'no_commit'):
4211 if not opts.get(b'no_commit'):
4212 lock = repo.lock
4212 lock = repo.lock
4213 tr = lambda: repo.transaction(b'import')
4213 tr = lambda: repo.transaction(b'import')
4214 dsguard = util.nullcontextmanager
4214 dsguard = util.nullcontextmanager
4215 else:
4215 else:
4216 lock = util.nullcontextmanager
4216 lock = util.nullcontextmanager
4217 tr = util.nullcontextmanager
4217 tr = util.nullcontextmanager
4218 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4218 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4219 with lock(), tr(), dsguard():
4219 with lock(), tr(), dsguard():
4220 parents = repo[None].parents()
4220 parents = repo[None].parents()
4221 for patchurl in patches:
4221 for patchurl in patches:
4222 if patchurl == b'-':
4222 if patchurl == b'-':
4223 ui.status(_(b'applying patch from stdin\n'))
4223 ui.status(_(b'applying patch from stdin\n'))
4224 patchfile = ui.fin
4224 patchfile = ui.fin
4225 patchurl = b'stdin' # for error message
4225 patchurl = b'stdin' # for error message
4226 else:
4226 else:
4227 patchurl = os.path.join(base, patchurl)
4227 patchurl = os.path.join(base, patchurl)
4228 ui.status(_(b'applying %s\n') % patchurl)
4228 ui.status(_(b'applying %s\n') % patchurl)
4229 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4229 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4230
4230
4231 haspatch = False
4231 haspatch = False
4232 for hunk in patch.split(patchfile):
4232 for hunk in patch.split(patchfile):
4233 with patch.extract(ui, hunk) as patchdata:
4233 with patch.extract(ui, hunk) as patchdata:
4234 msg, node, rej = cmdutil.tryimportone(
4234 msg, node, rej = cmdutil.tryimportone(
4235 ui, repo, patchdata, parents, opts, msgs, hg.clean
4235 ui, repo, patchdata, parents, opts, msgs, hg.clean
4236 )
4236 )
4237 if msg:
4237 if msg:
4238 haspatch = True
4238 haspatch = True
4239 ui.note(msg + b'\n')
4239 ui.note(msg + b'\n')
4240 if update or exact:
4240 if update or exact:
4241 parents = repo[None].parents()
4241 parents = repo[None].parents()
4242 else:
4242 else:
4243 parents = [repo[node]]
4243 parents = [repo[node]]
4244 if rej:
4244 if rej:
4245 ui.write_err(_(b"patch applied partially\n"))
4245 ui.write_err(_(b"patch applied partially\n"))
4246 ui.write_err(
4246 ui.write_err(
4247 _(
4247 _(
4248 b"(fix the .rej files and run "
4248 b"(fix the .rej files and run "
4249 b"`hg commit --amend`)\n"
4249 b"`hg commit --amend`)\n"
4250 )
4250 )
4251 )
4251 )
4252 ret = 1
4252 ret = 1
4253 break
4253 break
4254
4254
4255 if not haspatch:
4255 if not haspatch:
4256 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4256 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4257
4257
4258 if msgs:
4258 if msgs:
4259 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4259 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4260 return ret
4260 return ret
4261
4261
4262
4262
4263 @command(
4263 @command(
4264 b'incoming|in',
4264 b'incoming|in',
4265 [
4265 [
4266 (
4266 (
4267 b'f',
4267 b'f',
4268 b'force',
4268 b'force',
4269 None,
4269 None,
4270 _(b'run even if remote repository is unrelated'),
4270 _(b'run even if remote repository is unrelated'),
4271 ),
4271 ),
4272 (b'n', b'newest-first', None, _(b'show newest record first')),
4272 (b'n', b'newest-first', None, _(b'show newest record first')),
4273 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4273 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4274 (
4274 (
4275 b'r',
4275 b'r',
4276 b'rev',
4276 b'rev',
4277 [],
4277 [],
4278 _(b'a remote changeset intended to be added'),
4278 _(b'a remote changeset intended to be added'),
4279 _(b'REV'),
4279 _(b'REV'),
4280 ),
4280 ),
4281 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4281 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4282 (
4282 (
4283 b'b',
4283 b'b',
4284 b'branch',
4284 b'branch',
4285 [],
4285 [],
4286 _(b'a specific branch you would like to pull'),
4286 _(b'a specific branch you would like to pull'),
4287 _(b'BRANCH'),
4287 _(b'BRANCH'),
4288 ),
4288 ),
4289 ]
4289 ]
4290 + logopts
4290 + logopts
4291 + remoteopts
4291 + remoteopts
4292 + subrepoopts,
4292 + subrepoopts,
4293 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4293 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4294 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4294 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4295 )
4295 )
4296 def incoming(ui, repo, source=b"default", **opts):
4296 def incoming(ui, repo, source=b"default", **opts):
4297 """show new changesets found in source
4297 """show new changesets found in source
4298
4298
4299 Show new changesets found in the specified path/URL or the default
4299 Show new changesets found in the specified path/URL or the default
4300 pull location. These are the changesets that would have been pulled
4300 pull location. These are the changesets that would have been pulled
4301 by :hg:`pull` at the time you issued this command.
4301 by :hg:`pull` at the time you issued this command.
4302
4302
4303 See pull for valid source format details.
4303 See pull for valid source format details.
4304
4304
4305 .. container:: verbose
4305 .. container:: verbose
4306
4306
4307 With -B/--bookmarks, the result of bookmark comparison between
4307 With -B/--bookmarks, the result of bookmark comparison between
4308 local and remote repositories is displayed. With -v/--verbose,
4308 local and remote repositories is displayed. With -v/--verbose,
4309 status is also displayed for each bookmark like below::
4309 status is also displayed for each bookmark like below::
4310
4310
4311 BM1 01234567890a added
4311 BM1 01234567890a added
4312 BM2 1234567890ab advanced
4312 BM2 1234567890ab advanced
4313 BM3 234567890abc diverged
4313 BM3 234567890abc diverged
4314 BM4 34567890abcd changed
4314 BM4 34567890abcd changed
4315
4315
4316 The action taken locally when pulling depends on the
4316 The action taken locally when pulling depends on the
4317 status of each bookmark:
4317 status of each bookmark:
4318
4318
4319 :``added``: pull will create it
4319 :``added``: pull will create it
4320 :``advanced``: pull will update it
4320 :``advanced``: pull will update it
4321 :``diverged``: pull will create a divergent bookmark
4321 :``diverged``: pull will create a divergent bookmark
4322 :``changed``: result depends on remote changesets
4322 :``changed``: result depends on remote changesets
4323
4323
4324 From the point of view of pulling behavior, bookmark
4324 From the point of view of pulling behavior, bookmark
4325 existing only in the remote repository are treated as ``added``,
4325 existing only in the remote repository are treated as ``added``,
4326 even if it is in fact locally deleted.
4326 even if it is in fact locally deleted.
4327
4327
4328 .. container:: verbose
4328 .. container:: verbose
4329
4329
4330 For remote repository, using --bundle avoids downloading the
4330 For remote repository, using --bundle avoids downloading the
4331 changesets twice if the incoming is followed by a pull.
4331 changesets twice if the incoming is followed by a pull.
4332
4332
4333 Examples:
4333 Examples:
4334
4334
4335 - show incoming changes with patches and full description::
4335 - show incoming changes with patches and full description::
4336
4336
4337 hg incoming -vp
4337 hg incoming -vp
4338
4338
4339 - show incoming changes excluding merges, store a bundle::
4339 - show incoming changes excluding merges, store a bundle::
4340
4340
4341 hg in -vpM --bundle incoming.hg
4341 hg in -vpM --bundle incoming.hg
4342 hg pull incoming.hg
4342 hg pull incoming.hg
4343
4343
4344 - briefly list changes inside a bundle::
4344 - briefly list changes inside a bundle::
4345
4345
4346 hg in changes.hg -T "{desc|firstline}\\n"
4346 hg in changes.hg -T "{desc|firstline}\\n"
4347
4347
4348 Returns 0 if there are incoming changes, 1 otherwise.
4348 Returns 0 if there are incoming changes, 1 otherwise.
4349 """
4349 """
4350 opts = pycompat.byteskwargs(opts)
4350 opts = pycompat.byteskwargs(opts)
4351 if opts.get(b'graph'):
4351 if opts.get(b'graph'):
4352 logcmdutil.checkunsupportedgraphflags([], opts)
4352 logcmdutil.checkunsupportedgraphflags([], opts)
4353
4353
4354 def display(other, chlist, displayer):
4354 def display(other, chlist, displayer):
4355 revdag = logcmdutil.graphrevs(other, chlist, opts)
4355 revdag = logcmdutil.graphrevs(other, chlist, opts)
4356 logcmdutil.displaygraph(
4356 logcmdutil.displaygraph(
4357 ui, repo, revdag, displayer, graphmod.asciiedges
4357 ui, repo, revdag, displayer, graphmod.asciiedges
4358 )
4358 )
4359
4359
4360 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4360 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4361 return 0
4361 return 0
4362
4362
4363 if opts.get(b'bundle') and opts.get(b'subrepos'):
4363 if opts.get(b'bundle') and opts.get(b'subrepos'):
4364 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4364 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4365
4365
4366 if opts.get(b'bookmarks'):
4366 if opts.get(b'bookmarks'):
4367 source, branches = hg.parseurl(
4367 source, branches = hg.parseurl(
4368 ui.expandpath(source), opts.get(b'branch')
4368 ui.expandpath(source), opts.get(b'branch')
4369 )
4369 )
4370 other = hg.peer(repo, opts, source)
4370 other = hg.peer(repo, opts, source)
4371 if b'bookmarks' not in other.listkeys(b'namespaces'):
4371 if b'bookmarks' not in other.listkeys(b'namespaces'):
4372 ui.warn(_(b"remote doesn't support bookmarks\n"))
4372 ui.warn(_(b"remote doesn't support bookmarks\n"))
4373 return 0
4373 return 0
4374 ui.pager(b'incoming')
4374 ui.pager(b'incoming')
4375 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4375 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4376 return bookmarks.incoming(ui, repo, other)
4376 return bookmarks.incoming(ui, repo, other)
4377
4377
4378 repo._subtoppath = ui.expandpath(source)
4378 repo._subtoppath = ui.expandpath(source)
4379 try:
4379 try:
4380 return hg.incoming(ui, repo, source, opts)
4380 return hg.incoming(ui, repo, source, opts)
4381 finally:
4381 finally:
4382 del repo._subtoppath
4382 del repo._subtoppath
4383
4383
4384
4384
4385 @command(
4385 @command(
4386 b'init',
4386 b'init',
4387 remoteopts,
4387 remoteopts,
4388 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4388 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4389 helpcategory=command.CATEGORY_REPO_CREATION,
4389 helpcategory=command.CATEGORY_REPO_CREATION,
4390 helpbasic=True,
4390 helpbasic=True,
4391 norepo=True,
4391 norepo=True,
4392 )
4392 )
4393 def init(ui, dest=b".", **opts):
4393 def init(ui, dest=b".", **opts):
4394 """create a new repository in the given directory
4394 """create a new repository in the given directory
4395
4395
4396 Initialize a new repository in the given directory. If the given
4396 Initialize a new repository in the given directory. If the given
4397 directory does not exist, it will be created.
4397 directory does not exist, it will be created.
4398
4398
4399 If no directory is given, the current directory is used.
4399 If no directory is given, the current directory is used.
4400
4400
4401 It is possible to specify an ``ssh://`` URL as the destination.
4401 It is possible to specify an ``ssh://`` URL as the destination.
4402 See :hg:`help urls` for more information.
4402 See :hg:`help urls` for more information.
4403
4403
4404 Returns 0 on success.
4404 Returns 0 on success.
4405 """
4405 """
4406 opts = pycompat.byteskwargs(opts)
4406 opts = pycompat.byteskwargs(opts)
4407 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4407 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4408
4408
4409
4409
4410 @command(
4410 @command(
4411 b'locate',
4411 b'locate',
4412 [
4412 [
4413 (
4413 (
4414 b'r',
4414 b'r',
4415 b'rev',
4415 b'rev',
4416 b'',
4416 b'',
4417 _(b'search the repository as it is in REV'),
4417 _(b'search the repository as it is in REV'),
4418 _(b'REV'),
4418 _(b'REV'),
4419 ),
4419 ),
4420 (
4420 (
4421 b'0',
4421 b'0',
4422 b'print0',
4422 b'print0',
4423 None,
4423 None,
4424 _(b'end filenames with NUL, for use with xargs'),
4424 _(b'end filenames with NUL, for use with xargs'),
4425 ),
4425 ),
4426 (
4426 (
4427 b'f',
4427 b'f',
4428 b'fullpath',
4428 b'fullpath',
4429 None,
4429 None,
4430 _(b'print complete paths from the filesystem root'),
4430 _(b'print complete paths from the filesystem root'),
4431 ),
4431 ),
4432 ]
4432 ]
4433 + walkopts,
4433 + walkopts,
4434 _(b'[OPTION]... [PATTERN]...'),
4434 _(b'[OPTION]... [PATTERN]...'),
4435 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4435 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4436 )
4436 )
4437 def locate(ui, repo, *pats, **opts):
4437 def locate(ui, repo, *pats, **opts):
4438 """locate files matching specific patterns (DEPRECATED)
4438 """locate files matching specific patterns (DEPRECATED)
4439
4439
4440 Print files under Mercurial control in the working directory whose
4440 Print files under Mercurial control in the working directory whose
4441 names match the given patterns.
4441 names match the given patterns.
4442
4442
4443 By default, this command searches all directories in the working
4443 By default, this command searches all directories in the working
4444 directory. To search just the current directory and its
4444 directory. To search just the current directory and its
4445 subdirectories, use "--include .".
4445 subdirectories, use "--include .".
4446
4446
4447 If no patterns are given to match, this command prints the names
4447 If no patterns are given to match, this command prints the names
4448 of all files under Mercurial control in the working directory.
4448 of all files under Mercurial control in the working directory.
4449
4449
4450 If you want to feed the output of this command into the "xargs"
4450 If you want to feed the output of this command into the "xargs"
4451 command, use the -0 option to both this command and "xargs". This
4451 command, use the -0 option to both this command and "xargs". This
4452 will avoid the problem of "xargs" treating single filenames that
4452 will avoid the problem of "xargs" treating single filenames that
4453 contain whitespace as multiple filenames.
4453 contain whitespace as multiple filenames.
4454
4454
4455 See :hg:`help files` for a more versatile command.
4455 See :hg:`help files` for a more versatile command.
4456
4456
4457 Returns 0 if a match is found, 1 otherwise.
4457 Returns 0 if a match is found, 1 otherwise.
4458 """
4458 """
4459 opts = pycompat.byteskwargs(opts)
4459 opts = pycompat.byteskwargs(opts)
4460 if opts.get(b'print0'):
4460 if opts.get(b'print0'):
4461 end = b'\0'
4461 end = b'\0'
4462 else:
4462 else:
4463 end = b'\n'
4463 end = b'\n'
4464 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4464 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4465
4465
4466 ret = 1
4466 ret = 1
4467 m = scmutil.match(
4467 m = scmutil.match(
4468 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4468 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4469 )
4469 )
4470
4470
4471 ui.pager(b'locate')
4471 ui.pager(b'locate')
4472 if ctx.rev() is None:
4472 if ctx.rev() is None:
4473 # When run on the working copy, "locate" includes removed files, so
4473 # When run on the working copy, "locate" includes removed files, so
4474 # we get the list of files from the dirstate.
4474 # we get the list of files from the dirstate.
4475 filesgen = sorted(repo.dirstate.matches(m))
4475 filesgen = sorted(repo.dirstate.matches(m))
4476 else:
4476 else:
4477 filesgen = ctx.matches(m)
4477 filesgen = ctx.matches(m)
4478 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4478 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4479 for abs in filesgen:
4479 for abs in filesgen:
4480 if opts.get(b'fullpath'):
4480 if opts.get(b'fullpath'):
4481 ui.write(repo.wjoin(abs), end)
4481 ui.write(repo.wjoin(abs), end)
4482 else:
4482 else:
4483 ui.write(uipathfn(abs), end)
4483 ui.write(uipathfn(abs), end)
4484 ret = 0
4484 ret = 0
4485
4485
4486 return ret
4486 return ret
4487
4487
4488
4488
4489 @command(
4489 @command(
4490 b'log|history',
4490 b'log|history',
4491 [
4491 [
4492 (
4492 (
4493 b'f',
4493 b'f',
4494 b'follow',
4494 b'follow',
4495 None,
4495 None,
4496 _(
4496 _(
4497 b'follow changeset history, or file history across copies and renames'
4497 b'follow changeset history, or file history across copies and renames'
4498 ),
4498 ),
4499 ),
4499 ),
4500 (
4500 (
4501 b'',
4501 b'',
4502 b'follow-first',
4502 b'follow-first',
4503 None,
4503 None,
4504 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4504 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4505 ),
4505 ),
4506 (
4506 (
4507 b'd',
4507 b'd',
4508 b'date',
4508 b'date',
4509 b'',
4509 b'',
4510 _(b'show revisions matching date spec'),
4510 _(b'show revisions matching date spec'),
4511 _(b'DATE'),
4511 _(b'DATE'),
4512 ),
4512 ),
4513 (b'C', b'copies', None, _(b'show copied files')),
4513 (b'C', b'copies', None, _(b'show copied files')),
4514 (
4514 (
4515 b'k',
4515 b'k',
4516 b'keyword',
4516 b'keyword',
4517 [],
4517 [],
4518 _(b'do case-insensitive search for a given text'),
4518 _(b'do case-insensitive search for a given text'),
4519 _(b'TEXT'),
4519 _(b'TEXT'),
4520 ),
4520 ),
4521 (
4521 (
4522 b'r',
4522 b'r',
4523 b'rev',
4523 b'rev',
4524 [],
4524 [],
4525 _(b'show the specified revision or revset'),
4525 _(b'show the specified revision or revset'),
4526 _(b'REV'),
4526 _(b'REV'),
4527 ),
4527 ),
4528 (
4528 (
4529 b'L',
4529 b'L',
4530 b'line-range',
4530 b'line-range',
4531 [],
4531 [],
4532 _(b'follow line range of specified file (EXPERIMENTAL)'),
4532 _(b'follow line range of specified file (EXPERIMENTAL)'),
4533 _(b'FILE,RANGE'),
4533 _(b'FILE,RANGE'),
4534 ),
4534 ),
4535 (
4535 (
4536 b'',
4536 b'',
4537 b'removed',
4537 b'removed',
4538 None,
4538 None,
4539 _(b'include revisions where files were removed'),
4539 _(b'include revisions where files were removed'),
4540 ),
4540 ),
4541 (
4541 (
4542 b'm',
4542 b'm',
4543 b'only-merges',
4543 b'only-merges',
4544 None,
4544 None,
4545 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4545 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4546 ),
4546 ),
4547 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4547 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4548 (
4548 (
4549 b'',
4549 b'',
4550 b'only-branch',
4550 b'only-branch',
4551 [],
4551 [],
4552 _(
4552 _(
4553 b'show only changesets within the given named branch (DEPRECATED)'
4553 b'show only changesets within the given named branch (DEPRECATED)'
4554 ),
4554 ),
4555 _(b'BRANCH'),
4555 _(b'BRANCH'),
4556 ),
4556 ),
4557 (
4557 (
4558 b'b',
4558 b'b',
4559 b'branch',
4559 b'branch',
4560 [],
4560 [],
4561 _(b'show changesets within the given named branch'),
4561 _(b'show changesets within the given named branch'),
4562 _(b'BRANCH'),
4562 _(b'BRANCH'),
4563 ),
4563 ),
4564 (
4564 (
4565 b'P',
4565 b'P',
4566 b'prune',
4566 b'prune',
4567 [],
4567 [],
4568 _(b'do not display revision or any of its ancestors'),
4568 _(b'do not display revision or any of its ancestors'),
4569 _(b'REV'),
4569 _(b'REV'),
4570 ),
4570 ),
4571 ]
4571 ]
4572 + logopts
4572 + logopts
4573 + walkopts,
4573 + walkopts,
4574 _(b'[OPTION]... [FILE]'),
4574 _(b'[OPTION]... [FILE]'),
4575 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4575 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4576 helpbasic=True,
4576 helpbasic=True,
4577 inferrepo=True,
4577 inferrepo=True,
4578 intents={INTENT_READONLY},
4578 intents={INTENT_READONLY},
4579 )
4579 )
4580 def log(ui, repo, *pats, **opts):
4580 def log(ui, repo, *pats, **opts):
4581 """show revision history of entire repository or files
4581 """show revision history of entire repository or files
4582
4582
4583 Print the revision history of the specified files or the entire
4583 Print the revision history of the specified files or the entire
4584 project.
4584 project.
4585
4585
4586 If no revision range is specified, the default is ``tip:0`` unless
4586 If no revision range is specified, the default is ``tip:0`` unless
4587 --follow is set, in which case the working directory parent is
4587 --follow is set, in which case the working directory parent is
4588 used as the starting revision.
4588 used as the starting revision.
4589
4589
4590 File history is shown without following rename or copy history of
4590 File history is shown without following rename or copy history of
4591 files. Use -f/--follow with a filename to follow history across
4591 files. Use -f/--follow with a filename to follow history across
4592 renames and copies. --follow without a filename will only show
4592 renames and copies. --follow without a filename will only show
4593 ancestors of the starting revision.
4593 ancestors of the starting revision.
4594
4594
4595 By default this command prints revision number and changeset id,
4595 By default this command prints revision number and changeset id,
4596 tags, non-trivial parents, user, date and time, and a summary for
4596 tags, non-trivial parents, user, date and time, and a summary for
4597 each commit. When the -v/--verbose switch is used, the list of
4597 each commit. When the -v/--verbose switch is used, the list of
4598 changed files and full commit message are shown.
4598 changed files and full commit message are shown.
4599
4599
4600 With --graph the revisions are shown as an ASCII art DAG with the most
4600 With --graph the revisions are shown as an ASCII art DAG with the most
4601 recent changeset at the top.
4601 recent changeset at the top.
4602 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4602 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4603 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4603 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4604 changeset from the lines below is a parent of the 'o' merge on the same
4604 changeset from the lines below is a parent of the 'o' merge on the same
4605 line.
4605 line.
4606 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4606 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4607 of a '|' indicates one or more revisions in a path are omitted.
4607 of a '|' indicates one or more revisions in a path are omitted.
4608
4608
4609 .. container:: verbose
4609 .. container:: verbose
4610
4610
4611 Use -L/--line-range FILE,M:N options to follow the history of lines
4611 Use -L/--line-range FILE,M:N options to follow the history of lines
4612 from M to N in FILE. With -p/--patch only diff hunks affecting
4612 from M to N in FILE. With -p/--patch only diff hunks affecting
4613 specified line range will be shown. This option requires --follow;
4613 specified line range will be shown. This option requires --follow;
4614 it can be specified multiple times. Currently, this option is not
4614 it can be specified multiple times. Currently, this option is not
4615 compatible with --graph. This option is experimental.
4615 compatible with --graph. This option is experimental.
4616
4616
4617 .. note::
4617 .. note::
4618
4618
4619 :hg:`log --patch` may generate unexpected diff output for merge
4619 :hg:`log --patch` may generate unexpected diff output for merge
4620 changesets, as it will only compare the merge changeset against
4620 changesets, as it will only compare the merge changeset against
4621 its first parent. Also, only files different from BOTH parents
4621 its first parent. Also, only files different from BOTH parents
4622 will appear in files:.
4622 will appear in files:.
4623
4623
4624 .. note::
4624 .. note::
4625
4625
4626 For performance reasons, :hg:`log FILE` may omit duplicate changes
4626 For performance reasons, :hg:`log FILE` may omit duplicate changes
4627 made on branches and will not show removals or mode changes. To
4627 made on branches and will not show removals or mode changes. To
4628 see all such changes, use the --removed switch.
4628 see all such changes, use the --removed switch.
4629
4629
4630 .. container:: verbose
4630 .. container:: verbose
4631
4631
4632 .. note::
4632 .. note::
4633
4633
4634 The history resulting from -L/--line-range options depends on diff
4634 The history resulting from -L/--line-range options depends on diff
4635 options; for instance if white-spaces are ignored, respective changes
4635 options; for instance if white-spaces are ignored, respective changes
4636 with only white-spaces in specified line range will not be listed.
4636 with only white-spaces in specified line range will not be listed.
4637
4637
4638 .. container:: verbose
4638 .. container:: verbose
4639
4639
4640 Some examples:
4640 Some examples:
4641
4641
4642 - changesets with full descriptions and file lists::
4642 - changesets with full descriptions and file lists::
4643
4643
4644 hg log -v
4644 hg log -v
4645
4645
4646 - changesets ancestral to the working directory::
4646 - changesets ancestral to the working directory::
4647
4647
4648 hg log -f
4648 hg log -f
4649
4649
4650 - last 10 commits on the current branch::
4650 - last 10 commits on the current branch::
4651
4651
4652 hg log -l 10 -b .
4652 hg log -l 10 -b .
4653
4653
4654 - changesets showing all modifications of a file, including removals::
4654 - changesets showing all modifications of a file, including removals::
4655
4655
4656 hg log --removed file.c
4656 hg log --removed file.c
4657
4657
4658 - all changesets that touch a directory, with diffs, excluding merges::
4658 - all changesets that touch a directory, with diffs, excluding merges::
4659
4659
4660 hg log -Mp lib/
4660 hg log -Mp lib/
4661
4661
4662 - all revision numbers that match a keyword::
4662 - all revision numbers that match a keyword::
4663
4663
4664 hg log -k bug --template "{rev}\\n"
4664 hg log -k bug --template "{rev}\\n"
4665
4665
4666 - the full hash identifier of the working directory parent::
4666 - the full hash identifier of the working directory parent::
4667
4667
4668 hg log -r . --template "{node}\\n"
4668 hg log -r . --template "{node}\\n"
4669
4669
4670 - list available log templates::
4670 - list available log templates::
4671
4671
4672 hg log -T list
4672 hg log -T list
4673
4673
4674 - check if a given changeset is included in a tagged release::
4674 - check if a given changeset is included in a tagged release::
4675
4675
4676 hg log -r "a21ccf and ancestor(1.9)"
4676 hg log -r "a21ccf and ancestor(1.9)"
4677
4677
4678 - find all changesets by some user in a date range::
4678 - find all changesets by some user in a date range::
4679
4679
4680 hg log -k alice -d "may 2008 to jul 2008"
4680 hg log -k alice -d "may 2008 to jul 2008"
4681
4681
4682 - summary of all changesets after the last tag::
4682 - summary of all changesets after the last tag::
4683
4683
4684 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4684 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4685
4685
4686 - changesets touching lines 13 to 23 for file.c::
4686 - changesets touching lines 13 to 23 for file.c::
4687
4687
4688 hg log -L file.c,13:23
4688 hg log -L file.c,13:23
4689
4689
4690 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4690 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4691 main.c with patch::
4691 main.c with patch::
4692
4692
4693 hg log -L file.c,13:23 -L main.c,2:6 -p
4693 hg log -L file.c,13:23 -L main.c,2:6 -p
4694
4694
4695 See :hg:`help dates` for a list of formats valid for -d/--date.
4695 See :hg:`help dates` for a list of formats valid for -d/--date.
4696
4696
4697 See :hg:`help revisions` for more about specifying and ordering
4697 See :hg:`help revisions` for more about specifying and ordering
4698 revisions.
4698 revisions.
4699
4699
4700 See :hg:`help templates` for more about pre-packaged styles and
4700 See :hg:`help templates` for more about pre-packaged styles and
4701 specifying custom templates. The default template used by the log
4701 specifying custom templates. The default template used by the log
4702 command can be customized via the ``ui.logtemplate`` configuration
4702 command can be customized via the ``ui.logtemplate`` configuration
4703 setting.
4703 setting.
4704
4704
4705 Returns 0 on success.
4705 Returns 0 on success.
4706
4706
4707 """
4707 """
4708 opts = pycompat.byteskwargs(opts)
4708 opts = pycompat.byteskwargs(opts)
4709 linerange = opts.get(b'line_range')
4709 linerange = opts.get(b'line_range')
4710
4710
4711 if linerange and not opts.get(b'follow'):
4711 if linerange and not opts.get(b'follow'):
4712 raise error.Abort(_(b'--line-range requires --follow'))
4712 raise error.Abort(_(b'--line-range requires --follow'))
4713
4713
4714 if linerange and pats:
4714 if linerange and pats:
4715 # TODO: take pats as patterns with no line-range filter
4715 # TODO: take pats as patterns with no line-range filter
4716 raise error.Abort(
4716 raise error.Abort(
4717 _(b'FILE arguments are not compatible with --line-range option')
4717 _(b'FILE arguments are not compatible with --line-range option')
4718 )
4718 )
4719
4719
4720 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4720 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4721 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4721 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4722 if linerange:
4722 if linerange:
4723 # TODO: should follow file history from logcmdutil._initialrevs(),
4723 # TODO: should follow file history from logcmdutil._initialrevs(),
4724 # then filter the result by logcmdutil._makerevset() and --limit
4724 # then filter the result by logcmdutil._makerevset() and --limit
4725 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4725 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4726
4726
4727 getcopies = None
4727 getcopies = None
4728 if opts.get(b'copies'):
4728 if opts.get(b'copies'):
4729 endrev = None
4729 endrev = None
4730 if revs:
4730 if revs:
4731 endrev = revs.max() + 1
4731 endrev = revs.max() + 1
4732 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4732 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4733
4733
4734 ui.pager(b'log')
4734 ui.pager(b'log')
4735 displayer = logcmdutil.changesetdisplayer(
4735 displayer = logcmdutil.changesetdisplayer(
4736 ui, repo, opts, differ, buffered=True
4736 ui, repo, opts, differ, buffered=True
4737 )
4737 )
4738 if opts.get(b'graph'):
4738 if opts.get(b'graph'):
4739 displayfn = logcmdutil.displaygraphrevs
4739 displayfn = logcmdutil.displaygraphrevs
4740 else:
4740 else:
4741 displayfn = logcmdutil.displayrevs
4741 displayfn = logcmdutil.displayrevs
4742 displayfn(ui, repo, revs, displayer, getcopies)
4742 displayfn(ui, repo, revs, displayer, getcopies)
4743
4743
4744
4744
4745 @command(
4745 @command(
4746 b'manifest',
4746 b'manifest',
4747 [
4747 [
4748 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4748 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4749 (b'', b'all', False, _(b"list files from all revisions")),
4749 (b'', b'all', False, _(b"list files from all revisions")),
4750 ]
4750 ]
4751 + formatteropts,
4751 + formatteropts,
4752 _(b'[-r REV]'),
4752 _(b'[-r REV]'),
4753 helpcategory=command.CATEGORY_MAINTENANCE,
4753 helpcategory=command.CATEGORY_MAINTENANCE,
4754 intents={INTENT_READONLY},
4754 intents={INTENT_READONLY},
4755 )
4755 )
4756 def manifest(ui, repo, node=None, rev=None, **opts):
4756 def manifest(ui, repo, node=None, rev=None, **opts):
4757 """output the current or given revision of the project manifest
4757 """output the current or given revision of the project manifest
4758
4758
4759 Print a list of version controlled files for the given revision.
4759 Print a list of version controlled files for the given revision.
4760 If no revision is given, the first parent of the working directory
4760 If no revision is given, the first parent of the working directory
4761 is used, or the null revision if no revision is checked out.
4761 is used, or the null revision if no revision is checked out.
4762
4762
4763 With -v, print file permissions, symlink and executable bits.
4763 With -v, print file permissions, symlink and executable bits.
4764 With --debug, print file revision hashes.
4764 With --debug, print file revision hashes.
4765
4765
4766 If option --all is specified, the list of all files from all revisions
4766 If option --all is specified, the list of all files from all revisions
4767 is printed. This includes deleted and renamed files.
4767 is printed. This includes deleted and renamed files.
4768
4768
4769 Returns 0 on success.
4769 Returns 0 on success.
4770 """
4770 """
4771 opts = pycompat.byteskwargs(opts)
4771 opts = pycompat.byteskwargs(opts)
4772 fm = ui.formatter(b'manifest', opts)
4772 fm = ui.formatter(b'manifest', opts)
4773
4773
4774 if opts.get(b'all'):
4774 if opts.get(b'all'):
4775 if rev or node:
4775 if rev or node:
4776 raise error.Abort(_(b"can't specify a revision with --all"))
4776 raise error.Abort(_(b"can't specify a revision with --all"))
4777
4777
4778 res = set()
4778 res = set()
4779 for rev in repo:
4779 for rev in repo:
4780 ctx = repo[rev]
4780 ctx = repo[rev]
4781 res |= set(ctx.files())
4781 res |= set(ctx.files())
4782
4782
4783 ui.pager(b'manifest')
4783 ui.pager(b'manifest')
4784 for f in sorted(res):
4784 for f in sorted(res):
4785 fm.startitem()
4785 fm.startitem()
4786 fm.write(b"path", b'%s\n', f)
4786 fm.write(b"path", b'%s\n', f)
4787 fm.end()
4787 fm.end()
4788 return
4788 return
4789
4789
4790 if rev and node:
4790 if rev and node:
4791 raise error.Abort(_(b"please specify just one revision"))
4791 raise error.Abort(_(b"please specify just one revision"))
4792
4792
4793 if not node:
4793 if not node:
4794 node = rev
4794 node = rev
4795
4795
4796 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4796 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4797 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4797 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4798 if node:
4798 if node:
4799 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4799 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4800 ctx = scmutil.revsingle(repo, node)
4800 ctx = scmutil.revsingle(repo, node)
4801 mf = ctx.manifest()
4801 mf = ctx.manifest()
4802 ui.pager(b'manifest')
4802 ui.pager(b'manifest')
4803 for f in ctx:
4803 for f in ctx:
4804 fm.startitem()
4804 fm.startitem()
4805 fm.context(ctx=ctx)
4805 fm.context(ctx=ctx)
4806 fl = ctx[f].flags()
4806 fl = ctx[f].flags()
4807 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4807 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4808 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4808 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4809 fm.write(b'path', b'%s\n', f)
4809 fm.write(b'path', b'%s\n', f)
4810 fm.end()
4810 fm.end()
4811
4811
4812
4812
4813 @command(
4813 @command(
4814 b'merge',
4814 b'merge',
4815 [
4815 [
4816 (
4816 (
4817 b'f',
4817 b'f',
4818 b'force',
4818 b'force',
4819 None,
4819 None,
4820 _(b'force a merge including outstanding changes (DEPRECATED)'),
4820 _(b'force a merge including outstanding changes (DEPRECATED)'),
4821 ),
4821 ),
4822 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4822 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4823 (
4823 (
4824 b'P',
4824 b'P',
4825 b'preview',
4825 b'preview',
4826 None,
4826 None,
4827 _(b'review revisions to merge (no merge is performed)'),
4827 _(b'review revisions to merge (no merge is performed)'),
4828 ),
4828 ),
4829 (b'', b'abort', None, _(b'abort the ongoing merge')),
4829 (b'', b'abort', None, _(b'abort the ongoing merge')),
4830 ]
4830 ]
4831 + mergetoolopts,
4831 + mergetoolopts,
4832 _(b'[-P] [[-r] REV]'),
4832 _(b'[-P] [[-r] REV]'),
4833 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4833 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4834 helpbasic=True,
4834 helpbasic=True,
4835 )
4835 )
4836 def merge(ui, repo, node=None, **opts):
4836 def merge(ui, repo, node=None, **opts):
4837 """merge another revision into working directory
4837 """merge another revision into working directory
4838
4838
4839 The current working directory is updated with all changes made in
4839 The current working directory is updated with all changes made in
4840 the requested revision since the last common predecessor revision.
4840 the requested revision since the last common predecessor revision.
4841
4841
4842 Files that changed between either parent are marked as changed for
4842 Files that changed between either parent are marked as changed for
4843 the next commit and a commit must be performed before any further
4843 the next commit and a commit must be performed before any further
4844 updates to the repository are allowed. The next commit will have
4844 updates to the repository are allowed. The next commit will have
4845 two parents.
4845 two parents.
4846
4846
4847 ``--tool`` can be used to specify the merge tool used for file
4847 ``--tool`` can be used to specify the merge tool used for file
4848 merges. It overrides the HGMERGE environment variable and your
4848 merges. It overrides the HGMERGE environment variable and your
4849 configuration files. See :hg:`help merge-tools` for options.
4849 configuration files. See :hg:`help merge-tools` for options.
4850
4850
4851 If no revision is specified, the working directory's parent is a
4851 If no revision is specified, the working directory's parent is a
4852 head revision, and the current branch contains exactly one other
4852 head revision, and the current branch contains exactly one other
4853 head, the other head is merged with by default. Otherwise, an
4853 head, the other head is merged with by default. Otherwise, an
4854 explicit revision with which to merge must be provided.
4854 explicit revision with which to merge must be provided.
4855
4855
4856 See :hg:`help resolve` for information on handling file conflicts.
4856 See :hg:`help resolve` for information on handling file conflicts.
4857
4857
4858 To undo an uncommitted merge, use :hg:`merge --abort` which
4858 To undo an uncommitted merge, use :hg:`merge --abort` which
4859 will check out a clean copy of the original merge parent, losing
4859 will check out a clean copy of the original merge parent, losing
4860 all changes.
4860 all changes.
4861
4861
4862 Returns 0 on success, 1 if there are unresolved files.
4862 Returns 0 on success, 1 if there are unresolved files.
4863 """
4863 """
4864
4864
4865 opts = pycompat.byteskwargs(opts)
4865 opts = pycompat.byteskwargs(opts)
4866 abort = opts.get(b'abort')
4866 abort = opts.get(b'abort')
4867 if abort and repo.dirstate.p2() == nullid:
4867 if abort and repo.dirstate.p2() == nullid:
4868 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4868 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4869 if abort:
4869 if abort:
4870 state = cmdutil.getunfinishedstate(repo)
4870 state = cmdutil.getunfinishedstate(repo)
4871 if state and state._opname != b'merge':
4871 if state and state._opname != b'merge':
4872 raise error.Abort(
4872 raise error.Abort(
4873 _(b'cannot abort merge with %s in progress') % (state._opname),
4873 _(b'cannot abort merge with %s in progress') % (state._opname),
4874 hint=state.hint(),
4874 hint=state.hint(),
4875 )
4875 )
4876 if node:
4876 if node:
4877 raise error.Abort(_(b"cannot specify a node with --abort"))
4877 raise error.Abort(_(b"cannot specify a node with --abort"))
4878 if opts.get(b'rev'):
4878 if opts.get(b'rev'):
4879 raise error.Abort(_(b"cannot specify both --rev and --abort"))
4879 raise error.Abort(_(b"cannot specify both --rev and --abort"))
4880 if opts.get(b'preview'):
4880 if opts.get(b'preview'):
4881 raise error.Abort(_(b"cannot specify --preview with --abort"))
4881 raise error.Abort(_(b"cannot specify --preview with --abort"))
4882 if opts.get(b'rev') and node:
4882 if opts.get(b'rev') and node:
4883 raise error.Abort(_(b"please specify just one revision"))
4883 raise error.Abort(_(b"please specify just one revision"))
4884 if not node:
4884 if not node:
4885 node = opts.get(b'rev')
4885 node = opts.get(b'rev')
4886
4886
4887 if node:
4887 if node:
4888 node = scmutil.revsingle(repo, node).node()
4888 node = scmutil.revsingle(repo, node).node()
4889
4889
4890 if not node and not abort:
4890 if not node and not abort:
4891 node = repo[destutil.destmerge(repo)].node()
4891 node = repo[destutil.destmerge(repo)].node()
4892
4892
4893 if opts.get(b'preview'):
4893 if opts.get(b'preview'):
4894 # find nodes that are ancestors of p2 but not of p1
4894 # find nodes that are ancestors of p2 but not of p1
4895 p1 = repo.lookup(b'.')
4895 p1 = repo.lookup(b'.')
4896 p2 = node
4896 p2 = node
4897 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4897 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4898
4898
4899 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4899 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4900 for node in nodes:
4900 for node in nodes:
4901 displayer.show(repo[node])
4901 displayer.show(repo[node])
4902 displayer.close()
4902 displayer.close()
4903 return 0
4903 return 0
4904
4904
4905 # ui.forcemerge is an internal variable, do not document
4905 # ui.forcemerge is an internal variable, do not document
4906 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4906 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4907 with ui.configoverride(overrides, b'merge'):
4907 with ui.configoverride(overrides, b'merge'):
4908 force = opts.get(b'force')
4908 force = opts.get(b'force')
4909 labels = [b'working copy', b'merge rev']
4909 labels = [b'working copy', b'merge rev']
4910 return hg.merge(
4910 return hg.merge(
4911 repo,
4911 repo,
4912 node,
4912 node,
4913 force=force,
4913 force=force,
4914 mergeforce=force,
4914 mergeforce=force,
4915 labels=labels,
4915 labels=labels,
4916 abort=abort,
4916 abort=abort,
4917 )
4917 )
4918
4918
4919
4919
4920 statemod.addunfinished(
4920 statemod.addunfinished(
4921 b'merge',
4921 b'merge',
4922 fname=None,
4922 fname=None,
4923 clearable=True,
4923 clearable=True,
4924 allowcommit=True,
4924 allowcommit=True,
4925 cmdmsg=_(b'outstanding uncommitted merge'),
4925 cmdmsg=_(b'outstanding uncommitted merge'),
4926 abortfunc=hg.abortmerge,
4926 abortfunc=hg.abortmerge,
4927 statushint=_(
4927 statushint=_(
4928 b'To continue: hg commit\nTo abort: hg merge --abort'
4928 b'To continue: hg commit\nTo abort: hg merge --abort'
4929 ),
4929 ),
4930 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4930 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4931 )
4931 )
4932
4932
4933
4933
4934 @command(
4934 @command(
4935 b'outgoing|out',
4935 b'outgoing|out',
4936 [
4936 [
4937 (
4937 (
4938 b'f',
4938 b'f',
4939 b'force',
4939 b'force',
4940 None,
4940 None,
4941 _(b'run even when the destination is unrelated'),
4941 _(b'run even when the destination is unrelated'),
4942 ),
4942 ),
4943 (
4943 (
4944 b'r',
4944 b'r',
4945 b'rev',
4945 b'rev',
4946 [],
4946 [],
4947 _(b'a changeset intended to be included in the destination'),
4947 _(b'a changeset intended to be included in the destination'),
4948 _(b'REV'),
4948 _(b'REV'),
4949 ),
4949 ),
4950 (b'n', b'newest-first', None, _(b'show newest record first')),
4950 (b'n', b'newest-first', None, _(b'show newest record first')),
4951 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4951 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4952 (
4952 (
4953 b'b',
4953 b'b',
4954 b'branch',
4954 b'branch',
4955 [],
4955 [],
4956 _(b'a specific branch you would like to push'),
4956 _(b'a specific branch you would like to push'),
4957 _(b'BRANCH'),
4957 _(b'BRANCH'),
4958 ),
4958 ),
4959 ]
4959 ]
4960 + logopts
4960 + logopts
4961 + remoteopts
4961 + remoteopts
4962 + subrepoopts,
4962 + subrepoopts,
4963 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4963 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4964 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4964 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4965 )
4965 )
4966 def outgoing(ui, repo, dest=None, **opts):
4966 def outgoing(ui, repo, dest=None, **opts):
4967 """show changesets not found in the destination
4967 """show changesets not found in the destination
4968
4968
4969 Show changesets not found in the specified destination repository
4969 Show changesets not found in the specified destination repository
4970 or the default push location. These are the changesets that would
4970 or the default push location. These are the changesets that would
4971 be pushed if a push was requested.
4971 be pushed if a push was requested.
4972
4972
4973 See pull for details of valid destination formats.
4973 See pull for details of valid destination formats.
4974
4974
4975 .. container:: verbose
4975 .. container:: verbose
4976
4976
4977 With -B/--bookmarks, the result of bookmark comparison between
4977 With -B/--bookmarks, the result of bookmark comparison between
4978 local and remote repositories is displayed. With -v/--verbose,
4978 local and remote repositories is displayed. With -v/--verbose,
4979 status is also displayed for each bookmark like below::
4979 status is also displayed for each bookmark like below::
4980
4980
4981 BM1 01234567890a added
4981 BM1 01234567890a added
4982 BM2 deleted
4982 BM2 deleted
4983 BM3 234567890abc advanced
4983 BM3 234567890abc advanced
4984 BM4 34567890abcd diverged
4984 BM4 34567890abcd diverged
4985 BM5 4567890abcde changed
4985 BM5 4567890abcde changed
4986
4986
4987 The action taken when pushing depends on the
4987 The action taken when pushing depends on the
4988 status of each bookmark:
4988 status of each bookmark:
4989
4989
4990 :``added``: push with ``-B`` will create it
4990 :``added``: push with ``-B`` will create it
4991 :``deleted``: push with ``-B`` will delete it
4991 :``deleted``: push with ``-B`` will delete it
4992 :``advanced``: push will update it
4992 :``advanced``: push will update it
4993 :``diverged``: push with ``-B`` will update it
4993 :``diverged``: push with ``-B`` will update it
4994 :``changed``: push with ``-B`` will update it
4994 :``changed``: push with ``-B`` will update it
4995
4995
4996 From the point of view of pushing behavior, bookmarks
4996 From the point of view of pushing behavior, bookmarks
4997 existing only in the remote repository are treated as
4997 existing only in the remote repository are treated as
4998 ``deleted``, even if it is in fact added remotely.
4998 ``deleted``, even if it is in fact added remotely.
4999
4999
5000 Returns 0 if there are outgoing changes, 1 otherwise.
5000 Returns 0 if there are outgoing changes, 1 otherwise.
5001 """
5001 """
5002 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5002 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5003 # style URLs, so don't overwrite dest.
5003 # style URLs, so don't overwrite dest.
5004 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5004 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5005 if not path:
5005 if not path:
5006 raise error.Abort(
5006 raise error.Abort(
5007 _(b'default repository not configured!'),
5007 _(b'default repository not configured!'),
5008 hint=_(b"see 'hg help config.paths'"),
5008 hint=_(b"see 'hg help config.paths'"),
5009 )
5009 )
5010
5010
5011 opts = pycompat.byteskwargs(opts)
5011 opts = pycompat.byteskwargs(opts)
5012 if opts.get(b'graph'):
5012 if opts.get(b'graph'):
5013 logcmdutil.checkunsupportedgraphflags([], opts)
5013 logcmdutil.checkunsupportedgraphflags([], opts)
5014 o, other = hg._outgoing(ui, repo, dest, opts)
5014 o, other = hg._outgoing(ui, repo, dest, opts)
5015 if not o:
5015 if not o:
5016 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5016 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5017 return
5017 return
5018
5018
5019 revdag = logcmdutil.graphrevs(repo, o, opts)
5019 revdag = logcmdutil.graphrevs(repo, o, opts)
5020 ui.pager(b'outgoing')
5020 ui.pager(b'outgoing')
5021 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5021 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5022 logcmdutil.displaygraph(
5022 logcmdutil.displaygraph(
5023 ui, repo, revdag, displayer, graphmod.asciiedges
5023 ui, repo, revdag, displayer, graphmod.asciiedges
5024 )
5024 )
5025 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5025 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5026 return 0
5026 return 0
5027
5027
5028 if opts.get(b'bookmarks'):
5028 if opts.get(b'bookmarks'):
5029 dest = path.pushloc or path.loc
5029 dest = path.pushloc or path.loc
5030 other = hg.peer(repo, opts, dest)
5030 other = hg.peer(repo, opts, dest)
5031 if b'bookmarks' not in other.listkeys(b'namespaces'):
5031 if b'bookmarks' not in other.listkeys(b'namespaces'):
5032 ui.warn(_(b"remote doesn't support bookmarks\n"))
5032 ui.warn(_(b"remote doesn't support bookmarks\n"))
5033 return 0
5033 return 0
5034 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5034 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5035 ui.pager(b'outgoing')
5035 ui.pager(b'outgoing')
5036 return bookmarks.outgoing(ui, repo, other)
5036 return bookmarks.outgoing(ui, repo, other)
5037
5037
5038 repo._subtoppath = path.pushloc or path.loc
5038 repo._subtoppath = path.pushloc or path.loc
5039 try:
5039 try:
5040 return hg.outgoing(ui, repo, dest, opts)
5040 return hg.outgoing(ui, repo, dest, opts)
5041 finally:
5041 finally:
5042 del repo._subtoppath
5042 del repo._subtoppath
5043
5043
5044
5044
5045 @command(
5045 @command(
5046 b'parents',
5046 b'parents',
5047 [
5047 [
5048 (
5048 (
5049 b'r',
5049 b'r',
5050 b'rev',
5050 b'rev',
5051 b'',
5051 b'',
5052 _(b'show parents of the specified revision'),
5052 _(b'show parents of the specified revision'),
5053 _(b'REV'),
5053 _(b'REV'),
5054 ),
5054 ),
5055 ]
5055 ]
5056 + templateopts,
5056 + templateopts,
5057 _(b'[-r REV] [FILE]'),
5057 _(b'[-r REV] [FILE]'),
5058 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5058 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5059 inferrepo=True,
5059 inferrepo=True,
5060 )
5060 )
5061 def parents(ui, repo, file_=None, **opts):
5061 def parents(ui, repo, file_=None, **opts):
5062 """show the parents of the working directory or revision (DEPRECATED)
5062 """show the parents of the working directory or revision (DEPRECATED)
5063
5063
5064 Print the working directory's parent revisions. If a revision is
5064 Print the working directory's parent revisions. If a revision is
5065 given via -r/--rev, the parent of that revision will be printed.
5065 given via -r/--rev, the parent of that revision will be printed.
5066 If a file argument is given, the revision in which the file was
5066 If a file argument is given, the revision in which the file was
5067 last changed (before the working directory revision or the
5067 last changed (before the working directory revision or the
5068 argument to --rev if given) is printed.
5068 argument to --rev if given) is printed.
5069
5069
5070 This command is equivalent to::
5070 This command is equivalent to::
5071
5071
5072 hg log -r "p1()+p2()" or
5072 hg log -r "p1()+p2()" or
5073 hg log -r "p1(REV)+p2(REV)" or
5073 hg log -r "p1(REV)+p2(REV)" or
5074 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5074 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5075 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5075 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5076
5076
5077 See :hg:`summary` and :hg:`help revsets` for related information.
5077 See :hg:`summary` and :hg:`help revsets` for related information.
5078
5078
5079 Returns 0 on success.
5079 Returns 0 on success.
5080 """
5080 """
5081
5081
5082 opts = pycompat.byteskwargs(opts)
5082 opts = pycompat.byteskwargs(opts)
5083 rev = opts.get(b'rev')
5083 rev = opts.get(b'rev')
5084 if rev:
5084 if rev:
5085 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5085 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5086 ctx = scmutil.revsingle(repo, rev, None)
5086 ctx = scmutil.revsingle(repo, rev, None)
5087
5087
5088 if file_:
5088 if file_:
5089 m = scmutil.match(ctx, (file_,), opts)
5089 m = scmutil.match(ctx, (file_,), opts)
5090 if m.anypats() or len(m.files()) != 1:
5090 if m.anypats() or len(m.files()) != 1:
5091 raise error.Abort(_(b'can only specify an explicit filename'))
5091 raise error.Abort(_(b'can only specify an explicit filename'))
5092 file_ = m.files()[0]
5092 file_ = m.files()[0]
5093 filenodes = []
5093 filenodes = []
5094 for cp in ctx.parents():
5094 for cp in ctx.parents():
5095 if not cp:
5095 if not cp:
5096 continue
5096 continue
5097 try:
5097 try:
5098 filenodes.append(cp.filenode(file_))
5098 filenodes.append(cp.filenode(file_))
5099 except error.LookupError:
5099 except error.LookupError:
5100 pass
5100 pass
5101 if not filenodes:
5101 if not filenodes:
5102 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5102 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5103 p = []
5103 p = []
5104 for fn in filenodes:
5104 for fn in filenodes:
5105 fctx = repo.filectx(file_, fileid=fn)
5105 fctx = repo.filectx(file_, fileid=fn)
5106 p.append(fctx.node())
5106 p.append(fctx.node())
5107 else:
5107 else:
5108 p = [cp.node() for cp in ctx.parents()]
5108 p = [cp.node() for cp in ctx.parents()]
5109
5109
5110 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5110 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5111 for n in p:
5111 for n in p:
5112 if n != nullid:
5112 if n != nullid:
5113 displayer.show(repo[n])
5113 displayer.show(repo[n])
5114 displayer.close()
5114 displayer.close()
5115
5115
5116
5116
5117 @command(
5117 @command(
5118 b'paths',
5118 b'paths',
5119 formatteropts,
5119 formatteropts,
5120 _(b'[NAME]'),
5120 _(b'[NAME]'),
5121 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5121 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5122 optionalrepo=True,
5122 optionalrepo=True,
5123 intents={INTENT_READONLY},
5123 intents={INTENT_READONLY},
5124 )
5124 )
5125 def paths(ui, repo, search=None, **opts):
5125 def paths(ui, repo, search=None, **opts):
5126 """show aliases for remote repositories
5126 """show aliases for remote repositories
5127
5127
5128 Show definition of symbolic path name NAME. If no name is given,
5128 Show definition of symbolic path name NAME. If no name is given,
5129 show definition of all available names.
5129 show definition of all available names.
5130
5130
5131 Option -q/--quiet suppresses all output when searching for NAME
5131 Option -q/--quiet suppresses all output when searching for NAME
5132 and shows only the path names when listing all definitions.
5132 and shows only the path names when listing all definitions.
5133
5133
5134 Path names are defined in the [paths] section of your
5134 Path names are defined in the [paths] section of your
5135 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5135 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5136 repository, ``.hg/hgrc`` is used, too.
5136 repository, ``.hg/hgrc`` is used, too.
5137
5137
5138 The path names ``default`` and ``default-push`` have a special
5138 The path names ``default`` and ``default-push`` have a special
5139 meaning. When performing a push or pull operation, they are used
5139 meaning. When performing a push or pull operation, they are used
5140 as fallbacks if no location is specified on the command-line.
5140 as fallbacks if no location is specified on the command-line.
5141 When ``default-push`` is set, it will be used for push and
5141 When ``default-push`` is set, it will be used for push and
5142 ``default`` will be used for pull; otherwise ``default`` is used
5142 ``default`` will be used for pull; otherwise ``default`` is used
5143 as the fallback for both. When cloning a repository, the clone
5143 as the fallback for both. When cloning a repository, the clone
5144 source is written as ``default`` in ``.hg/hgrc``.
5144 source is written as ``default`` in ``.hg/hgrc``.
5145
5145
5146 .. note::
5146 .. note::
5147
5147
5148 ``default`` and ``default-push`` apply to all inbound (e.g.
5148 ``default`` and ``default-push`` apply to all inbound (e.g.
5149 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5149 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5150 and :hg:`bundle`) operations.
5150 and :hg:`bundle`) operations.
5151
5151
5152 See :hg:`help urls` for more information.
5152 See :hg:`help urls` for more information.
5153
5153
5154 .. container:: verbose
5154 .. container:: verbose
5155
5155
5156 Template:
5156 Template:
5157
5157
5158 The following keywords are supported. See also :hg:`help templates`.
5158 The following keywords are supported. See also :hg:`help templates`.
5159
5159
5160 :name: String. Symbolic name of the path alias.
5160 :name: String. Symbolic name of the path alias.
5161 :pushurl: String. URL for push operations.
5161 :pushurl: String. URL for push operations.
5162 :url: String. URL or directory path for the other operations.
5162 :url: String. URL or directory path for the other operations.
5163
5163
5164 Returns 0 on success.
5164 Returns 0 on success.
5165 """
5165 """
5166
5166
5167 opts = pycompat.byteskwargs(opts)
5167 opts = pycompat.byteskwargs(opts)
5168 ui.pager(b'paths')
5168 ui.pager(b'paths')
5169 if search:
5169 if search:
5170 pathitems = [
5170 pathitems = [
5171 (name, path)
5171 (name, path)
5172 for name, path in pycompat.iteritems(ui.paths)
5172 for name, path in pycompat.iteritems(ui.paths)
5173 if name == search
5173 if name == search
5174 ]
5174 ]
5175 else:
5175 else:
5176 pathitems = sorted(pycompat.iteritems(ui.paths))
5176 pathitems = sorted(pycompat.iteritems(ui.paths))
5177
5177
5178 fm = ui.formatter(b'paths', opts)
5178 fm = ui.formatter(b'paths', opts)
5179 if fm.isplain():
5179 if fm.isplain():
5180 hidepassword = util.hidepassword
5180 hidepassword = util.hidepassword
5181 else:
5181 else:
5182 hidepassword = bytes
5182 hidepassword = bytes
5183 if ui.quiet:
5183 if ui.quiet:
5184 namefmt = b'%s\n'
5184 namefmt = b'%s\n'
5185 else:
5185 else:
5186 namefmt = b'%s = '
5186 namefmt = b'%s = '
5187 showsubopts = not search and not ui.quiet
5187 showsubopts = not search and not ui.quiet
5188
5188
5189 for name, path in pathitems:
5189 for name, path in pathitems:
5190 fm.startitem()
5190 fm.startitem()
5191 fm.condwrite(not search, b'name', namefmt, name)
5191 fm.condwrite(not search, b'name', namefmt, name)
5192 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5192 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5193 for subopt, value in sorted(path.suboptions.items()):
5193 for subopt, value in sorted(path.suboptions.items()):
5194 assert subopt not in (b'name', b'url')
5194 assert subopt not in (b'name', b'url')
5195 if showsubopts:
5195 if showsubopts:
5196 fm.plain(b'%s:%s = ' % (name, subopt))
5196 fm.plain(b'%s:%s = ' % (name, subopt))
5197 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5197 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5198
5198
5199 fm.end()
5199 fm.end()
5200
5200
5201 if search and not pathitems:
5201 if search and not pathitems:
5202 if not ui.quiet:
5202 if not ui.quiet:
5203 ui.warn(_(b"not found!\n"))
5203 ui.warn(_(b"not found!\n"))
5204 return 1
5204 return 1
5205 else:
5205 else:
5206 return 0
5206 return 0
5207
5207
5208
5208
5209 @command(
5209 @command(
5210 b'phase',
5210 b'phase',
5211 [
5211 [
5212 (b'p', b'public', False, _(b'set changeset phase to public')),
5212 (b'p', b'public', False, _(b'set changeset phase to public')),
5213 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5213 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5214 (b's', b'secret', False, _(b'set changeset phase to secret')),
5214 (b's', b'secret', False, _(b'set changeset phase to secret')),
5215 (b'f', b'force', False, _(b'allow to move boundary backward')),
5215 (b'f', b'force', False, _(b'allow to move boundary backward')),
5216 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5216 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5217 ],
5217 ],
5218 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5218 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5219 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5219 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5220 )
5220 )
5221 def phase(ui, repo, *revs, **opts):
5221 def phase(ui, repo, *revs, **opts):
5222 """set or show the current phase name
5222 """set or show the current phase name
5223
5223
5224 With no argument, show the phase name of the current revision(s).
5224 With no argument, show the phase name of the current revision(s).
5225
5225
5226 With one of -p/--public, -d/--draft or -s/--secret, change the
5226 With one of -p/--public, -d/--draft or -s/--secret, change the
5227 phase value of the specified revisions.
5227 phase value of the specified revisions.
5228
5228
5229 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5229 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5230 lower phase to a higher phase. Phases are ordered as follows::
5230 lower phase to a higher phase. Phases are ordered as follows::
5231
5231
5232 public < draft < secret
5232 public < draft < secret
5233
5233
5234 Returns 0 on success, 1 if some phases could not be changed.
5234 Returns 0 on success, 1 if some phases could not be changed.
5235
5235
5236 (For more information about the phases concept, see :hg:`help phases`.)
5236 (For more information about the phases concept, see :hg:`help phases`.)
5237 """
5237 """
5238 opts = pycompat.byteskwargs(opts)
5238 opts = pycompat.byteskwargs(opts)
5239 # search for a unique phase argument
5239 # search for a unique phase argument
5240 targetphase = None
5240 targetphase = None
5241 for idx, name in enumerate(phases.cmdphasenames):
5241 for idx, name in enumerate(phases.cmdphasenames):
5242 if opts[name]:
5242 if opts[name]:
5243 if targetphase is not None:
5243 if targetphase is not None:
5244 raise error.Abort(_(b'only one phase can be specified'))
5244 raise error.Abort(_(b'only one phase can be specified'))
5245 targetphase = idx
5245 targetphase = idx
5246
5246
5247 # look for specified revision
5247 # look for specified revision
5248 revs = list(revs)
5248 revs = list(revs)
5249 revs.extend(opts[b'rev'])
5249 revs.extend(opts[b'rev'])
5250 if not revs:
5250 if not revs:
5251 # display both parents as the second parent phase can influence
5251 # display both parents as the second parent phase can influence
5252 # the phase of a merge commit
5252 # the phase of a merge commit
5253 revs = [c.rev() for c in repo[None].parents()]
5253 revs = [c.rev() for c in repo[None].parents()]
5254
5254
5255 revs = scmutil.revrange(repo, revs)
5255 revs = scmutil.revrange(repo, revs)
5256
5256
5257 ret = 0
5257 ret = 0
5258 if targetphase is None:
5258 if targetphase is None:
5259 # display
5259 # display
5260 for r in revs:
5260 for r in revs:
5261 ctx = repo[r]
5261 ctx = repo[r]
5262 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5262 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5263 else:
5263 else:
5264 with repo.lock(), repo.transaction(b"phase") as tr:
5264 with repo.lock(), repo.transaction(b"phase") as tr:
5265 # set phase
5265 # set phase
5266 if not revs:
5266 if not revs:
5267 raise error.Abort(_(b'empty revision set'))
5267 raise error.Abort(_(b'empty revision set'))
5268 nodes = [repo[r].node() for r in revs]
5268 nodes = [repo[r].node() for r in revs]
5269 # moving revision from public to draft may hide them
5269 # moving revision from public to draft may hide them
5270 # We have to check result on an unfiltered repository
5270 # We have to check result on an unfiltered repository
5271 unfi = repo.unfiltered()
5271 unfi = repo.unfiltered()
5272 getphase = unfi._phasecache.phase
5272 getphase = unfi._phasecache.phase
5273 olddata = [getphase(unfi, r) for r in unfi]
5273 olddata = [getphase(unfi, r) for r in unfi]
5274 phases.advanceboundary(repo, tr, targetphase, nodes)
5274 phases.advanceboundary(repo, tr, targetphase, nodes)
5275 if opts[b'force']:
5275 if opts[b'force']:
5276 phases.retractboundary(repo, tr, targetphase, nodes)
5276 phases.retractboundary(repo, tr, targetphase, nodes)
5277 getphase = unfi._phasecache.phase
5277 getphase = unfi._phasecache.phase
5278 newdata = [getphase(unfi, r) for r in unfi]
5278 newdata = [getphase(unfi, r) for r in unfi]
5279 changes = sum(newdata[r] != olddata[r] for r in unfi)
5279 changes = sum(newdata[r] != olddata[r] for r in unfi)
5280 cl = unfi.changelog
5280 cl = unfi.changelog
5281 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5281 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5282 if rejected:
5282 if rejected:
5283 ui.warn(
5283 ui.warn(
5284 _(
5284 _(
5285 b'cannot move %i changesets to a higher '
5285 b'cannot move %i changesets to a higher '
5286 b'phase, use --force\n'
5286 b'phase, use --force\n'
5287 )
5287 )
5288 % len(rejected)
5288 % len(rejected)
5289 )
5289 )
5290 ret = 1
5290 ret = 1
5291 if changes:
5291 if changes:
5292 msg = _(b'phase changed for %i changesets\n') % changes
5292 msg = _(b'phase changed for %i changesets\n') % changes
5293 if ret:
5293 if ret:
5294 ui.status(msg)
5294 ui.status(msg)
5295 else:
5295 else:
5296 ui.note(msg)
5296 ui.note(msg)
5297 else:
5297 else:
5298 ui.warn(_(b'no phases changed\n'))
5298 ui.warn(_(b'no phases changed\n'))
5299 return ret
5299 return ret
5300
5300
5301
5301
5302 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5302 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5303 """Run after a changegroup has been added via pull/unbundle
5303 """Run after a changegroup has been added via pull/unbundle
5304
5304
5305 This takes arguments below:
5305 This takes arguments below:
5306
5306
5307 :modheads: change of heads by pull/unbundle
5307 :modheads: change of heads by pull/unbundle
5308 :optupdate: updating working directory is needed or not
5308 :optupdate: updating working directory is needed or not
5309 :checkout: update destination revision (or None to default destination)
5309 :checkout: update destination revision (or None to default destination)
5310 :brev: a name, which might be a bookmark to be activated after updating
5310 :brev: a name, which might be a bookmark to be activated after updating
5311 """
5311 """
5312 if modheads == 0:
5312 if modheads == 0:
5313 return
5313 return
5314 if optupdate:
5314 if optupdate:
5315 try:
5315 try:
5316 return hg.updatetotally(ui, repo, checkout, brev)
5316 return hg.updatetotally(ui, repo, checkout, brev)
5317 except error.UpdateAbort as inst:
5317 except error.UpdateAbort as inst:
5318 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5318 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5319 hint = inst.hint
5319 hint = inst.hint
5320 raise error.UpdateAbort(msg, hint=hint)
5320 raise error.UpdateAbort(msg, hint=hint)
5321 if modheads is not None and modheads > 1:
5321 if modheads is not None and modheads > 1:
5322 currentbranchheads = len(repo.branchheads())
5322 currentbranchheads = len(repo.branchheads())
5323 if currentbranchheads == modheads:
5323 if currentbranchheads == modheads:
5324 ui.status(
5324 ui.status(
5325 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5325 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5326 )
5326 )
5327 elif currentbranchheads > 1:
5327 elif currentbranchheads > 1:
5328 ui.status(
5328 ui.status(
5329 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5329 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5330 )
5330 )
5331 else:
5331 else:
5332 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5332 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5333 elif not ui.configbool(b'commands', b'update.requiredest'):
5333 elif not ui.configbool(b'commands', b'update.requiredest'):
5334 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5334 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5335
5335
5336
5336
5337 @command(
5337 @command(
5338 b'pull',
5338 b'pull',
5339 [
5339 [
5340 (
5340 (
5341 b'u',
5341 b'u',
5342 b'update',
5342 b'update',
5343 None,
5343 None,
5344 _(b'update to new branch head if new descendants were pulled'),
5344 _(b'update to new branch head if new descendants were pulled'),
5345 ),
5345 ),
5346 (
5346 (
5347 b'f',
5347 b'f',
5348 b'force',
5348 b'force',
5349 None,
5349 None,
5350 _(b'run even when remote repository is unrelated'),
5350 _(b'run even when remote repository is unrelated'),
5351 ),
5351 ),
5352 (
5352 (
5353 b'r',
5353 b'r',
5354 b'rev',
5354 b'rev',
5355 [],
5355 [],
5356 _(b'a remote changeset intended to be added'),
5356 _(b'a remote changeset intended to be added'),
5357 _(b'REV'),
5357 _(b'REV'),
5358 ),
5358 ),
5359 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5359 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5360 (
5360 (
5361 b'b',
5361 b'b',
5362 b'branch',
5362 b'branch',
5363 [],
5363 [],
5364 _(b'a specific branch you would like to pull'),
5364 _(b'a specific branch you would like to pull'),
5365 _(b'BRANCH'),
5365 _(b'BRANCH'),
5366 ),
5366 ),
5367 ]
5367 ]
5368 + remoteopts,
5368 + remoteopts,
5369 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5369 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5370 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5370 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5371 helpbasic=True,
5371 helpbasic=True,
5372 )
5372 )
5373 def pull(ui, repo, source=b"default", **opts):
5373 def pull(ui, repo, source=b"default", **opts):
5374 """pull changes from the specified source
5374 """pull changes from the specified source
5375
5375
5376 Pull changes from a remote repository to a local one.
5376 Pull changes from a remote repository to a local one.
5377
5377
5378 This finds all changes from the repository at the specified path
5378 This finds all changes from the repository at the specified path
5379 or URL and adds them to a local repository (the current one unless
5379 or URL and adds them to a local repository (the current one unless
5380 -R is specified). By default, this does not update the copy of the
5380 -R is specified). By default, this does not update the copy of the
5381 project in the working directory.
5381 project in the working directory.
5382
5382
5383 When cloning from servers that support it, Mercurial may fetch
5383 When cloning from servers that support it, Mercurial may fetch
5384 pre-generated data. When this is done, hooks operating on incoming
5384 pre-generated data. When this is done, hooks operating on incoming
5385 changesets and changegroups may fire more than once, once for each
5385 changesets and changegroups may fire more than once, once for each
5386 pre-generated bundle and as well as for any additional remaining
5386 pre-generated bundle and as well as for any additional remaining
5387 data. See :hg:`help -e clonebundles` for more.
5387 data. See :hg:`help -e clonebundles` for more.
5388
5388
5389 Use :hg:`incoming` if you want to see what would have been added
5389 Use :hg:`incoming` if you want to see what would have been added
5390 by a pull at the time you issued this command. If you then decide
5390 by a pull at the time you issued this command. If you then decide
5391 to add those changes to the repository, you should use :hg:`pull
5391 to add those changes to the repository, you should use :hg:`pull
5392 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5392 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5393
5393
5394 If SOURCE is omitted, the 'default' path will be used.
5394 If SOURCE is omitted, the 'default' path will be used.
5395 See :hg:`help urls` for more information.
5395 See :hg:`help urls` for more information.
5396
5396
5397 Specifying bookmark as ``.`` is equivalent to specifying the active
5397 Specifying bookmark as ``.`` is equivalent to specifying the active
5398 bookmark's name.
5398 bookmark's name.
5399
5399
5400 Returns 0 on success, 1 if an update had unresolved files.
5400 Returns 0 on success, 1 if an update had unresolved files.
5401 """
5401 """
5402
5402
5403 opts = pycompat.byteskwargs(opts)
5403 opts = pycompat.byteskwargs(opts)
5404 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5404 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5405 b'update'
5405 b'update'
5406 ):
5406 ):
5407 msg = _(b'update destination required by configuration')
5407 msg = _(b'update destination required by configuration')
5408 hint = _(b'use hg pull followed by hg update DEST')
5408 hint = _(b'use hg pull followed by hg update DEST')
5409 raise error.Abort(msg, hint=hint)
5409 raise error.Abort(msg, hint=hint)
5410
5410
5411 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5411 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5412 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5412 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5413 other = hg.peer(repo, opts, source)
5413 other = hg.peer(repo, opts, source)
5414 try:
5414 try:
5415 revs, checkout = hg.addbranchrevs(
5415 revs, checkout = hg.addbranchrevs(
5416 repo, other, branches, opts.get(b'rev')
5416 repo, other, branches, opts.get(b'rev')
5417 )
5417 )
5418
5418
5419 pullopargs = {}
5419 pullopargs = {}
5420
5420
5421 nodes = None
5421 nodes = None
5422 if opts.get(b'bookmark') or revs:
5422 if opts.get(b'bookmark') or revs:
5423 # The list of bookmark used here is the same used to actually update
5423 # The list of bookmark used here is the same used to actually update
5424 # the bookmark names, to avoid the race from issue 4689 and we do
5424 # the bookmark names, to avoid the race from issue 4689 and we do
5425 # all lookup and bookmark queries in one go so they see the same
5425 # all lookup and bookmark queries in one go so they see the same
5426 # version of the server state (issue 4700).
5426 # version of the server state (issue 4700).
5427 nodes = []
5427 nodes = []
5428 fnodes = []
5428 fnodes = []
5429 revs = revs or []
5429 revs = revs or []
5430 if revs and not other.capable(b'lookup'):
5430 if revs and not other.capable(b'lookup'):
5431 err = _(
5431 err = _(
5432 b"other repository doesn't support revision lookup, "
5432 b"other repository doesn't support revision lookup, "
5433 b"so a rev cannot be specified."
5433 b"so a rev cannot be specified."
5434 )
5434 )
5435 raise error.Abort(err)
5435 raise error.Abort(err)
5436 with other.commandexecutor() as e:
5436 with other.commandexecutor() as e:
5437 fremotebookmarks = e.callcommand(
5437 fremotebookmarks = e.callcommand(
5438 b'listkeys', {b'namespace': b'bookmarks'}
5438 b'listkeys', {b'namespace': b'bookmarks'}
5439 )
5439 )
5440 for r in revs:
5440 for r in revs:
5441 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5441 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5442 remotebookmarks = fremotebookmarks.result()
5442 remotebookmarks = fremotebookmarks.result()
5443 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5443 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5444 pullopargs[b'remotebookmarks'] = remotebookmarks
5444 pullopargs[b'remotebookmarks'] = remotebookmarks
5445 for b in opts.get(b'bookmark', []):
5445 for b in opts.get(b'bookmark', []):
5446 b = repo._bookmarks.expandname(b)
5446 b = repo._bookmarks.expandname(b)
5447 if b not in remotebookmarks:
5447 if b not in remotebookmarks:
5448 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5448 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5449 nodes.append(remotebookmarks[b])
5449 nodes.append(remotebookmarks[b])
5450 for i, rev in enumerate(revs):
5450 for i, rev in enumerate(revs):
5451 node = fnodes[i].result()
5451 node = fnodes[i].result()
5452 nodes.append(node)
5452 nodes.append(node)
5453 if rev == checkout:
5453 if rev == checkout:
5454 checkout = node
5454 checkout = node
5455
5455
5456 wlock = util.nullcontextmanager()
5456 wlock = util.nullcontextmanager()
5457 if opts.get(b'update'):
5457 if opts.get(b'update'):
5458 wlock = repo.wlock()
5458 wlock = repo.wlock()
5459 with wlock:
5459 with wlock:
5460 pullopargs.update(opts.get(b'opargs', {}))
5460 pullopargs.update(opts.get(b'opargs', {}))
5461 modheads = exchange.pull(
5461 modheads = exchange.pull(
5462 repo,
5462 repo,
5463 other,
5463 other,
5464 heads=nodes,
5464 heads=nodes,
5465 force=opts.get(b'force'),
5465 force=opts.get(b'force'),
5466 bookmarks=opts.get(b'bookmark', ()),
5466 bookmarks=opts.get(b'bookmark', ()),
5467 opargs=pullopargs,
5467 opargs=pullopargs,
5468 ).cgresult
5468 ).cgresult
5469
5469
5470 # brev is a name, which might be a bookmark to be activated at
5470 # brev is a name, which might be a bookmark to be activated at
5471 # the end of the update. In other words, it is an explicit
5471 # the end of the update. In other words, it is an explicit
5472 # destination of the update
5472 # destination of the update
5473 brev = None
5473 brev = None
5474
5474
5475 if checkout:
5475 if checkout:
5476 checkout = repo.unfiltered().changelog.rev(checkout)
5476 checkout = repo.unfiltered().changelog.rev(checkout)
5477
5477
5478 # order below depends on implementation of
5478 # order below depends on implementation of
5479 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5479 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5480 # because 'checkout' is determined without it.
5480 # because 'checkout' is determined without it.
5481 if opts.get(b'rev'):
5481 if opts.get(b'rev'):
5482 brev = opts[b'rev'][0]
5482 brev = opts[b'rev'][0]
5483 elif opts.get(b'branch'):
5483 elif opts.get(b'branch'):
5484 brev = opts[b'branch'][0]
5484 brev = opts[b'branch'][0]
5485 else:
5485 else:
5486 brev = branches[0]
5486 brev = branches[0]
5487 repo._subtoppath = source
5487 repo._subtoppath = source
5488 try:
5488 try:
5489 ret = postincoming(
5489 ret = postincoming(
5490 ui, repo, modheads, opts.get(b'update'), checkout, brev
5490 ui, repo, modheads, opts.get(b'update'), checkout, brev
5491 )
5491 )
5492 except error.FilteredRepoLookupError as exc:
5492 except error.FilteredRepoLookupError as exc:
5493 msg = _(b'cannot update to target: %s') % exc.args[0]
5493 msg = _(b'cannot update to target: %s') % exc.args[0]
5494 exc.args = (msg,) + exc.args[1:]
5494 exc.args = (msg,) + exc.args[1:]
5495 raise
5495 raise
5496 finally:
5496 finally:
5497 del repo._subtoppath
5497 del repo._subtoppath
5498
5498
5499 finally:
5499 finally:
5500 other.close()
5500 other.close()
5501 return ret
5501 return ret
5502
5502
5503
5503
5504 @command(
5504 @command(
5505 b'push',
5505 b'push',
5506 [
5506 [
5507 (b'f', b'force', None, _(b'force push')),
5507 (b'f', b'force', None, _(b'force push')),
5508 (
5508 (
5509 b'r',
5509 b'r',
5510 b'rev',
5510 b'rev',
5511 [],
5511 [],
5512 _(b'a changeset intended to be included in the destination'),
5512 _(b'a changeset intended to be included in the destination'),
5513 _(b'REV'),
5513 _(b'REV'),
5514 ),
5514 ),
5515 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5515 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5516 (
5516 (
5517 b'b',
5517 b'b',
5518 b'branch',
5518 b'branch',
5519 [],
5519 [],
5520 _(b'a specific branch you would like to push'),
5520 _(b'a specific branch you would like to push'),
5521 _(b'BRANCH'),
5521 _(b'BRANCH'),
5522 ),
5522 ),
5523 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5523 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5524 (
5524 (
5525 b'',
5525 b'',
5526 b'pushvars',
5526 b'pushvars',
5527 [],
5527 [],
5528 _(b'variables that can be sent to server (ADVANCED)'),
5528 _(b'variables that can be sent to server (ADVANCED)'),
5529 ),
5529 ),
5530 (
5530 (
5531 b'',
5531 b'',
5532 b'publish',
5532 b'publish',
5533 False,
5533 False,
5534 _(b'push the changeset as public (EXPERIMENTAL)'),
5534 _(b'push the changeset as public (EXPERIMENTAL)'),
5535 ),
5535 ),
5536 ]
5536 ]
5537 + remoteopts,
5537 + remoteopts,
5538 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5538 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5539 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5539 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5540 helpbasic=True,
5540 helpbasic=True,
5541 )
5541 )
5542 def push(ui, repo, dest=None, **opts):
5542 def push(ui, repo, dest=None, **opts):
5543 """push changes to the specified destination
5543 """push changes to the specified destination
5544
5544
5545 Push changesets from the local repository to the specified
5545 Push changesets from the local repository to the specified
5546 destination.
5546 destination.
5547
5547
5548 This operation is symmetrical to pull: it is identical to a pull
5548 This operation is symmetrical to pull: it is identical to a pull
5549 in the destination repository from the current one.
5549 in the destination repository from the current one.
5550
5550
5551 By default, push will not allow creation of new heads at the
5551 By default, push will not allow creation of new heads at the
5552 destination, since multiple heads would make it unclear which head
5552 destination, since multiple heads would make it unclear which head
5553 to use. In this situation, it is recommended to pull and merge
5553 to use. In this situation, it is recommended to pull and merge
5554 before pushing.
5554 before pushing.
5555
5555
5556 Use --new-branch if you want to allow push to create a new named
5556 Use --new-branch if you want to allow push to create a new named
5557 branch that is not present at the destination. This allows you to
5557 branch that is not present at the destination. This allows you to
5558 only create a new branch without forcing other changes.
5558 only create a new branch without forcing other changes.
5559
5559
5560 .. note::
5560 .. note::
5561
5561
5562 Extra care should be taken with the -f/--force option,
5562 Extra care should be taken with the -f/--force option,
5563 which will push all new heads on all branches, an action which will
5563 which will push all new heads on all branches, an action which will
5564 almost always cause confusion for collaborators.
5564 almost always cause confusion for collaborators.
5565
5565
5566 If -r/--rev is used, the specified revision and all its ancestors
5566 If -r/--rev is used, the specified revision and all its ancestors
5567 will be pushed to the remote repository.
5567 will be pushed to the remote repository.
5568
5568
5569 If -B/--bookmark is used, the specified bookmarked revision, its
5569 If -B/--bookmark is used, the specified bookmarked revision, its
5570 ancestors, and the bookmark will be pushed to the remote
5570 ancestors, and the bookmark will be pushed to the remote
5571 repository. Specifying ``.`` is equivalent to specifying the active
5571 repository. Specifying ``.`` is equivalent to specifying the active
5572 bookmark's name.
5572 bookmark's name.
5573
5573
5574 Please see :hg:`help urls` for important details about ``ssh://``
5574 Please see :hg:`help urls` for important details about ``ssh://``
5575 URLs. If DESTINATION is omitted, a default path will be used.
5575 URLs. If DESTINATION is omitted, a default path will be used.
5576
5576
5577 .. container:: verbose
5577 .. container:: verbose
5578
5578
5579 The --pushvars option sends strings to the server that become
5579 The --pushvars option sends strings to the server that become
5580 environment variables prepended with ``HG_USERVAR_``. For example,
5580 environment variables prepended with ``HG_USERVAR_``. For example,
5581 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5581 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5582 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5582 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5583
5583
5584 pushvars can provide for user-overridable hooks as well as set debug
5584 pushvars can provide for user-overridable hooks as well as set debug
5585 levels. One example is having a hook that blocks commits containing
5585 levels. One example is having a hook that blocks commits containing
5586 conflict markers, but enables the user to override the hook if the file
5586 conflict markers, but enables the user to override the hook if the file
5587 is using conflict markers for testing purposes or the file format has
5587 is using conflict markers for testing purposes or the file format has
5588 strings that look like conflict markers.
5588 strings that look like conflict markers.
5589
5589
5590 By default, servers will ignore `--pushvars`. To enable it add the
5590 By default, servers will ignore `--pushvars`. To enable it add the
5591 following to your configuration file::
5591 following to your configuration file::
5592
5592
5593 [push]
5593 [push]
5594 pushvars.server = true
5594 pushvars.server = true
5595
5595
5596 Returns 0 if push was successful, 1 if nothing to push.
5596 Returns 0 if push was successful, 1 if nothing to push.
5597 """
5597 """
5598
5598
5599 opts = pycompat.byteskwargs(opts)
5599 opts = pycompat.byteskwargs(opts)
5600 if opts.get(b'bookmark'):
5600 if opts.get(b'bookmark'):
5601 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5601 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5602 for b in opts[b'bookmark']:
5602 for b in opts[b'bookmark']:
5603 # translate -B options to -r so changesets get pushed
5603 # translate -B options to -r so changesets get pushed
5604 b = repo._bookmarks.expandname(b)
5604 b = repo._bookmarks.expandname(b)
5605 if b in repo._bookmarks:
5605 if b in repo._bookmarks:
5606 opts.setdefault(b'rev', []).append(b)
5606 opts.setdefault(b'rev', []).append(b)
5607 else:
5607 else:
5608 # if we try to push a deleted bookmark, translate it to null
5608 # if we try to push a deleted bookmark, translate it to null
5609 # this lets simultaneous -r, -b options continue working
5609 # this lets simultaneous -r, -b options continue working
5610 opts.setdefault(b'rev', []).append(b"null")
5610 opts.setdefault(b'rev', []).append(b"null")
5611
5611
5612 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5612 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5613 if not path:
5613 if not path:
5614 raise error.Abort(
5614 raise error.Abort(
5615 _(b'default repository not configured!'),
5615 _(b'default repository not configured!'),
5616 hint=_(b"see 'hg help config.paths'"),
5616 hint=_(b"see 'hg help config.paths'"),
5617 )
5617 )
5618 dest = path.pushloc or path.loc
5618 dest = path.pushloc or path.loc
5619 branches = (path.branch, opts.get(b'branch') or [])
5619 branches = (path.branch, opts.get(b'branch') or [])
5620 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5620 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5621 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5621 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5622 other = hg.peer(repo, opts, dest)
5622 other = hg.peer(repo, opts, dest)
5623
5623
5624 if revs:
5624 if revs:
5625 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5625 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5626 if not revs:
5626 if not revs:
5627 raise error.Abort(
5627 raise error.Abort(
5628 _(b"specified revisions evaluate to an empty set"),
5628 _(b"specified revisions evaluate to an empty set"),
5629 hint=_(b"use different revision arguments"),
5629 hint=_(b"use different revision arguments"),
5630 )
5630 )
5631 elif path.pushrev:
5631 elif path.pushrev:
5632 # It doesn't make any sense to specify ancestor revisions. So limit
5632 # It doesn't make any sense to specify ancestor revisions. So limit
5633 # to DAG heads to make discovery simpler.
5633 # to DAG heads to make discovery simpler.
5634 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5634 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5635 revs = scmutil.revrange(repo, [expr])
5635 revs = scmutil.revrange(repo, [expr])
5636 revs = [repo[rev].node() for rev in revs]
5636 revs = [repo[rev].node() for rev in revs]
5637 if not revs:
5637 if not revs:
5638 raise error.Abort(
5638 raise error.Abort(
5639 _(b'default push revset for path evaluates to an empty set')
5639 _(b'default push revset for path evaluates to an empty set')
5640 )
5640 )
5641 elif ui.configbool(b'commands', b'push.require-revs'):
5641 elif ui.configbool(b'commands', b'push.require-revs'):
5642 raise error.Abort(
5642 raise error.Abort(
5643 _(b'no revisions specified to push'),
5643 _(b'no revisions specified to push'),
5644 hint=_(b'did you mean "hg push -r ."?'),
5644 hint=_(b'did you mean "hg push -r ."?'),
5645 )
5645 )
5646
5646
5647 repo._subtoppath = dest
5647 repo._subtoppath = dest
5648 try:
5648 try:
5649 # push subrepos depth-first for coherent ordering
5649 # push subrepos depth-first for coherent ordering
5650 c = repo[b'.']
5650 c = repo[b'.']
5651 subs = c.substate # only repos that are committed
5651 subs = c.substate # only repos that are committed
5652 for s in sorted(subs):
5652 for s in sorted(subs):
5653 result = c.sub(s).push(opts)
5653 result = c.sub(s).push(opts)
5654 if result == 0:
5654 if result == 0:
5655 return not result
5655 return not result
5656 finally:
5656 finally:
5657 del repo._subtoppath
5657 del repo._subtoppath
5658
5658
5659 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5659 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5660 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5660 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5661
5661
5662 pushop = exchange.push(
5662 pushop = exchange.push(
5663 repo,
5663 repo,
5664 other,
5664 other,
5665 opts.get(b'force'),
5665 opts.get(b'force'),
5666 revs=revs,
5666 revs=revs,
5667 newbranch=opts.get(b'new_branch'),
5667 newbranch=opts.get(b'new_branch'),
5668 bookmarks=opts.get(b'bookmark', ()),
5668 bookmarks=opts.get(b'bookmark', ()),
5669 publish=opts.get(b'publish'),
5669 publish=opts.get(b'publish'),
5670 opargs=opargs,
5670 opargs=opargs,
5671 )
5671 )
5672
5672
5673 result = not pushop.cgresult
5673 result = not pushop.cgresult
5674
5674
5675 if pushop.bkresult is not None:
5675 if pushop.bkresult is not None:
5676 if pushop.bkresult == 2:
5676 if pushop.bkresult == 2:
5677 result = 2
5677 result = 2
5678 elif not result and pushop.bkresult:
5678 elif not result and pushop.bkresult:
5679 result = 2
5679 result = 2
5680
5680
5681 return result
5681 return result
5682
5682
5683
5683
5684 @command(
5684 @command(
5685 b'recover',
5685 b'recover',
5686 [(b'', b'verify', True, b"run `hg verify` after succesful recover"),],
5686 [(b'', b'verify', True, b"run `hg verify` after succesful recover"),],
5687 helpcategory=command.CATEGORY_MAINTENANCE,
5687 helpcategory=command.CATEGORY_MAINTENANCE,
5688 )
5688 )
5689 def recover(ui, repo, **opts):
5689 def recover(ui, repo, **opts):
5690 """roll back an interrupted transaction
5690 """roll back an interrupted transaction
5691
5691
5692 Recover from an interrupted commit or pull.
5692 Recover from an interrupted commit or pull.
5693
5693
5694 This command tries to fix the repository status after an
5694 This command tries to fix the repository status after an
5695 interrupted operation. It should only be necessary when Mercurial
5695 interrupted operation. It should only be necessary when Mercurial
5696 suggests it.
5696 suggests it.
5697
5697
5698 Returns 0 if successful, 1 if nothing to recover or verify fails.
5698 Returns 0 if successful, 1 if nothing to recover or verify fails.
5699 """
5699 """
5700 ret = repo.recover()
5700 ret = repo.recover()
5701 if ret:
5701 if ret:
5702 if opts['verify']:
5702 if opts['verify']:
5703 return hg.verify(repo)
5703 return hg.verify(repo)
5704 else:
5704 else:
5705 msg = _(
5705 msg = _(
5706 b"(verify step skipped, run `hg verify` to check your "
5706 b"(verify step skipped, run `hg verify` to check your "
5707 b"repository content)\n"
5707 b"repository content)\n"
5708 )
5708 )
5709 ui.warn(msg)
5709 ui.warn(msg)
5710 return 0
5710 return 0
5711 return 1
5711 return 1
5712
5712
5713
5713
5714 @command(
5714 @command(
5715 b'remove|rm',
5715 b'remove|rm',
5716 [
5716 [
5717 (b'A', b'after', None, _(b'record delete for missing files')),
5717 (b'A', b'after', None, _(b'record delete for missing files')),
5718 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5718 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5719 ]
5719 ]
5720 + subrepoopts
5720 + subrepoopts
5721 + walkopts
5721 + walkopts
5722 + dryrunopts,
5722 + dryrunopts,
5723 _(b'[OPTION]... FILE...'),
5723 _(b'[OPTION]... FILE...'),
5724 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5724 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5725 helpbasic=True,
5725 helpbasic=True,
5726 inferrepo=True,
5726 inferrepo=True,
5727 )
5727 )
5728 def remove(ui, repo, *pats, **opts):
5728 def remove(ui, repo, *pats, **opts):
5729 """remove the specified files on the next commit
5729 """remove the specified files on the next commit
5730
5730
5731 Schedule the indicated files for removal from the current branch.
5731 Schedule the indicated files for removal from the current branch.
5732
5732
5733 This command schedules the files to be removed at the next commit.
5733 This command schedules the files to be removed at the next commit.
5734 To undo a remove before that, see :hg:`revert`. To undo added
5734 To undo a remove before that, see :hg:`revert`. To undo added
5735 files, see :hg:`forget`.
5735 files, see :hg:`forget`.
5736
5736
5737 .. container:: verbose
5737 .. container:: verbose
5738
5738
5739 -A/--after can be used to remove only files that have already
5739 -A/--after can be used to remove only files that have already
5740 been deleted, -f/--force can be used to force deletion, and -Af
5740 been deleted, -f/--force can be used to force deletion, and -Af
5741 can be used to remove files from the next revision without
5741 can be used to remove files from the next revision without
5742 deleting them from the working directory.
5742 deleting them from the working directory.
5743
5743
5744 The following table details the behavior of remove for different
5744 The following table details the behavior of remove for different
5745 file states (columns) and option combinations (rows). The file
5745 file states (columns) and option combinations (rows). The file
5746 states are Added [A], Clean [C], Modified [M] and Missing [!]
5746 states are Added [A], Clean [C], Modified [M] and Missing [!]
5747 (as reported by :hg:`status`). The actions are Warn, Remove
5747 (as reported by :hg:`status`). The actions are Warn, Remove
5748 (from branch) and Delete (from disk):
5748 (from branch) and Delete (from disk):
5749
5749
5750 ========= == == == ==
5750 ========= == == == ==
5751 opt/state A C M !
5751 opt/state A C M !
5752 ========= == == == ==
5752 ========= == == == ==
5753 none W RD W R
5753 none W RD W R
5754 -f R RD RD R
5754 -f R RD RD R
5755 -A W W W R
5755 -A W W W R
5756 -Af R R R R
5756 -Af R R R R
5757 ========= == == == ==
5757 ========= == == == ==
5758
5758
5759 .. note::
5759 .. note::
5760
5760
5761 :hg:`remove` never deletes files in Added [A] state from the
5761 :hg:`remove` never deletes files in Added [A] state from the
5762 working directory, not even if ``--force`` is specified.
5762 working directory, not even if ``--force`` is specified.
5763
5763
5764 Returns 0 on success, 1 if any warnings encountered.
5764 Returns 0 on success, 1 if any warnings encountered.
5765 """
5765 """
5766
5766
5767 opts = pycompat.byteskwargs(opts)
5767 opts = pycompat.byteskwargs(opts)
5768 after, force = opts.get(b'after'), opts.get(b'force')
5768 after, force = opts.get(b'after'), opts.get(b'force')
5769 dryrun = opts.get(b'dry_run')
5769 dryrun = opts.get(b'dry_run')
5770 if not pats and not after:
5770 if not pats and not after:
5771 raise error.Abort(_(b'no files specified'))
5771 raise error.Abort(_(b'no files specified'))
5772
5772
5773 m = scmutil.match(repo[None], pats, opts)
5773 m = scmutil.match(repo[None], pats, opts)
5774 subrepos = opts.get(b'subrepos')
5774 subrepos = opts.get(b'subrepos')
5775 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5775 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5776 return cmdutil.remove(
5776 return cmdutil.remove(
5777 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5777 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5778 )
5778 )
5779
5779
5780
5780
5781 @command(
5781 @command(
5782 b'rename|move|mv',
5782 b'rename|move|mv',
5783 [
5783 [
5784 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5784 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5785 (
5785 (
5786 b'f',
5786 b'f',
5787 b'force',
5787 b'force',
5788 None,
5788 None,
5789 _(b'forcibly move over an existing managed file'),
5789 _(b'forcibly move over an existing managed file'),
5790 ),
5790 ),
5791 ]
5791 ]
5792 + walkopts
5792 + walkopts
5793 + dryrunopts,
5793 + dryrunopts,
5794 _(b'[OPTION]... SOURCE... DEST'),
5794 _(b'[OPTION]... SOURCE... DEST'),
5795 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5795 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5796 )
5796 )
5797 def rename(ui, repo, *pats, **opts):
5797 def rename(ui, repo, *pats, **opts):
5798 """rename files; equivalent of copy + remove
5798 """rename files; equivalent of copy + remove
5799
5799
5800 Mark dest as copies of sources; mark sources for deletion. If dest
5800 Mark dest as copies of sources; mark sources for deletion. If dest
5801 is a directory, copies are put in that directory. If dest is a
5801 is a directory, copies are put in that directory. If dest is a
5802 file, there can only be one source.
5802 file, there can only be one source.
5803
5803
5804 By default, this command copies the contents of files as they
5804 By default, this command copies the contents of files as they
5805 exist in the working directory. If invoked with -A/--after, the
5805 exist in the working directory. If invoked with -A/--after, the
5806 operation is recorded, but no copying is performed.
5806 operation is recorded, but no copying is performed.
5807
5807
5808 This command takes effect at the next commit. To undo a rename
5808 This command takes effect at the next commit. To undo a rename
5809 before that, see :hg:`revert`.
5809 before that, see :hg:`revert`.
5810
5810
5811 Returns 0 on success, 1 if errors are encountered.
5811 Returns 0 on success, 1 if errors are encountered.
5812 """
5812 """
5813 opts = pycompat.byteskwargs(opts)
5813 opts = pycompat.byteskwargs(opts)
5814 with repo.wlock(False):
5814 with repo.wlock(False):
5815 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5815 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5816
5816
5817
5817
5818 @command(
5818 @command(
5819 b'resolve',
5819 b'resolve',
5820 [
5820 [
5821 (b'a', b'all', None, _(b'select all unresolved files')),
5821 (b'a', b'all', None, _(b'select all unresolved files')),
5822 (b'l', b'list', None, _(b'list state of files needing merge')),
5822 (b'l', b'list', None, _(b'list state of files needing merge')),
5823 (b'm', b'mark', None, _(b'mark files as resolved')),
5823 (b'm', b'mark', None, _(b'mark files as resolved')),
5824 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5824 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5825 (b'n', b'no-status', None, _(b'hide status prefix')),
5825 (b'n', b'no-status', None, _(b'hide status prefix')),
5826 (b'', b're-merge', None, _(b're-merge files')),
5826 (b'', b're-merge', None, _(b're-merge files')),
5827 ]
5827 ]
5828 + mergetoolopts
5828 + mergetoolopts
5829 + walkopts
5829 + walkopts
5830 + formatteropts,
5830 + formatteropts,
5831 _(b'[OPTION]... [FILE]...'),
5831 _(b'[OPTION]... [FILE]...'),
5832 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5832 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5833 inferrepo=True,
5833 inferrepo=True,
5834 )
5834 )
5835 def resolve(ui, repo, *pats, **opts):
5835 def resolve(ui, repo, *pats, **opts):
5836 """redo merges or set/view the merge status of files
5836 """redo merges or set/view the merge status of files
5837
5837
5838 Merges with unresolved conflicts are often the result of
5838 Merges with unresolved conflicts are often the result of
5839 non-interactive merging using the ``internal:merge`` configuration
5839 non-interactive merging using the ``internal:merge`` configuration
5840 setting, or a command-line merge tool like ``diff3``. The resolve
5840 setting, or a command-line merge tool like ``diff3``. The resolve
5841 command is used to manage the files involved in a merge, after
5841 command is used to manage the files involved in a merge, after
5842 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5842 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5843 working directory must have two parents). See :hg:`help
5843 working directory must have two parents). See :hg:`help
5844 merge-tools` for information on configuring merge tools.
5844 merge-tools` for information on configuring merge tools.
5845
5845
5846 The resolve command can be used in the following ways:
5846 The resolve command can be used in the following ways:
5847
5847
5848 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5848 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5849 the specified files, discarding any previous merge attempts. Re-merging
5849 the specified files, discarding any previous merge attempts. Re-merging
5850 is not performed for files already marked as resolved. Use ``--all/-a``
5850 is not performed for files already marked as resolved. Use ``--all/-a``
5851 to select all unresolved files. ``--tool`` can be used to specify
5851 to select all unresolved files. ``--tool`` can be used to specify
5852 the merge tool used for the given files. It overrides the HGMERGE
5852 the merge tool used for the given files. It overrides the HGMERGE
5853 environment variable and your configuration files. Previous file
5853 environment variable and your configuration files. Previous file
5854 contents are saved with a ``.orig`` suffix.
5854 contents are saved with a ``.orig`` suffix.
5855
5855
5856 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5856 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5857 (e.g. after having manually fixed-up the files). The default is
5857 (e.g. after having manually fixed-up the files). The default is
5858 to mark all unresolved files.
5858 to mark all unresolved files.
5859
5859
5860 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5860 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5861 default is to mark all resolved files.
5861 default is to mark all resolved files.
5862
5862
5863 - :hg:`resolve -l`: list files which had or still have conflicts.
5863 - :hg:`resolve -l`: list files which had or still have conflicts.
5864 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5864 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5865 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5865 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5866 the list. See :hg:`help filesets` for details.
5866 the list. See :hg:`help filesets` for details.
5867
5867
5868 .. note::
5868 .. note::
5869
5869
5870 Mercurial will not let you commit files with unresolved merge
5870 Mercurial will not let you commit files with unresolved merge
5871 conflicts. You must use :hg:`resolve -m ...` before you can
5871 conflicts. You must use :hg:`resolve -m ...` before you can
5872 commit after a conflicting merge.
5872 commit after a conflicting merge.
5873
5873
5874 .. container:: verbose
5874 .. container:: verbose
5875
5875
5876 Template:
5876 Template:
5877
5877
5878 The following keywords are supported in addition to the common template
5878 The following keywords are supported in addition to the common template
5879 keywords and functions. See also :hg:`help templates`.
5879 keywords and functions. See also :hg:`help templates`.
5880
5880
5881 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5881 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5882 :path: String. Repository-absolute path of the file.
5882 :path: String. Repository-absolute path of the file.
5883
5883
5884 Returns 0 on success, 1 if any files fail a resolve attempt.
5884 Returns 0 on success, 1 if any files fail a resolve attempt.
5885 """
5885 """
5886
5886
5887 opts = pycompat.byteskwargs(opts)
5887 opts = pycompat.byteskwargs(opts)
5888 confirm = ui.configbool(b'commands', b'resolve.confirm')
5888 confirm = ui.configbool(b'commands', b'resolve.confirm')
5889 flaglist = b'all mark unmark list no_status re_merge'.split()
5889 flaglist = b'all mark unmark list no_status re_merge'.split()
5890 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5890 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5891
5891
5892 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5892 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5893 if actioncount > 1:
5893 if actioncount > 1:
5894 raise error.Abort(_(b"too many actions specified"))
5894 raise error.Abort(_(b"too many actions specified"))
5895 elif actioncount == 0 and ui.configbool(
5895 elif actioncount == 0 and ui.configbool(
5896 b'commands', b'resolve.explicit-re-merge'
5896 b'commands', b'resolve.explicit-re-merge'
5897 ):
5897 ):
5898 hint = _(b'use --mark, --unmark, --list or --re-merge')
5898 hint = _(b'use --mark, --unmark, --list or --re-merge')
5899 raise error.Abort(_(b'no action specified'), hint=hint)
5899 raise error.Abort(_(b'no action specified'), hint=hint)
5900 if pats and all:
5900 if pats and all:
5901 raise error.Abort(_(b"can't specify --all and patterns"))
5901 raise error.Abort(_(b"can't specify --all and patterns"))
5902 if not (all or pats or show or mark or unmark):
5902 if not (all or pats or show or mark or unmark):
5903 raise error.Abort(
5903 raise error.Abort(
5904 _(b'no files or directories specified'),
5904 _(b'no files or directories specified'),
5905 hint=b'use --all to re-merge all unresolved files',
5905 hint=b'use --all to re-merge all unresolved files',
5906 )
5906 )
5907
5907
5908 if confirm:
5908 if confirm:
5909 if all:
5909 if all:
5910 if ui.promptchoice(
5910 if ui.promptchoice(
5911 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5911 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5912 ):
5912 ):
5913 raise error.Abort(_(b'user quit'))
5913 raise error.Abort(_(b'user quit'))
5914 if mark and not pats:
5914 if mark and not pats:
5915 if ui.promptchoice(
5915 if ui.promptchoice(
5916 _(
5916 _(
5917 b'mark all unresolved files as resolved (yn)?'
5917 b'mark all unresolved files as resolved (yn)?'
5918 b'$$ &Yes $$ &No'
5918 b'$$ &Yes $$ &No'
5919 )
5919 )
5920 ):
5920 ):
5921 raise error.Abort(_(b'user quit'))
5921 raise error.Abort(_(b'user quit'))
5922 if unmark and not pats:
5922 if unmark and not pats:
5923 if ui.promptchoice(
5923 if ui.promptchoice(
5924 _(
5924 _(
5925 b'mark all resolved files as unresolved (yn)?'
5925 b'mark all resolved files as unresolved (yn)?'
5926 b'$$ &Yes $$ &No'
5926 b'$$ &Yes $$ &No'
5927 )
5927 )
5928 ):
5928 ):
5929 raise error.Abort(_(b'user quit'))
5929 raise error.Abort(_(b'user quit'))
5930
5930
5931 uipathfn = scmutil.getuipathfn(repo)
5931 uipathfn = scmutil.getuipathfn(repo)
5932
5932
5933 if show:
5933 if show:
5934 ui.pager(b'resolve')
5934 ui.pager(b'resolve')
5935 fm = ui.formatter(b'resolve', opts)
5935 fm = ui.formatter(b'resolve', opts)
5936 ms = mergemod.mergestate.read(repo)
5936 ms = mergemod.mergestate.read(repo)
5937 wctx = repo[None]
5937 wctx = repo[None]
5938 m = scmutil.match(wctx, pats, opts)
5938 m = scmutil.match(wctx, pats, opts)
5939
5939
5940 # Labels and keys based on merge state. Unresolved path conflicts show
5940 # Labels and keys based on merge state. Unresolved path conflicts show
5941 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5941 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5942 # resolved conflicts.
5942 # resolved conflicts.
5943 mergestateinfo = {
5943 mergestateinfo = {
5944 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5944 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5945 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5945 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5946 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5946 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5947 b'resolve.unresolved',
5947 b'resolve.unresolved',
5948 b'P',
5948 b'P',
5949 ),
5949 ),
5950 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5950 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5951 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5951 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5952 b'resolve.driverresolved',
5952 b'resolve.driverresolved',
5953 b'D',
5953 b'D',
5954 ),
5954 ),
5955 }
5955 }
5956
5956
5957 for f in ms:
5957 for f in ms:
5958 if not m(f):
5958 if not m(f):
5959 continue
5959 continue
5960
5960
5961 label, key = mergestateinfo[ms[f]]
5961 label, key = mergestateinfo[ms[f]]
5962 fm.startitem()
5962 fm.startitem()
5963 fm.context(ctx=wctx)
5963 fm.context(ctx=wctx)
5964 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5964 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5965 fm.data(path=f)
5965 fm.data(path=f)
5966 fm.plain(b'%s\n' % uipathfn(f), label=label)
5966 fm.plain(b'%s\n' % uipathfn(f), label=label)
5967 fm.end()
5967 fm.end()
5968 return 0
5968 return 0
5969
5969
5970 with repo.wlock():
5970 with repo.wlock():
5971 ms = mergemod.mergestate.read(repo)
5971 ms = mergemod.mergestate.read(repo)
5972
5972
5973 if not (ms.active() or repo.dirstate.p2() != nullid):
5973 if not (ms.active() or repo.dirstate.p2() != nullid):
5974 raise error.Abort(
5974 raise error.Abort(
5975 _(b'resolve command not applicable when not merging')
5975 _(b'resolve command not applicable when not merging')
5976 )
5976 )
5977
5977
5978 wctx = repo[None]
5978 wctx = repo[None]
5979
5979
5980 if (
5980 if (
5981 ms.mergedriver
5981 ms.mergedriver
5982 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5982 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5983 ):
5983 ):
5984 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5984 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5985 ms.commit()
5985 ms.commit()
5986 # allow mark and unmark to go through
5986 # allow mark and unmark to go through
5987 if not mark and not unmark and not proceed:
5987 if not mark and not unmark and not proceed:
5988 return 1
5988 return 1
5989
5989
5990 m = scmutil.match(wctx, pats, opts)
5990 m = scmutil.match(wctx, pats, opts)
5991 ret = 0
5991 ret = 0
5992 didwork = False
5992 didwork = False
5993 runconclude = False
5993 runconclude = False
5994
5994
5995 tocomplete = []
5995 tocomplete = []
5996 hasconflictmarkers = []
5996 hasconflictmarkers = []
5997 if mark:
5997 if mark:
5998 markcheck = ui.config(b'commands', b'resolve.mark-check')
5998 markcheck = ui.config(b'commands', b'resolve.mark-check')
5999 if markcheck not in [b'warn', b'abort']:
5999 if markcheck not in [b'warn', b'abort']:
6000 # Treat all invalid / unrecognized values as 'none'.
6000 # Treat all invalid / unrecognized values as 'none'.
6001 markcheck = False
6001 markcheck = False
6002 for f in ms:
6002 for f in ms:
6003 if not m(f):
6003 if not m(f):
6004 continue
6004 continue
6005
6005
6006 didwork = True
6006 didwork = True
6007
6007
6008 # don't let driver-resolved files be marked, and run the conclude
6008 # don't let driver-resolved files be marked, and run the conclude
6009 # step if asked to resolve
6009 # step if asked to resolve
6010 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
6010 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
6011 exact = m.exact(f)
6011 exact = m.exact(f)
6012 if mark:
6012 if mark:
6013 if exact:
6013 if exact:
6014 ui.warn(
6014 ui.warn(
6015 _(b'not marking %s as it is driver-resolved\n')
6015 _(b'not marking %s as it is driver-resolved\n')
6016 % uipathfn(f)
6016 % uipathfn(f)
6017 )
6017 )
6018 elif unmark:
6018 elif unmark:
6019 if exact:
6019 if exact:
6020 ui.warn(
6020 ui.warn(
6021 _(b'not unmarking %s as it is driver-resolved\n')
6021 _(b'not unmarking %s as it is driver-resolved\n')
6022 % uipathfn(f)
6022 % uipathfn(f)
6023 )
6023 )
6024 else:
6024 else:
6025 runconclude = True
6025 runconclude = True
6026 continue
6026 continue
6027
6027
6028 # path conflicts must be resolved manually
6028 # path conflicts must be resolved manually
6029 if ms[f] in (
6029 if ms[f] in (
6030 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6030 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6031 mergemod.MERGE_RECORD_RESOLVED_PATH,
6031 mergemod.MERGE_RECORD_RESOLVED_PATH,
6032 ):
6032 ):
6033 if mark:
6033 if mark:
6034 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6034 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6035 elif unmark:
6035 elif unmark:
6036 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6036 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6037 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6037 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6038 ui.warn(
6038 ui.warn(
6039 _(b'%s: path conflict must be resolved manually\n')
6039 _(b'%s: path conflict must be resolved manually\n')
6040 % uipathfn(f)
6040 % uipathfn(f)
6041 )
6041 )
6042 continue
6042 continue
6043
6043
6044 if mark:
6044 if mark:
6045 if markcheck:
6045 if markcheck:
6046 fdata = repo.wvfs.tryread(f)
6046 fdata = repo.wvfs.tryread(f)
6047 if (
6047 if (
6048 filemerge.hasconflictmarkers(fdata)
6048 filemerge.hasconflictmarkers(fdata)
6049 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6049 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6050 ):
6050 ):
6051 hasconflictmarkers.append(f)
6051 hasconflictmarkers.append(f)
6052 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6052 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6053 elif unmark:
6053 elif unmark:
6054 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6054 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6055 else:
6055 else:
6056 # backup pre-resolve (merge uses .orig for its own purposes)
6056 # backup pre-resolve (merge uses .orig for its own purposes)
6057 a = repo.wjoin(f)
6057 a = repo.wjoin(f)
6058 try:
6058 try:
6059 util.copyfile(a, a + b".resolve")
6059 util.copyfile(a, a + b".resolve")
6060 except (IOError, OSError) as inst:
6060 except (IOError, OSError) as inst:
6061 if inst.errno != errno.ENOENT:
6061 if inst.errno != errno.ENOENT:
6062 raise
6062 raise
6063
6063
6064 try:
6064 try:
6065 # preresolve file
6065 # preresolve file
6066 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6066 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6067 with ui.configoverride(overrides, b'resolve'):
6067 with ui.configoverride(overrides, b'resolve'):
6068 complete, r = ms.preresolve(f, wctx)
6068 complete, r = ms.preresolve(f, wctx)
6069 if not complete:
6069 if not complete:
6070 tocomplete.append(f)
6070 tocomplete.append(f)
6071 elif r:
6071 elif r:
6072 ret = 1
6072 ret = 1
6073 finally:
6073 finally:
6074 ms.commit()
6074 ms.commit()
6075
6075
6076 # replace filemerge's .orig file with our resolve file, but only
6076 # replace filemerge's .orig file with our resolve file, but only
6077 # for merges that are complete
6077 # for merges that are complete
6078 if complete:
6078 if complete:
6079 try:
6079 try:
6080 util.rename(
6080 util.rename(
6081 a + b".resolve", scmutil.backuppath(ui, repo, f)
6081 a + b".resolve", scmutil.backuppath(ui, repo, f)
6082 )
6082 )
6083 except OSError as inst:
6083 except OSError as inst:
6084 if inst.errno != errno.ENOENT:
6084 if inst.errno != errno.ENOENT:
6085 raise
6085 raise
6086
6086
6087 if hasconflictmarkers:
6087 if hasconflictmarkers:
6088 ui.warn(
6088 ui.warn(
6089 _(
6089 _(
6090 b'warning: the following files still have conflict '
6090 b'warning: the following files still have conflict '
6091 b'markers:\n'
6091 b'markers:\n'
6092 )
6092 )
6093 + b''.join(
6093 + b''.join(
6094 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6094 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6095 )
6095 )
6096 )
6096 )
6097 if markcheck == b'abort' and not all and not pats:
6097 if markcheck == b'abort' and not all and not pats:
6098 raise error.Abort(
6098 raise error.Abort(
6099 _(b'conflict markers detected'),
6099 _(b'conflict markers detected'),
6100 hint=_(b'use --all to mark anyway'),
6100 hint=_(b'use --all to mark anyway'),
6101 )
6101 )
6102
6102
6103 for f in tocomplete:
6103 for f in tocomplete:
6104 try:
6104 try:
6105 # resolve file
6105 # resolve file
6106 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6106 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6107 with ui.configoverride(overrides, b'resolve'):
6107 with ui.configoverride(overrides, b'resolve'):
6108 r = ms.resolve(f, wctx)
6108 r = ms.resolve(f, wctx)
6109 if r:
6109 if r:
6110 ret = 1
6110 ret = 1
6111 finally:
6111 finally:
6112 ms.commit()
6112 ms.commit()
6113
6113
6114 # replace filemerge's .orig file with our resolve file
6114 # replace filemerge's .orig file with our resolve file
6115 a = repo.wjoin(f)
6115 a = repo.wjoin(f)
6116 try:
6116 try:
6117 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6117 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6118 except OSError as inst:
6118 except OSError as inst:
6119 if inst.errno != errno.ENOENT:
6119 if inst.errno != errno.ENOENT:
6120 raise
6120 raise
6121
6121
6122 ms.commit()
6122 ms.commit()
6123 ms.recordactions()
6123 ms.recordactions()
6124
6124
6125 if not didwork and pats:
6125 if not didwork and pats:
6126 hint = None
6126 hint = None
6127 if not any([p for p in pats if p.find(b':') >= 0]):
6127 if not any([p for p in pats if p.find(b':') >= 0]):
6128 pats = [b'path:%s' % p for p in pats]
6128 pats = [b'path:%s' % p for p in pats]
6129 m = scmutil.match(wctx, pats, opts)
6129 m = scmutil.match(wctx, pats, opts)
6130 for f in ms:
6130 for f in ms:
6131 if not m(f):
6131 if not m(f):
6132 continue
6132 continue
6133
6133
6134 def flag(o):
6134 def flag(o):
6135 if o == b're_merge':
6135 if o == b're_merge':
6136 return b'--re-merge '
6136 return b'--re-merge '
6137 return b'-%s ' % o[0:1]
6137 return b'-%s ' % o[0:1]
6138
6138
6139 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6139 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6140 hint = _(b"(try: hg resolve %s%s)\n") % (
6140 hint = _(b"(try: hg resolve %s%s)\n") % (
6141 flags,
6141 flags,
6142 b' '.join(pats),
6142 b' '.join(pats),
6143 )
6143 )
6144 break
6144 break
6145 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6145 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6146 if hint:
6146 if hint:
6147 ui.warn(hint)
6147 ui.warn(hint)
6148 elif ms.mergedriver and ms.mdstate() != b's':
6148 elif ms.mergedriver and ms.mdstate() != b's':
6149 # run conclude step when either a driver-resolved file is requested
6149 # run conclude step when either a driver-resolved file is requested
6150 # or there are no driver-resolved files
6150 # or there are no driver-resolved files
6151 # we can't use 'ret' to determine whether any files are unresolved
6151 # we can't use 'ret' to determine whether any files are unresolved
6152 # because we might not have tried to resolve some
6152 # because we might not have tried to resolve some
6153 if (runconclude or not list(ms.driverresolved())) and not list(
6153 if (runconclude or not list(ms.driverresolved())) and not list(
6154 ms.unresolved()
6154 ms.unresolved()
6155 ):
6155 ):
6156 proceed = mergemod.driverconclude(repo, ms, wctx)
6156 proceed = mergemod.driverconclude(repo, ms, wctx)
6157 ms.commit()
6157 ms.commit()
6158 if not proceed:
6158 if not proceed:
6159 return 1
6159 return 1
6160
6160
6161 # Nudge users into finishing an unfinished operation
6161 # Nudge users into finishing an unfinished operation
6162 unresolvedf = list(ms.unresolved())
6162 unresolvedf = list(ms.unresolved())
6163 driverresolvedf = list(ms.driverresolved())
6163 driverresolvedf = list(ms.driverresolved())
6164 if not unresolvedf and not driverresolvedf:
6164 if not unresolvedf and not driverresolvedf:
6165 ui.status(_(b'(no more unresolved files)\n'))
6165 ui.status(_(b'(no more unresolved files)\n'))
6166 cmdutil.checkafterresolved(repo)
6166 cmdutil.checkafterresolved(repo)
6167 elif not unresolvedf:
6167 elif not unresolvedf:
6168 ui.status(
6168 ui.status(
6169 _(
6169 _(
6170 b'(no more unresolved files -- '
6170 b'(no more unresolved files -- '
6171 b'run "hg resolve --all" to conclude)\n'
6171 b'run "hg resolve --all" to conclude)\n'
6172 )
6172 )
6173 )
6173 )
6174
6174
6175 return ret
6175 return ret
6176
6176
6177
6177
6178 @command(
6178 @command(
6179 b'revert',
6179 b'revert',
6180 [
6180 [
6181 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6181 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6182 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6182 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6183 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6183 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6184 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6184 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6185 (b'i', b'interactive', None, _(b'interactively select the changes')),
6185 (b'i', b'interactive', None, _(b'interactively select the changes')),
6186 ]
6186 ]
6187 + walkopts
6187 + walkopts
6188 + dryrunopts,
6188 + dryrunopts,
6189 _(b'[OPTION]... [-r REV] [NAME]...'),
6189 _(b'[OPTION]... [-r REV] [NAME]...'),
6190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6191 )
6191 )
6192 def revert(ui, repo, *pats, **opts):
6192 def revert(ui, repo, *pats, **opts):
6193 """restore files to their checkout state
6193 """restore files to their checkout state
6194
6194
6195 .. note::
6195 .. note::
6196
6196
6197 To check out earlier revisions, you should use :hg:`update REV`.
6197 To check out earlier revisions, you should use :hg:`update REV`.
6198 To cancel an uncommitted merge (and lose your changes),
6198 To cancel an uncommitted merge (and lose your changes),
6199 use :hg:`merge --abort`.
6199 use :hg:`merge --abort`.
6200
6200
6201 With no revision specified, revert the specified files or directories
6201 With no revision specified, revert the specified files or directories
6202 to the contents they had in the parent of the working directory.
6202 to the contents they had in the parent of the working directory.
6203 This restores the contents of files to an unmodified
6203 This restores the contents of files to an unmodified
6204 state and unschedules adds, removes, copies, and renames. If the
6204 state and unschedules adds, removes, copies, and renames. If the
6205 working directory has two parents, you must explicitly specify a
6205 working directory has two parents, you must explicitly specify a
6206 revision.
6206 revision.
6207
6207
6208 Using the -r/--rev or -d/--date options, revert the given files or
6208 Using the -r/--rev or -d/--date options, revert the given files or
6209 directories to their states as of a specific revision. Because
6209 directories to their states as of a specific revision. Because
6210 revert does not change the working directory parents, this will
6210 revert does not change the working directory parents, this will
6211 cause these files to appear modified. This can be helpful to "back
6211 cause these files to appear modified. This can be helpful to "back
6212 out" some or all of an earlier change. See :hg:`backout` for a
6212 out" some or all of an earlier change. See :hg:`backout` for a
6213 related method.
6213 related method.
6214
6214
6215 Modified files are saved with a .orig suffix before reverting.
6215 Modified files are saved with a .orig suffix before reverting.
6216 To disable these backups, use --no-backup. It is possible to store
6216 To disable these backups, use --no-backup. It is possible to store
6217 the backup files in a custom directory relative to the root of the
6217 the backup files in a custom directory relative to the root of the
6218 repository by setting the ``ui.origbackuppath`` configuration
6218 repository by setting the ``ui.origbackuppath`` configuration
6219 option.
6219 option.
6220
6220
6221 See :hg:`help dates` for a list of formats valid for -d/--date.
6221 See :hg:`help dates` for a list of formats valid for -d/--date.
6222
6222
6223 See :hg:`help backout` for a way to reverse the effect of an
6223 See :hg:`help backout` for a way to reverse the effect of an
6224 earlier changeset.
6224 earlier changeset.
6225
6225
6226 Returns 0 on success.
6226 Returns 0 on success.
6227 """
6227 """
6228
6228
6229 opts = pycompat.byteskwargs(opts)
6229 opts = pycompat.byteskwargs(opts)
6230 if opts.get(b"date"):
6230 if opts.get(b"date"):
6231 if opts.get(b"rev"):
6231 if opts.get(b"rev"):
6232 raise error.Abort(_(b"you can't specify a revision and a date"))
6232 raise error.Abort(_(b"you can't specify a revision and a date"))
6233 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6233 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6234
6234
6235 parent, p2 = repo.dirstate.parents()
6235 parent, p2 = repo.dirstate.parents()
6236 if not opts.get(b'rev') and p2 != nullid:
6236 if not opts.get(b'rev') and p2 != nullid:
6237 # revert after merge is a trap for new users (issue2915)
6237 # revert after merge is a trap for new users (issue2915)
6238 raise error.Abort(
6238 raise error.Abort(
6239 _(b'uncommitted merge with no revision specified'),
6239 _(b'uncommitted merge with no revision specified'),
6240 hint=_(b"use 'hg update' or see 'hg help revert'"),
6240 hint=_(b"use 'hg update' or see 'hg help revert'"),
6241 )
6241 )
6242
6242
6243 rev = opts.get(b'rev')
6243 rev = opts.get(b'rev')
6244 if rev:
6244 if rev:
6245 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6245 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6246 ctx = scmutil.revsingle(repo, rev)
6246 ctx = scmutil.revsingle(repo, rev)
6247
6247
6248 if not (
6248 if not (
6249 pats
6249 pats
6250 or opts.get(b'include')
6250 or opts.get(b'include')
6251 or opts.get(b'exclude')
6251 or opts.get(b'exclude')
6252 or opts.get(b'all')
6252 or opts.get(b'all')
6253 or opts.get(b'interactive')
6253 or opts.get(b'interactive')
6254 ):
6254 ):
6255 msg = _(b"no files or directories specified")
6255 msg = _(b"no files or directories specified")
6256 if p2 != nullid:
6256 if p2 != nullid:
6257 hint = _(
6257 hint = _(
6258 b"uncommitted merge, use --all to discard all changes,"
6258 b"uncommitted merge, use --all to discard all changes,"
6259 b" or 'hg update -C .' to abort the merge"
6259 b" or 'hg update -C .' to abort the merge"
6260 )
6260 )
6261 raise error.Abort(msg, hint=hint)
6261 raise error.Abort(msg, hint=hint)
6262 dirty = any(repo.status())
6262 dirty = any(repo.status())
6263 node = ctx.node()
6263 node = ctx.node()
6264 if node != parent:
6264 if node != parent:
6265 if dirty:
6265 if dirty:
6266 hint = (
6266 hint = (
6267 _(
6267 _(
6268 b"uncommitted changes, use --all to discard all"
6268 b"uncommitted changes, use --all to discard all"
6269 b" changes, or 'hg update %d' to update"
6269 b" changes, or 'hg update %d' to update"
6270 )
6270 )
6271 % ctx.rev()
6271 % ctx.rev()
6272 )
6272 )
6273 else:
6273 else:
6274 hint = (
6274 hint = (
6275 _(
6275 _(
6276 b"use --all to revert all files,"
6276 b"use --all to revert all files,"
6277 b" or 'hg update %d' to update"
6277 b" or 'hg update %d' to update"
6278 )
6278 )
6279 % ctx.rev()
6279 % ctx.rev()
6280 )
6280 )
6281 elif dirty:
6281 elif dirty:
6282 hint = _(b"uncommitted changes, use --all to discard all changes")
6282 hint = _(b"uncommitted changes, use --all to discard all changes")
6283 else:
6283 else:
6284 hint = _(b"use --all to revert all files")
6284 hint = _(b"use --all to revert all files")
6285 raise error.Abort(msg, hint=hint)
6285 raise error.Abort(msg, hint=hint)
6286
6286
6287 return cmdutil.revert(
6287 return cmdutil.revert(
6288 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6288 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6289 )
6289 )
6290
6290
6291
6291
6292 @command(
6292 @command(
6293 b'rollback',
6293 b'rollback',
6294 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6294 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6295 helpcategory=command.CATEGORY_MAINTENANCE,
6295 helpcategory=command.CATEGORY_MAINTENANCE,
6296 )
6296 )
6297 def rollback(ui, repo, **opts):
6297 def rollback(ui, repo, **opts):
6298 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6298 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6299
6299
6300 Please use :hg:`commit --amend` instead of rollback to correct
6300 Please use :hg:`commit --amend` instead of rollback to correct
6301 mistakes in the last commit.
6301 mistakes in the last commit.
6302
6302
6303 This command should be used with care. There is only one level of
6303 This command should be used with care. There is only one level of
6304 rollback, and there is no way to undo a rollback. It will also
6304 rollback, and there is no way to undo a rollback. It will also
6305 restore the dirstate at the time of the last transaction, losing
6305 restore the dirstate at the time of the last transaction, losing
6306 any dirstate changes since that time. This command does not alter
6306 any dirstate changes since that time. This command does not alter
6307 the working directory.
6307 the working directory.
6308
6308
6309 Transactions are used to encapsulate the effects of all commands
6309 Transactions are used to encapsulate the effects of all commands
6310 that create new changesets or propagate existing changesets into a
6310 that create new changesets or propagate existing changesets into a
6311 repository.
6311 repository.
6312
6312
6313 .. container:: verbose
6313 .. container:: verbose
6314
6314
6315 For example, the following commands are transactional, and their
6315 For example, the following commands are transactional, and their
6316 effects can be rolled back:
6316 effects can be rolled back:
6317
6317
6318 - commit
6318 - commit
6319 - import
6319 - import
6320 - pull
6320 - pull
6321 - push (with this repository as the destination)
6321 - push (with this repository as the destination)
6322 - unbundle
6322 - unbundle
6323
6323
6324 To avoid permanent data loss, rollback will refuse to rollback a
6324 To avoid permanent data loss, rollback will refuse to rollback a
6325 commit transaction if it isn't checked out. Use --force to
6325 commit transaction if it isn't checked out. Use --force to
6326 override this protection.
6326 override this protection.
6327
6327
6328 The rollback command can be entirely disabled by setting the
6328 The rollback command can be entirely disabled by setting the
6329 ``ui.rollback`` configuration setting to false. If you're here
6329 ``ui.rollback`` configuration setting to false. If you're here
6330 because you want to use rollback and it's disabled, you can
6330 because you want to use rollback and it's disabled, you can
6331 re-enable the command by setting ``ui.rollback`` to true.
6331 re-enable the command by setting ``ui.rollback`` to true.
6332
6332
6333 This command is not intended for use on public repositories. Once
6333 This command is not intended for use on public repositories. Once
6334 changes are visible for pull by other users, rolling a transaction
6334 changes are visible for pull by other users, rolling a transaction
6335 back locally is ineffective (someone else may already have pulled
6335 back locally is ineffective (someone else may already have pulled
6336 the changes). Furthermore, a race is possible with readers of the
6336 the changes). Furthermore, a race is possible with readers of the
6337 repository; for example an in-progress pull from the repository
6337 repository; for example an in-progress pull from the repository
6338 may fail if a rollback is performed.
6338 may fail if a rollback is performed.
6339
6339
6340 Returns 0 on success, 1 if no rollback data is available.
6340 Returns 0 on success, 1 if no rollback data is available.
6341 """
6341 """
6342 if not ui.configbool(b'ui', b'rollback'):
6342 if not ui.configbool(b'ui', b'rollback'):
6343 raise error.Abort(
6343 raise error.Abort(
6344 _(b'rollback is disabled because it is unsafe'),
6344 _(b'rollback is disabled because it is unsafe'),
6345 hint=b'see `hg help -v rollback` for information',
6345 hint=b'see `hg help -v rollback` for information',
6346 )
6346 )
6347 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6347 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6348
6348
6349
6349
6350 @command(
6350 @command(
6351 b'root',
6351 b'root',
6352 [] + formatteropts,
6352 [] + formatteropts,
6353 intents={INTENT_READONLY},
6353 intents={INTENT_READONLY},
6354 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6354 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6355 )
6355 )
6356 def root(ui, repo, **opts):
6356 def root(ui, repo, **opts):
6357 """print the root (top) of the current working directory
6357 """print the root (top) of the current working directory
6358
6358
6359 Print the root directory of the current repository.
6359 Print the root directory of the current repository.
6360
6360
6361 .. container:: verbose
6361 .. container:: verbose
6362
6362
6363 Template:
6363 Template:
6364
6364
6365 The following keywords are supported in addition to the common template
6365 The following keywords are supported in addition to the common template
6366 keywords and functions. See also :hg:`help templates`.
6366 keywords and functions. See also :hg:`help templates`.
6367
6367
6368 :hgpath: String. Path to the .hg directory.
6368 :hgpath: String. Path to the .hg directory.
6369 :storepath: String. Path to the directory holding versioned data.
6369 :storepath: String. Path to the directory holding versioned data.
6370
6370
6371 Returns 0 on success.
6371 Returns 0 on success.
6372 """
6372 """
6373 opts = pycompat.byteskwargs(opts)
6373 opts = pycompat.byteskwargs(opts)
6374 with ui.formatter(b'root', opts) as fm:
6374 with ui.formatter(b'root', opts) as fm:
6375 fm.startitem()
6375 fm.startitem()
6376 fm.write(b'reporoot', b'%s\n', repo.root)
6376 fm.write(b'reporoot', b'%s\n', repo.root)
6377 fm.data(hgpath=repo.path, storepath=repo.spath)
6377 fm.data(hgpath=repo.path, storepath=repo.spath)
6378
6378
6379
6379
6380 @command(
6380 @command(
6381 b'serve',
6381 b'serve',
6382 [
6382 [
6383 (
6383 (
6384 b'A',
6384 b'A',
6385 b'accesslog',
6385 b'accesslog',
6386 b'',
6386 b'',
6387 _(b'name of access log file to write to'),
6387 _(b'name of access log file to write to'),
6388 _(b'FILE'),
6388 _(b'FILE'),
6389 ),
6389 ),
6390 (b'd', b'daemon', None, _(b'run server in background')),
6390 (b'd', b'daemon', None, _(b'run server in background')),
6391 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6391 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6392 (
6392 (
6393 b'E',
6393 b'E',
6394 b'errorlog',
6394 b'errorlog',
6395 b'',
6395 b'',
6396 _(b'name of error log file to write to'),
6396 _(b'name of error log file to write to'),
6397 _(b'FILE'),
6397 _(b'FILE'),
6398 ),
6398 ),
6399 # use string type, then we can check if something was passed
6399 # use string type, then we can check if something was passed
6400 (
6400 (
6401 b'p',
6401 b'p',
6402 b'port',
6402 b'port',
6403 b'',
6403 b'',
6404 _(b'port to listen on (default: 8000)'),
6404 _(b'port to listen on (default: 8000)'),
6405 _(b'PORT'),
6405 _(b'PORT'),
6406 ),
6406 ),
6407 (
6407 (
6408 b'a',
6408 b'a',
6409 b'address',
6409 b'address',
6410 b'',
6410 b'',
6411 _(b'address to listen on (default: all interfaces)'),
6411 _(b'address to listen on (default: all interfaces)'),
6412 _(b'ADDR'),
6412 _(b'ADDR'),
6413 ),
6413 ),
6414 (
6414 (
6415 b'',
6415 b'',
6416 b'prefix',
6416 b'prefix',
6417 b'',
6417 b'',
6418 _(b'prefix path to serve from (default: server root)'),
6418 _(b'prefix path to serve from (default: server root)'),
6419 _(b'PREFIX'),
6419 _(b'PREFIX'),
6420 ),
6420 ),
6421 (
6421 (
6422 b'n',
6422 b'n',
6423 b'name',
6423 b'name',
6424 b'',
6424 b'',
6425 _(b'name to show in web pages (default: working directory)'),
6425 _(b'name to show in web pages (default: working directory)'),
6426 _(b'NAME'),
6426 _(b'NAME'),
6427 ),
6427 ),
6428 (
6428 (
6429 b'',
6429 b'',
6430 b'web-conf',
6430 b'web-conf',
6431 b'',
6431 b'',
6432 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6432 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6433 _(b'FILE'),
6433 _(b'FILE'),
6434 ),
6434 ),
6435 (
6435 (
6436 b'',
6436 b'',
6437 b'webdir-conf',
6437 b'webdir-conf',
6438 b'',
6438 b'',
6439 _(b'name of the hgweb config file (DEPRECATED)'),
6439 _(b'name of the hgweb config file (DEPRECATED)'),
6440 _(b'FILE'),
6440 _(b'FILE'),
6441 ),
6441 ),
6442 (
6442 (
6443 b'',
6443 b'',
6444 b'pid-file',
6444 b'pid-file',
6445 b'',
6445 b'',
6446 _(b'name of file to write process ID to'),
6446 _(b'name of file to write process ID to'),
6447 _(b'FILE'),
6447 _(b'FILE'),
6448 ),
6448 ),
6449 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6449 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6450 (
6450 (
6451 b'',
6451 b'',
6452 b'cmdserver',
6452 b'cmdserver',
6453 b'',
6453 b'',
6454 _(b'for remote clients (ADVANCED)'),
6454 _(b'for remote clients (ADVANCED)'),
6455 _(b'MODE'),
6455 _(b'MODE'),
6456 ),
6456 ),
6457 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6457 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6458 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6458 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6459 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6459 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6460 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6460 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6461 (b'', b'print-url', None, _(b'start and print only the URL')),
6461 (b'', b'print-url', None, _(b'start and print only the URL')),
6462 ]
6462 ]
6463 + subrepoopts,
6463 + subrepoopts,
6464 _(b'[OPTION]...'),
6464 _(b'[OPTION]...'),
6465 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6465 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6466 helpbasic=True,
6466 helpbasic=True,
6467 optionalrepo=True,
6467 optionalrepo=True,
6468 )
6468 )
6469 def serve(ui, repo, **opts):
6469 def serve(ui, repo, **opts):
6470 """start stand-alone webserver
6470 """start stand-alone webserver
6471
6471
6472 Start a local HTTP repository browser and pull server. You can use
6472 Start a local HTTP repository browser and pull server. You can use
6473 this for ad-hoc sharing and browsing of repositories. It is
6473 this for ad-hoc sharing and browsing of repositories. It is
6474 recommended to use a real web server to serve a repository for
6474 recommended to use a real web server to serve a repository for
6475 longer periods of time.
6475 longer periods of time.
6476
6476
6477 Please note that the server does not implement access control.
6477 Please note that the server does not implement access control.
6478 This means that, by default, anybody can read from the server and
6478 This means that, by default, anybody can read from the server and
6479 nobody can write to it by default. Set the ``web.allow-push``
6479 nobody can write to it by default. Set the ``web.allow-push``
6480 option to ``*`` to allow everybody to push to the server. You
6480 option to ``*`` to allow everybody to push to the server. You
6481 should use a real web server if you need to authenticate users.
6481 should use a real web server if you need to authenticate users.
6482
6482
6483 By default, the server logs accesses to stdout and errors to
6483 By default, the server logs accesses to stdout and errors to
6484 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6484 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6485 files.
6485 files.
6486
6486
6487 To have the server choose a free port number to listen on, specify
6487 To have the server choose a free port number to listen on, specify
6488 a port number of 0; in this case, the server will print the port
6488 a port number of 0; in this case, the server will print the port
6489 number it uses.
6489 number it uses.
6490
6490
6491 Returns 0 on success.
6491 Returns 0 on success.
6492 """
6492 """
6493
6493
6494 opts = pycompat.byteskwargs(opts)
6494 opts = pycompat.byteskwargs(opts)
6495 if opts[b"stdio"] and opts[b"cmdserver"]:
6495 if opts[b"stdio"] and opts[b"cmdserver"]:
6496 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6496 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6497 if opts[b"print_url"] and ui.verbose:
6497 if opts[b"print_url"] and ui.verbose:
6498 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6498 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6499
6499
6500 if opts[b"stdio"]:
6500 if opts[b"stdio"]:
6501 if repo is None:
6501 if repo is None:
6502 raise error.RepoError(
6502 raise error.RepoError(
6503 _(b"there is no Mercurial repository here (.hg not found)")
6503 _(b"there is no Mercurial repository here (.hg not found)")
6504 )
6504 )
6505 s = wireprotoserver.sshserver(ui, repo)
6505 s = wireprotoserver.sshserver(ui, repo)
6506 s.serve_forever()
6506 s.serve_forever()
6507
6507
6508 service = server.createservice(ui, repo, opts)
6508 service = server.createservice(ui, repo, opts)
6509 return server.runservice(opts, initfn=service.init, runfn=service.run)
6509 return server.runservice(opts, initfn=service.init, runfn=service.run)
6510
6510
6511
6511
6512 @command(
6512 @command(
6513 b'shelve',
6513 b'shelve',
6514 [
6514 [
6515 (
6515 (
6516 b'A',
6516 b'A',
6517 b'addremove',
6517 b'addremove',
6518 None,
6518 None,
6519 _(b'mark new/missing files as added/removed before shelving'),
6519 _(b'mark new/missing files as added/removed before shelving'),
6520 ),
6520 ),
6521 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6521 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6522 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6522 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6523 (
6523 (
6524 b'',
6524 b'',
6525 b'date',
6525 b'date',
6526 b'',
6526 b'',
6527 _(b'shelve with the specified commit date'),
6527 _(b'shelve with the specified commit date'),
6528 _(b'DATE'),
6528 _(b'DATE'),
6529 ),
6529 ),
6530 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6530 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6531 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6531 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6532 (
6532 (
6533 b'k',
6533 b'k',
6534 b'keep',
6534 b'keep',
6535 False,
6535 False,
6536 _(b'shelve, but keep changes in the working directory'),
6536 _(b'shelve, but keep changes in the working directory'),
6537 ),
6537 ),
6538 (b'l', b'list', None, _(b'list current shelves')),
6538 (b'l', b'list', None, _(b'list current shelves')),
6539 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6539 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6540 (
6540 (
6541 b'n',
6541 b'n',
6542 b'name',
6542 b'name',
6543 b'',
6543 b'',
6544 _(b'use the given name for the shelved commit'),
6544 _(b'use the given name for the shelved commit'),
6545 _(b'NAME'),
6545 _(b'NAME'),
6546 ),
6546 ),
6547 (
6547 (
6548 b'p',
6548 b'p',
6549 b'patch',
6549 b'patch',
6550 None,
6550 None,
6551 _(
6551 _(
6552 b'output patches for changes (provide the names of the shelved '
6552 b'output patches for changes (provide the names of the shelved '
6553 b'changes as positional arguments)'
6553 b'changes as positional arguments)'
6554 ),
6554 ),
6555 ),
6555 ),
6556 (b'i', b'interactive', None, _(b'interactive mode')),
6556 (b'i', b'interactive', None, _(b'interactive mode')),
6557 (
6557 (
6558 b'',
6558 b'',
6559 b'stat',
6559 b'stat',
6560 None,
6560 None,
6561 _(
6561 _(
6562 b'output diffstat-style summary of changes (provide the names of '
6562 b'output diffstat-style summary of changes (provide the names of '
6563 b'the shelved changes as positional arguments)'
6563 b'the shelved changes as positional arguments)'
6564 ),
6564 ),
6565 ),
6565 ),
6566 ]
6566 ]
6567 + cmdutil.walkopts,
6567 + cmdutil.walkopts,
6568 _(b'hg shelve [OPTION]... [FILE]...'),
6568 _(b'hg shelve [OPTION]... [FILE]...'),
6569 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6569 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6570 )
6570 )
6571 def shelve(ui, repo, *pats, **opts):
6571 def shelve(ui, repo, *pats, **opts):
6572 '''save and set aside changes from the working directory
6572 '''save and set aside changes from the working directory
6573
6573
6574 Shelving takes files that "hg status" reports as not clean, saves
6574 Shelving takes files that "hg status" reports as not clean, saves
6575 the modifications to a bundle (a shelved change), and reverts the
6575 the modifications to a bundle (a shelved change), and reverts the
6576 files so that their state in the working directory becomes clean.
6576 files so that their state in the working directory becomes clean.
6577
6577
6578 To restore these changes to the working directory, using "hg
6578 To restore these changes to the working directory, using "hg
6579 unshelve"; this will work even if you switch to a different
6579 unshelve"; this will work even if you switch to a different
6580 commit.
6580 commit.
6581
6581
6582 When no files are specified, "hg shelve" saves all not-clean
6582 When no files are specified, "hg shelve" saves all not-clean
6583 files. If specific files or directories are named, only changes to
6583 files. If specific files or directories are named, only changes to
6584 those files are shelved.
6584 those files are shelved.
6585
6585
6586 In bare shelve (when no files are specified, without interactive,
6586 In bare shelve (when no files are specified, without interactive,
6587 include and exclude option), shelving remembers information if the
6587 include and exclude option), shelving remembers information if the
6588 working directory was on newly created branch, in other words working
6588 working directory was on newly created branch, in other words working
6589 directory was on different branch than its first parent. In this
6589 directory was on different branch than its first parent. In this
6590 situation unshelving restores branch information to the working directory.
6590 situation unshelving restores branch information to the working directory.
6591
6591
6592 Each shelved change has a name that makes it easier to find later.
6592 Each shelved change has a name that makes it easier to find later.
6593 The name of a shelved change defaults to being based on the active
6593 The name of a shelved change defaults to being based on the active
6594 bookmark, or if there is no active bookmark, the current named
6594 bookmark, or if there is no active bookmark, the current named
6595 branch. To specify a different name, use ``--name``.
6595 branch. To specify a different name, use ``--name``.
6596
6596
6597 To see a list of existing shelved changes, use the ``--list``
6597 To see a list of existing shelved changes, use the ``--list``
6598 option. For each shelved change, this will print its name, age,
6598 option. For each shelved change, this will print its name, age,
6599 and description; use ``--patch`` or ``--stat`` for more details.
6599 and description; use ``--patch`` or ``--stat`` for more details.
6600
6600
6601 To delete specific shelved changes, use ``--delete``. To delete
6601 To delete specific shelved changes, use ``--delete``. To delete
6602 all shelved changes, use ``--cleanup``.
6602 all shelved changes, use ``--cleanup``.
6603 '''
6603 '''
6604 opts = pycompat.byteskwargs(opts)
6604 opts = pycompat.byteskwargs(opts)
6605 allowables = [
6605 allowables = [
6606 (b'addremove', {b'create'}), # 'create' is pseudo action
6606 (b'addremove', {b'create'}), # 'create' is pseudo action
6607 (b'unknown', {b'create'}),
6607 (b'unknown', {b'create'}),
6608 (b'cleanup', {b'cleanup'}),
6608 (b'cleanup', {b'cleanup'}),
6609 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6609 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6610 (b'delete', {b'delete'}),
6610 (b'delete', {b'delete'}),
6611 (b'edit', {b'create'}),
6611 (b'edit', {b'create'}),
6612 (b'keep', {b'create'}),
6612 (b'keep', {b'create'}),
6613 (b'list', {b'list'}),
6613 (b'list', {b'list'}),
6614 (b'message', {b'create'}),
6614 (b'message', {b'create'}),
6615 (b'name', {b'create'}),
6615 (b'name', {b'create'}),
6616 (b'patch', {b'patch', b'list'}),
6616 (b'patch', {b'patch', b'list'}),
6617 (b'stat', {b'stat', b'list'}),
6617 (b'stat', {b'stat', b'list'}),
6618 ]
6618 ]
6619
6619
6620 def checkopt(opt):
6620 def checkopt(opt):
6621 if opts.get(opt):
6621 if opts.get(opt):
6622 for i, allowable in allowables:
6622 for i, allowable in allowables:
6623 if opts[i] and opt not in allowable:
6623 if opts[i] and opt not in allowable:
6624 raise error.Abort(
6624 raise error.Abort(
6625 _(
6625 _(
6626 b"options '--%s' and '--%s' may not be "
6626 b"options '--%s' and '--%s' may not be "
6627 b"used together"
6627 b"used together"
6628 )
6628 )
6629 % (opt, i)
6629 % (opt, i)
6630 )
6630 )
6631 return True
6631 return True
6632
6632
6633 if checkopt(b'cleanup'):
6633 if checkopt(b'cleanup'):
6634 if pats:
6634 if pats:
6635 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6635 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6636 return shelvemod.cleanupcmd(ui, repo)
6636 return shelvemod.cleanupcmd(ui, repo)
6637 elif checkopt(b'delete'):
6637 elif checkopt(b'delete'):
6638 return shelvemod.deletecmd(ui, repo, pats)
6638 return shelvemod.deletecmd(ui, repo, pats)
6639 elif checkopt(b'list'):
6639 elif checkopt(b'list'):
6640 return shelvemod.listcmd(ui, repo, pats, opts)
6640 return shelvemod.listcmd(ui, repo, pats, opts)
6641 elif checkopt(b'patch') or checkopt(b'stat'):
6641 elif checkopt(b'patch') or checkopt(b'stat'):
6642 return shelvemod.patchcmds(ui, repo, pats, opts)
6642 return shelvemod.patchcmds(ui, repo, pats, opts)
6643 else:
6643 else:
6644 return shelvemod.createcmd(ui, repo, pats, opts)
6644 return shelvemod.createcmd(ui, repo, pats, opts)
6645
6645
6646
6646
6647 _NOTTERSE = b'nothing'
6647 _NOTTERSE = b'nothing'
6648
6648
6649
6649
6650 @command(
6650 @command(
6651 b'status|st',
6651 b'status|st',
6652 [
6652 [
6653 (b'A', b'all', None, _(b'show status of all files')),
6653 (b'A', b'all', None, _(b'show status of all files')),
6654 (b'm', b'modified', None, _(b'show only modified files')),
6654 (b'm', b'modified', None, _(b'show only modified files')),
6655 (b'a', b'added', None, _(b'show only added files')),
6655 (b'a', b'added', None, _(b'show only added files')),
6656 (b'r', b'removed', None, _(b'show only removed files')),
6656 (b'r', b'removed', None, _(b'show only removed files')),
6657 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6657 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6658 (b'c', b'clean', None, _(b'show only files without changes')),
6658 (b'c', b'clean', None, _(b'show only files without changes')),
6659 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6659 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6660 (b'i', b'ignored', None, _(b'show only ignored files')),
6660 (b'i', b'ignored', None, _(b'show only ignored files')),
6661 (b'n', b'no-status', None, _(b'hide status prefix')),
6661 (b'n', b'no-status', None, _(b'hide status prefix')),
6662 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6662 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6663 (b'C', b'copies', None, _(b'show source of copied files')),
6663 (b'C', b'copies', None, _(b'show source of copied files')),
6664 (
6664 (
6665 b'0',
6665 b'0',
6666 b'print0',
6666 b'print0',
6667 None,
6667 None,
6668 _(b'end filenames with NUL, for use with xargs'),
6668 _(b'end filenames with NUL, for use with xargs'),
6669 ),
6669 ),
6670 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6670 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6671 (
6671 (
6672 b'',
6672 b'',
6673 b'change',
6673 b'change',
6674 b'',
6674 b'',
6675 _(b'list the changed files of a revision'),
6675 _(b'list the changed files of a revision'),
6676 _(b'REV'),
6676 _(b'REV'),
6677 ),
6677 ),
6678 ]
6678 ]
6679 + walkopts
6679 + walkopts
6680 + subrepoopts
6680 + subrepoopts
6681 + formatteropts,
6681 + formatteropts,
6682 _(b'[OPTION]... [FILE]...'),
6682 _(b'[OPTION]... [FILE]...'),
6683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6684 helpbasic=True,
6684 helpbasic=True,
6685 inferrepo=True,
6685 inferrepo=True,
6686 intents={INTENT_READONLY},
6686 intents={INTENT_READONLY},
6687 )
6687 )
6688 def status(ui, repo, *pats, **opts):
6688 def status(ui, repo, *pats, **opts):
6689 """show changed files in the working directory
6689 """show changed files in the working directory
6690
6690
6691 Show status of files in the repository. If names are given, only
6691 Show status of files in the repository. If names are given, only
6692 files that match are shown. Files that are clean or ignored or
6692 files that match are shown. Files that are clean or ignored or
6693 the source of a copy/move operation, are not listed unless
6693 the source of a copy/move operation, are not listed unless
6694 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6694 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6695 Unless options described with "show only ..." are given, the
6695 Unless options described with "show only ..." are given, the
6696 options -mardu are used.
6696 options -mardu are used.
6697
6697
6698 Option -q/--quiet hides untracked (unknown and ignored) files
6698 Option -q/--quiet hides untracked (unknown and ignored) files
6699 unless explicitly requested with -u/--unknown or -i/--ignored.
6699 unless explicitly requested with -u/--unknown or -i/--ignored.
6700
6700
6701 .. note::
6701 .. note::
6702
6702
6703 :hg:`status` may appear to disagree with diff if permissions have
6703 :hg:`status` may appear to disagree with diff if permissions have
6704 changed or a merge has occurred. The standard diff format does
6704 changed or a merge has occurred. The standard diff format does
6705 not report permission changes and diff only reports changes
6705 not report permission changes and diff only reports changes
6706 relative to one merge parent.
6706 relative to one merge parent.
6707
6707
6708 If one revision is given, it is used as the base revision.
6708 If one revision is given, it is used as the base revision.
6709 If two revisions are given, the differences between them are
6709 If two revisions are given, the differences between them are
6710 shown. The --change option can also be used as a shortcut to list
6710 shown. The --change option can also be used as a shortcut to list
6711 the changed files of a revision from its first parent.
6711 the changed files of a revision from its first parent.
6712
6712
6713 The codes used to show the status of files are::
6713 The codes used to show the status of files are::
6714
6714
6715 M = modified
6715 M = modified
6716 A = added
6716 A = added
6717 R = removed
6717 R = removed
6718 C = clean
6718 C = clean
6719 ! = missing (deleted by non-hg command, but still tracked)
6719 ! = missing (deleted by non-hg command, but still tracked)
6720 ? = not tracked
6720 ? = not tracked
6721 I = ignored
6721 I = ignored
6722 = origin of the previous file (with --copies)
6722 = origin of the previous file (with --copies)
6723
6723
6724 .. container:: verbose
6724 .. container:: verbose
6725
6725
6726 The -t/--terse option abbreviates the output by showing only the directory
6726 The -t/--terse option abbreviates the output by showing only the directory
6727 name if all the files in it share the same status. The option takes an
6727 name if all the files in it share the same status. The option takes an
6728 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6728 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6729 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6729 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6730 for 'ignored' and 'c' for clean.
6730 for 'ignored' and 'c' for clean.
6731
6731
6732 It abbreviates only those statuses which are passed. Note that clean and
6732 It abbreviates only those statuses which are passed. Note that clean and
6733 ignored files are not displayed with '--terse ic' unless the -c/--clean
6733 ignored files are not displayed with '--terse ic' unless the -c/--clean
6734 and -i/--ignored options are also used.
6734 and -i/--ignored options are also used.
6735
6735
6736 The -v/--verbose option shows information when the repository is in an
6736 The -v/--verbose option shows information when the repository is in an
6737 unfinished merge, shelve, rebase state etc. You can have this behavior
6737 unfinished merge, shelve, rebase state etc. You can have this behavior
6738 turned on by default by enabling the ``commands.status.verbose`` option.
6738 turned on by default by enabling the ``commands.status.verbose`` option.
6739
6739
6740 You can skip displaying some of these states by setting
6740 You can skip displaying some of these states by setting
6741 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6741 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6742 'histedit', 'merge', 'rebase', or 'unshelve'.
6742 'histedit', 'merge', 'rebase', or 'unshelve'.
6743
6743
6744 Template:
6744 Template:
6745
6745
6746 The following keywords are supported in addition to the common template
6746 The following keywords are supported in addition to the common template
6747 keywords and functions. See also :hg:`help templates`.
6747 keywords and functions. See also :hg:`help templates`.
6748
6748
6749 :path: String. Repository-absolute path of the file.
6749 :path: String. Repository-absolute path of the file.
6750 :source: String. Repository-absolute path of the file originated from.
6750 :source: String. Repository-absolute path of the file originated from.
6751 Available if ``--copies`` is specified.
6751 Available if ``--copies`` is specified.
6752 :status: String. Character denoting file's status.
6752 :status: String. Character denoting file's status.
6753
6753
6754 Examples:
6754 Examples:
6755
6755
6756 - show changes in the working directory relative to a
6756 - show changes in the working directory relative to a
6757 changeset::
6757 changeset::
6758
6758
6759 hg status --rev 9353
6759 hg status --rev 9353
6760
6760
6761 - show changes in the working directory relative to the
6761 - show changes in the working directory relative to the
6762 current directory (see :hg:`help patterns` for more information)::
6762 current directory (see :hg:`help patterns` for more information)::
6763
6763
6764 hg status re:
6764 hg status re:
6765
6765
6766 - show all changes including copies in an existing changeset::
6766 - show all changes including copies in an existing changeset::
6767
6767
6768 hg status --copies --change 9353
6768 hg status --copies --change 9353
6769
6769
6770 - get a NUL separated list of added files, suitable for xargs::
6770 - get a NUL separated list of added files, suitable for xargs::
6771
6771
6772 hg status -an0
6772 hg status -an0
6773
6773
6774 - show more information about the repository status, abbreviating
6774 - show more information about the repository status, abbreviating
6775 added, removed, modified, deleted, and untracked paths::
6775 added, removed, modified, deleted, and untracked paths::
6776
6776
6777 hg status -v -t mardu
6777 hg status -v -t mardu
6778
6778
6779 Returns 0 on success.
6779 Returns 0 on success.
6780
6780
6781 """
6781 """
6782
6782
6783 opts = pycompat.byteskwargs(opts)
6783 opts = pycompat.byteskwargs(opts)
6784 revs = opts.get(b'rev')
6784 revs = opts.get(b'rev')
6785 change = opts.get(b'change')
6785 change = opts.get(b'change')
6786 terse = opts.get(b'terse')
6786 terse = opts.get(b'terse')
6787 if terse is _NOTTERSE:
6787 if terse is _NOTTERSE:
6788 if revs:
6788 if revs:
6789 terse = b''
6789 terse = b''
6790 else:
6790 else:
6791 terse = ui.config(b'commands', b'status.terse')
6791 terse = ui.config(b'commands', b'status.terse')
6792
6792
6793 if revs and change:
6793 if revs and change:
6794 msg = _(b'cannot specify --rev and --change at the same time')
6794 msg = _(b'cannot specify --rev and --change at the same time')
6795 raise error.Abort(msg)
6795 raise error.Abort(msg)
6796 elif revs and terse:
6796 elif revs and terse:
6797 msg = _(b'cannot use --terse with --rev')
6797 msg = _(b'cannot use --terse with --rev')
6798 raise error.Abort(msg)
6798 raise error.Abort(msg)
6799 elif change:
6799 elif change:
6800 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6800 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6801 ctx2 = scmutil.revsingle(repo, change, None)
6801 ctx2 = scmutil.revsingle(repo, change, None)
6802 ctx1 = ctx2.p1()
6802 ctx1 = ctx2.p1()
6803 else:
6803 else:
6804 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6804 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6805 ctx1, ctx2 = scmutil.revpair(repo, revs)
6805 ctx1, ctx2 = scmutil.revpair(repo, revs)
6806
6806
6807 forcerelativevalue = None
6807 forcerelativevalue = None
6808 if ui.hasconfig(b'commands', b'status.relative'):
6808 if ui.hasconfig(b'commands', b'status.relative'):
6809 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6809 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6810 uipathfn = scmutil.getuipathfn(
6810 uipathfn = scmutil.getuipathfn(
6811 repo,
6811 repo,
6812 legacyrelativevalue=bool(pats),
6812 legacyrelativevalue=bool(pats),
6813 forcerelativevalue=forcerelativevalue,
6813 forcerelativevalue=forcerelativevalue,
6814 )
6814 )
6815
6815
6816 if opts.get(b'print0'):
6816 if opts.get(b'print0'):
6817 end = b'\0'
6817 end = b'\0'
6818 else:
6818 else:
6819 end = b'\n'
6819 end = b'\n'
6820 states = b'modified added removed deleted unknown ignored clean'.split()
6820 states = b'modified added removed deleted unknown ignored clean'.split()
6821 show = [k for k in states if opts.get(k)]
6821 show = [k for k in states if opts.get(k)]
6822 if opts.get(b'all'):
6822 if opts.get(b'all'):
6823 show += ui.quiet and (states[:4] + [b'clean']) or states
6823 show += ui.quiet and (states[:4] + [b'clean']) or states
6824
6824
6825 if not show:
6825 if not show:
6826 if ui.quiet:
6826 if ui.quiet:
6827 show = states[:4]
6827 show = states[:4]
6828 else:
6828 else:
6829 show = states[:5]
6829 show = states[:5]
6830
6830
6831 m = scmutil.match(ctx2, pats, opts)
6831 m = scmutil.match(ctx2, pats, opts)
6832 if terse:
6832 if terse:
6833 # we need to compute clean and unknown to terse
6833 # we need to compute clean and unknown to terse
6834 stat = repo.status(
6834 stat = repo.status(
6835 ctx1.node(),
6835 ctx1.node(),
6836 ctx2.node(),
6836 ctx2.node(),
6837 m,
6837 m,
6838 b'ignored' in show or b'i' in terse,
6838 b'ignored' in show or b'i' in terse,
6839 clean=True,
6839 clean=True,
6840 unknown=True,
6840 unknown=True,
6841 listsubrepos=opts.get(b'subrepos'),
6841 listsubrepos=opts.get(b'subrepos'),
6842 )
6842 )
6843
6843
6844 stat = cmdutil.tersedir(stat, terse)
6844 stat = cmdutil.tersedir(stat, terse)
6845 else:
6845 else:
6846 stat = repo.status(
6846 stat = repo.status(
6847 ctx1.node(),
6847 ctx1.node(),
6848 ctx2.node(),
6848 ctx2.node(),
6849 m,
6849 m,
6850 b'ignored' in show,
6850 b'ignored' in show,
6851 b'clean' in show,
6851 b'clean' in show,
6852 b'unknown' in show,
6852 b'unknown' in show,
6853 opts.get(b'subrepos'),
6853 opts.get(b'subrepos'),
6854 )
6854 )
6855
6855
6856 changestates = zip(
6856 changestates = zip(
6857 states,
6857 states,
6858 pycompat.iterbytestr(b'MAR!?IC'),
6858 pycompat.iterbytestr(b'MAR!?IC'),
6859 [getattr(stat, s.decode('utf8')) for s in states],
6859 [getattr(stat, s.decode('utf8')) for s in states],
6860 )
6860 )
6861
6861
6862 copy = {}
6862 copy = {}
6863 if (
6863 if (
6864 opts.get(b'all')
6864 opts.get(b'all')
6865 or opts.get(b'copies')
6865 or opts.get(b'copies')
6866 or ui.configbool(b'ui', b'statuscopies')
6866 or ui.configbool(b'ui', b'statuscopies')
6867 ) and not opts.get(b'no_status'):
6867 ) and not opts.get(b'no_status'):
6868 copy = copies.pathcopies(ctx1, ctx2, m)
6868 copy = copies.pathcopies(ctx1, ctx2, m)
6869
6869
6870 morestatus = None
6870 morestatus = None
6871 if (
6871 if (
6872 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6872 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6873 ) and not ui.plain():
6873 ) and not ui.plain():
6874 morestatus = cmdutil.readmorestatus(repo)
6874 morestatus = cmdutil.readmorestatus(repo)
6875
6875
6876 ui.pager(b'status')
6876 ui.pager(b'status')
6877 fm = ui.formatter(b'status', opts)
6877 fm = ui.formatter(b'status', opts)
6878 fmt = b'%s' + end
6878 fmt = b'%s' + end
6879 showchar = not opts.get(b'no_status')
6879 showchar = not opts.get(b'no_status')
6880
6880
6881 for state, char, files in changestates:
6881 for state, char, files in changestates:
6882 if state in show:
6882 if state in show:
6883 label = b'status.' + state
6883 label = b'status.' + state
6884 for f in files:
6884 for f in files:
6885 fm.startitem()
6885 fm.startitem()
6886 fm.context(ctx=ctx2)
6886 fm.context(ctx=ctx2)
6887 fm.data(path=f)
6887 fm.data(itemtype=b'file', path=f)
6888 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6888 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6889 fm.plain(fmt % uipathfn(f), label=label)
6889 fm.plain(fmt % uipathfn(f), label=label)
6890 if f in copy:
6890 if f in copy:
6891 fm.data(source=copy[f])
6891 fm.data(source=copy[f])
6892 fm.plain(
6892 fm.plain(
6893 (b' %s' + end) % uipathfn(copy[f]),
6893 (b' %s' + end) % uipathfn(copy[f]),
6894 label=b'status.copied',
6894 label=b'status.copied',
6895 )
6895 )
6896 if morestatus:
6896 if morestatus:
6897 morestatus.formatfile(f, fm)
6897 morestatus.formatfile(f, fm)
6898
6898
6899 if morestatus:
6899 if morestatus:
6900 morestatus.formatfooter(fm)
6900 morestatus.formatfooter(fm)
6901 fm.end()
6901 fm.end()
6902
6902
6903
6903
6904 @command(
6904 @command(
6905 b'summary|sum',
6905 b'summary|sum',
6906 [(b'', b'remote', None, _(b'check for push and pull'))],
6906 [(b'', b'remote', None, _(b'check for push and pull'))],
6907 b'[--remote]',
6907 b'[--remote]',
6908 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6908 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6909 helpbasic=True,
6909 helpbasic=True,
6910 intents={INTENT_READONLY},
6910 intents={INTENT_READONLY},
6911 )
6911 )
6912 def summary(ui, repo, **opts):
6912 def summary(ui, repo, **opts):
6913 """summarize working directory state
6913 """summarize working directory state
6914
6914
6915 This generates a brief summary of the working directory state,
6915 This generates a brief summary of the working directory state,
6916 including parents, branch, commit status, phase and available updates.
6916 including parents, branch, commit status, phase and available updates.
6917
6917
6918 With the --remote option, this will check the default paths for
6918 With the --remote option, this will check the default paths for
6919 incoming and outgoing changes. This can be time-consuming.
6919 incoming and outgoing changes. This can be time-consuming.
6920
6920
6921 Returns 0 on success.
6921 Returns 0 on success.
6922 """
6922 """
6923
6923
6924 opts = pycompat.byteskwargs(opts)
6924 opts = pycompat.byteskwargs(opts)
6925 ui.pager(b'summary')
6925 ui.pager(b'summary')
6926 ctx = repo[None]
6926 ctx = repo[None]
6927 parents = ctx.parents()
6927 parents = ctx.parents()
6928 pnode = parents[0].node()
6928 pnode = parents[0].node()
6929 marks = []
6929 marks = []
6930
6930
6931 try:
6931 try:
6932 ms = mergemod.mergestate.read(repo)
6932 ms = mergemod.mergestate.read(repo)
6933 except error.UnsupportedMergeRecords as e:
6933 except error.UnsupportedMergeRecords as e:
6934 s = b' '.join(e.recordtypes)
6934 s = b' '.join(e.recordtypes)
6935 ui.warn(
6935 ui.warn(
6936 _(b'warning: merge state has unsupported record types: %s\n') % s
6936 _(b'warning: merge state has unsupported record types: %s\n') % s
6937 )
6937 )
6938 unresolved = []
6938 unresolved = []
6939 else:
6939 else:
6940 unresolved = list(ms.unresolved())
6940 unresolved = list(ms.unresolved())
6941
6941
6942 for p in parents:
6942 for p in parents:
6943 # label with log.changeset (instead of log.parent) since this
6943 # label with log.changeset (instead of log.parent) since this
6944 # shows a working directory parent *changeset*:
6944 # shows a working directory parent *changeset*:
6945 # i18n: column positioning for "hg summary"
6945 # i18n: column positioning for "hg summary"
6946 ui.write(
6946 ui.write(
6947 _(b'parent: %d:%s ') % (p.rev(), p),
6947 _(b'parent: %d:%s ') % (p.rev(), p),
6948 label=logcmdutil.changesetlabels(p),
6948 label=logcmdutil.changesetlabels(p),
6949 )
6949 )
6950 ui.write(b' '.join(p.tags()), label=b'log.tag')
6950 ui.write(b' '.join(p.tags()), label=b'log.tag')
6951 if p.bookmarks():
6951 if p.bookmarks():
6952 marks.extend(p.bookmarks())
6952 marks.extend(p.bookmarks())
6953 if p.rev() == -1:
6953 if p.rev() == -1:
6954 if not len(repo):
6954 if not len(repo):
6955 ui.write(_(b' (empty repository)'))
6955 ui.write(_(b' (empty repository)'))
6956 else:
6956 else:
6957 ui.write(_(b' (no revision checked out)'))
6957 ui.write(_(b' (no revision checked out)'))
6958 if p.obsolete():
6958 if p.obsolete():
6959 ui.write(_(b' (obsolete)'))
6959 ui.write(_(b' (obsolete)'))
6960 if p.isunstable():
6960 if p.isunstable():
6961 instabilities = (
6961 instabilities = (
6962 ui.label(instability, b'trouble.%s' % instability)
6962 ui.label(instability, b'trouble.%s' % instability)
6963 for instability in p.instabilities()
6963 for instability in p.instabilities()
6964 )
6964 )
6965 ui.write(b' (' + b', '.join(instabilities) + b')')
6965 ui.write(b' (' + b', '.join(instabilities) + b')')
6966 ui.write(b'\n')
6966 ui.write(b'\n')
6967 if p.description():
6967 if p.description():
6968 ui.status(
6968 ui.status(
6969 b' ' + p.description().splitlines()[0].strip() + b'\n',
6969 b' ' + p.description().splitlines()[0].strip() + b'\n',
6970 label=b'log.summary',
6970 label=b'log.summary',
6971 )
6971 )
6972
6972
6973 branch = ctx.branch()
6973 branch = ctx.branch()
6974 bheads = repo.branchheads(branch)
6974 bheads = repo.branchheads(branch)
6975 # i18n: column positioning for "hg summary"
6975 # i18n: column positioning for "hg summary"
6976 m = _(b'branch: %s\n') % branch
6976 m = _(b'branch: %s\n') % branch
6977 if branch != b'default':
6977 if branch != b'default':
6978 ui.write(m, label=b'log.branch')
6978 ui.write(m, label=b'log.branch')
6979 else:
6979 else:
6980 ui.status(m, label=b'log.branch')
6980 ui.status(m, label=b'log.branch')
6981
6981
6982 if marks:
6982 if marks:
6983 active = repo._activebookmark
6983 active = repo._activebookmark
6984 # i18n: column positioning for "hg summary"
6984 # i18n: column positioning for "hg summary"
6985 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6985 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6986 if active is not None:
6986 if active is not None:
6987 if active in marks:
6987 if active in marks:
6988 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6988 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6989 marks.remove(active)
6989 marks.remove(active)
6990 else:
6990 else:
6991 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6991 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6992 for m in marks:
6992 for m in marks:
6993 ui.write(b' ' + m, label=b'log.bookmark')
6993 ui.write(b' ' + m, label=b'log.bookmark')
6994 ui.write(b'\n', label=b'log.bookmark')
6994 ui.write(b'\n', label=b'log.bookmark')
6995
6995
6996 status = repo.status(unknown=True)
6996 status = repo.status(unknown=True)
6997
6997
6998 c = repo.dirstate.copies()
6998 c = repo.dirstate.copies()
6999 copied, renamed = [], []
6999 copied, renamed = [], []
7000 for d, s in pycompat.iteritems(c):
7000 for d, s in pycompat.iteritems(c):
7001 if s in status.removed:
7001 if s in status.removed:
7002 status.removed.remove(s)
7002 status.removed.remove(s)
7003 renamed.append(d)
7003 renamed.append(d)
7004 else:
7004 else:
7005 copied.append(d)
7005 copied.append(d)
7006 if d in status.added:
7006 if d in status.added:
7007 status.added.remove(d)
7007 status.added.remove(d)
7008
7008
7009 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7009 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7010
7010
7011 labels = [
7011 labels = [
7012 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7012 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7013 (ui.label(_(b'%d added'), b'status.added'), status.added),
7013 (ui.label(_(b'%d added'), b'status.added'), status.added),
7014 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7014 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7015 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7015 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7016 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7016 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7017 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7017 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7018 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7018 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7019 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7019 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7020 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7020 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7021 ]
7021 ]
7022 t = []
7022 t = []
7023 for l, s in labels:
7023 for l, s in labels:
7024 if s:
7024 if s:
7025 t.append(l % len(s))
7025 t.append(l % len(s))
7026
7026
7027 t = b', '.join(t)
7027 t = b', '.join(t)
7028 cleanworkdir = False
7028 cleanworkdir = False
7029
7029
7030 if repo.vfs.exists(b'graftstate'):
7030 if repo.vfs.exists(b'graftstate'):
7031 t += _(b' (graft in progress)')
7031 t += _(b' (graft in progress)')
7032 if repo.vfs.exists(b'updatestate'):
7032 if repo.vfs.exists(b'updatestate'):
7033 t += _(b' (interrupted update)')
7033 t += _(b' (interrupted update)')
7034 elif len(parents) > 1:
7034 elif len(parents) > 1:
7035 t += _(b' (merge)')
7035 t += _(b' (merge)')
7036 elif branch != parents[0].branch():
7036 elif branch != parents[0].branch():
7037 t += _(b' (new branch)')
7037 t += _(b' (new branch)')
7038 elif parents[0].closesbranch() and pnode in repo.branchheads(
7038 elif parents[0].closesbranch() and pnode in repo.branchheads(
7039 branch, closed=True
7039 branch, closed=True
7040 ):
7040 ):
7041 t += _(b' (head closed)')
7041 t += _(b' (head closed)')
7042 elif not (
7042 elif not (
7043 status.modified
7043 status.modified
7044 or status.added
7044 or status.added
7045 or status.removed
7045 or status.removed
7046 or renamed
7046 or renamed
7047 or copied
7047 or copied
7048 or subs
7048 or subs
7049 ):
7049 ):
7050 t += _(b' (clean)')
7050 t += _(b' (clean)')
7051 cleanworkdir = True
7051 cleanworkdir = True
7052 elif pnode not in bheads:
7052 elif pnode not in bheads:
7053 t += _(b' (new branch head)')
7053 t += _(b' (new branch head)')
7054
7054
7055 if parents:
7055 if parents:
7056 pendingphase = max(p.phase() for p in parents)
7056 pendingphase = max(p.phase() for p in parents)
7057 else:
7057 else:
7058 pendingphase = phases.public
7058 pendingphase = phases.public
7059
7059
7060 if pendingphase > phases.newcommitphase(ui):
7060 if pendingphase > phases.newcommitphase(ui):
7061 t += b' (%s)' % phases.phasenames[pendingphase]
7061 t += b' (%s)' % phases.phasenames[pendingphase]
7062
7062
7063 if cleanworkdir:
7063 if cleanworkdir:
7064 # i18n: column positioning for "hg summary"
7064 # i18n: column positioning for "hg summary"
7065 ui.status(_(b'commit: %s\n') % t.strip())
7065 ui.status(_(b'commit: %s\n') % t.strip())
7066 else:
7066 else:
7067 # i18n: column positioning for "hg summary"
7067 # i18n: column positioning for "hg summary"
7068 ui.write(_(b'commit: %s\n') % t.strip())
7068 ui.write(_(b'commit: %s\n') % t.strip())
7069
7069
7070 # all ancestors of branch heads - all ancestors of parent = new csets
7070 # all ancestors of branch heads - all ancestors of parent = new csets
7071 new = len(
7071 new = len(
7072 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7072 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7073 )
7073 )
7074
7074
7075 if new == 0:
7075 if new == 0:
7076 # i18n: column positioning for "hg summary"
7076 # i18n: column positioning for "hg summary"
7077 ui.status(_(b'update: (current)\n'))
7077 ui.status(_(b'update: (current)\n'))
7078 elif pnode not in bheads:
7078 elif pnode not in bheads:
7079 # i18n: column positioning for "hg summary"
7079 # i18n: column positioning for "hg summary"
7080 ui.write(_(b'update: %d new changesets (update)\n') % new)
7080 ui.write(_(b'update: %d new changesets (update)\n') % new)
7081 else:
7081 else:
7082 # i18n: column positioning for "hg summary"
7082 # i18n: column positioning for "hg summary"
7083 ui.write(
7083 ui.write(
7084 _(b'update: %d new changesets, %d branch heads (merge)\n')
7084 _(b'update: %d new changesets, %d branch heads (merge)\n')
7085 % (new, len(bheads))
7085 % (new, len(bheads))
7086 )
7086 )
7087
7087
7088 t = []
7088 t = []
7089 draft = len(repo.revs(b'draft()'))
7089 draft = len(repo.revs(b'draft()'))
7090 if draft:
7090 if draft:
7091 t.append(_(b'%d draft') % draft)
7091 t.append(_(b'%d draft') % draft)
7092 secret = len(repo.revs(b'secret()'))
7092 secret = len(repo.revs(b'secret()'))
7093 if secret:
7093 if secret:
7094 t.append(_(b'%d secret') % secret)
7094 t.append(_(b'%d secret') % secret)
7095
7095
7096 if draft or secret:
7096 if draft or secret:
7097 ui.status(_(b'phases: %s\n') % b', '.join(t))
7097 ui.status(_(b'phases: %s\n') % b', '.join(t))
7098
7098
7099 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7099 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7100 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7100 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7101 numtrouble = len(repo.revs(trouble + b"()"))
7101 numtrouble = len(repo.revs(trouble + b"()"))
7102 # We write all the possibilities to ease translation
7102 # We write all the possibilities to ease translation
7103 troublemsg = {
7103 troublemsg = {
7104 b"orphan": _(b"orphan: %d changesets"),
7104 b"orphan": _(b"orphan: %d changesets"),
7105 b"contentdivergent": _(b"content-divergent: %d changesets"),
7105 b"contentdivergent": _(b"content-divergent: %d changesets"),
7106 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7106 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7107 }
7107 }
7108 if numtrouble > 0:
7108 if numtrouble > 0:
7109 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7109 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7110
7110
7111 cmdutil.summaryhooks(ui, repo)
7111 cmdutil.summaryhooks(ui, repo)
7112
7112
7113 if opts.get(b'remote'):
7113 if opts.get(b'remote'):
7114 needsincoming, needsoutgoing = True, True
7114 needsincoming, needsoutgoing = True, True
7115 else:
7115 else:
7116 needsincoming, needsoutgoing = False, False
7116 needsincoming, needsoutgoing = False, False
7117 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7117 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7118 if i:
7118 if i:
7119 needsincoming = True
7119 needsincoming = True
7120 if o:
7120 if o:
7121 needsoutgoing = True
7121 needsoutgoing = True
7122 if not needsincoming and not needsoutgoing:
7122 if not needsincoming and not needsoutgoing:
7123 return
7123 return
7124
7124
7125 def getincoming():
7125 def getincoming():
7126 source, branches = hg.parseurl(ui.expandpath(b'default'))
7126 source, branches = hg.parseurl(ui.expandpath(b'default'))
7127 sbranch = branches[0]
7127 sbranch = branches[0]
7128 try:
7128 try:
7129 other = hg.peer(repo, {}, source)
7129 other = hg.peer(repo, {}, source)
7130 except error.RepoError:
7130 except error.RepoError:
7131 if opts.get(b'remote'):
7131 if opts.get(b'remote'):
7132 raise
7132 raise
7133 return source, sbranch, None, None, None
7133 return source, sbranch, None, None, None
7134 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7134 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7135 if revs:
7135 if revs:
7136 revs = [other.lookup(rev) for rev in revs]
7136 revs = [other.lookup(rev) for rev in revs]
7137 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7137 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7138 repo.ui.pushbuffer()
7138 repo.ui.pushbuffer()
7139 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7139 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7140 repo.ui.popbuffer()
7140 repo.ui.popbuffer()
7141 return source, sbranch, other, commoninc, commoninc[1]
7141 return source, sbranch, other, commoninc, commoninc[1]
7142
7142
7143 if needsincoming:
7143 if needsincoming:
7144 source, sbranch, sother, commoninc, incoming = getincoming()
7144 source, sbranch, sother, commoninc, incoming = getincoming()
7145 else:
7145 else:
7146 source = sbranch = sother = commoninc = incoming = None
7146 source = sbranch = sother = commoninc = incoming = None
7147
7147
7148 def getoutgoing():
7148 def getoutgoing():
7149 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7149 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7150 dbranch = branches[0]
7150 dbranch = branches[0]
7151 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7151 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7152 if source != dest:
7152 if source != dest:
7153 try:
7153 try:
7154 dother = hg.peer(repo, {}, dest)
7154 dother = hg.peer(repo, {}, dest)
7155 except error.RepoError:
7155 except error.RepoError:
7156 if opts.get(b'remote'):
7156 if opts.get(b'remote'):
7157 raise
7157 raise
7158 return dest, dbranch, None, None
7158 return dest, dbranch, None, None
7159 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7159 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7160 elif sother is None:
7160 elif sother is None:
7161 # there is no explicit destination peer, but source one is invalid
7161 # there is no explicit destination peer, but source one is invalid
7162 return dest, dbranch, None, None
7162 return dest, dbranch, None, None
7163 else:
7163 else:
7164 dother = sother
7164 dother = sother
7165 if source != dest or (sbranch is not None and sbranch != dbranch):
7165 if source != dest or (sbranch is not None and sbranch != dbranch):
7166 common = None
7166 common = None
7167 else:
7167 else:
7168 common = commoninc
7168 common = commoninc
7169 if revs:
7169 if revs:
7170 revs = [repo.lookup(rev) for rev in revs]
7170 revs = [repo.lookup(rev) for rev in revs]
7171 repo.ui.pushbuffer()
7171 repo.ui.pushbuffer()
7172 outgoing = discovery.findcommonoutgoing(
7172 outgoing = discovery.findcommonoutgoing(
7173 repo, dother, onlyheads=revs, commoninc=common
7173 repo, dother, onlyheads=revs, commoninc=common
7174 )
7174 )
7175 repo.ui.popbuffer()
7175 repo.ui.popbuffer()
7176 return dest, dbranch, dother, outgoing
7176 return dest, dbranch, dother, outgoing
7177
7177
7178 if needsoutgoing:
7178 if needsoutgoing:
7179 dest, dbranch, dother, outgoing = getoutgoing()
7179 dest, dbranch, dother, outgoing = getoutgoing()
7180 else:
7180 else:
7181 dest = dbranch = dother = outgoing = None
7181 dest = dbranch = dother = outgoing = None
7182
7182
7183 if opts.get(b'remote'):
7183 if opts.get(b'remote'):
7184 t = []
7184 t = []
7185 if incoming:
7185 if incoming:
7186 t.append(_(b'1 or more incoming'))
7186 t.append(_(b'1 or more incoming'))
7187 o = outgoing.missing
7187 o = outgoing.missing
7188 if o:
7188 if o:
7189 t.append(_(b'%d outgoing') % len(o))
7189 t.append(_(b'%d outgoing') % len(o))
7190 other = dother or sother
7190 other = dother or sother
7191 if b'bookmarks' in other.listkeys(b'namespaces'):
7191 if b'bookmarks' in other.listkeys(b'namespaces'):
7192 counts = bookmarks.summary(repo, other)
7192 counts = bookmarks.summary(repo, other)
7193 if counts[0] > 0:
7193 if counts[0] > 0:
7194 t.append(_(b'%d incoming bookmarks') % counts[0])
7194 t.append(_(b'%d incoming bookmarks') % counts[0])
7195 if counts[1] > 0:
7195 if counts[1] > 0:
7196 t.append(_(b'%d outgoing bookmarks') % counts[1])
7196 t.append(_(b'%d outgoing bookmarks') % counts[1])
7197
7197
7198 if t:
7198 if t:
7199 # i18n: column positioning for "hg summary"
7199 # i18n: column positioning for "hg summary"
7200 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7200 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7201 else:
7201 else:
7202 # i18n: column positioning for "hg summary"
7202 # i18n: column positioning for "hg summary"
7203 ui.status(_(b'remote: (synced)\n'))
7203 ui.status(_(b'remote: (synced)\n'))
7204
7204
7205 cmdutil.summaryremotehooks(
7205 cmdutil.summaryremotehooks(
7206 ui,
7206 ui,
7207 repo,
7207 repo,
7208 opts,
7208 opts,
7209 (
7209 (
7210 (source, sbranch, sother, commoninc),
7210 (source, sbranch, sother, commoninc),
7211 (dest, dbranch, dother, outgoing),
7211 (dest, dbranch, dother, outgoing),
7212 ),
7212 ),
7213 )
7213 )
7214
7214
7215
7215
7216 @command(
7216 @command(
7217 b'tag',
7217 b'tag',
7218 [
7218 [
7219 (b'f', b'force', None, _(b'force tag')),
7219 (b'f', b'force', None, _(b'force tag')),
7220 (b'l', b'local', None, _(b'make the tag local')),
7220 (b'l', b'local', None, _(b'make the tag local')),
7221 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7221 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7222 (b'', b'remove', None, _(b'remove a tag')),
7222 (b'', b'remove', None, _(b'remove a tag')),
7223 # -l/--local is already there, commitopts cannot be used
7223 # -l/--local is already there, commitopts cannot be used
7224 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7224 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7225 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7225 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7226 ]
7226 ]
7227 + commitopts2,
7227 + commitopts2,
7228 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7228 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7229 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7229 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7230 )
7230 )
7231 def tag(ui, repo, name1, *names, **opts):
7231 def tag(ui, repo, name1, *names, **opts):
7232 """add one or more tags for the current or given revision
7232 """add one or more tags for the current or given revision
7233
7233
7234 Name a particular revision using <name>.
7234 Name a particular revision using <name>.
7235
7235
7236 Tags are used to name particular revisions of the repository and are
7236 Tags are used to name particular revisions of the repository and are
7237 very useful to compare different revisions, to go back to significant
7237 very useful to compare different revisions, to go back to significant
7238 earlier versions or to mark branch points as releases, etc. Changing
7238 earlier versions or to mark branch points as releases, etc. Changing
7239 an existing tag is normally disallowed; use -f/--force to override.
7239 an existing tag is normally disallowed; use -f/--force to override.
7240
7240
7241 If no revision is given, the parent of the working directory is
7241 If no revision is given, the parent of the working directory is
7242 used.
7242 used.
7243
7243
7244 To facilitate version control, distribution, and merging of tags,
7244 To facilitate version control, distribution, and merging of tags,
7245 they are stored as a file named ".hgtags" which is managed similarly
7245 they are stored as a file named ".hgtags" which is managed similarly
7246 to other project files and can be hand-edited if necessary. This
7246 to other project files and can be hand-edited if necessary. This
7247 also means that tagging creates a new commit. The file
7247 also means that tagging creates a new commit. The file
7248 ".hg/localtags" is used for local tags (not shared among
7248 ".hg/localtags" is used for local tags (not shared among
7249 repositories).
7249 repositories).
7250
7250
7251 Tag commits are usually made at the head of a branch. If the parent
7251 Tag commits are usually made at the head of a branch. If the parent
7252 of the working directory is not a branch head, :hg:`tag` aborts; use
7252 of the working directory is not a branch head, :hg:`tag` aborts; use
7253 -f/--force to force the tag commit to be based on a non-head
7253 -f/--force to force the tag commit to be based on a non-head
7254 changeset.
7254 changeset.
7255
7255
7256 See :hg:`help dates` for a list of formats valid for -d/--date.
7256 See :hg:`help dates` for a list of formats valid for -d/--date.
7257
7257
7258 Since tag names have priority over branch names during revision
7258 Since tag names have priority over branch names during revision
7259 lookup, using an existing branch name as a tag name is discouraged.
7259 lookup, using an existing branch name as a tag name is discouraged.
7260
7260
7261 Returns 0 on success.
7261 Returns 0 on success.
7262 """
7262 """
7263 opts = pycompat.byteskwargs(opts)
7263 opts = pycompat.byteskwargs(opts)
7264 with repo.wlock(), repo.lock():
7264 with repo.wlock(), repo.lock():
7265 rev_ = b"."
7265 rev_ = b"."
7266 names = [t.strip() for t in (name1,) + names]
7266 names = [t.strip() for t in (name1,) + names]
7267 if len(names) != len(set(names)):
7267 if len(names) != len(set(names)):
7268 raise error.Abort(_(b'tag names must be unique'))
7268 raise error.Abort(_(b'tag names must be unique'))
7269 for n in names:
7269 for n in names:
7270 scmutil.checknewlabel(repo, n, b'tag')
7270 scmutil.checknewlabel(repo, n, b'tag')
7271 if not n:
7271 if not n:
7272 raise error.Abort(
7272 raise error.Abort(
7273 _(b'tag names cannot consist entirely of whitespace')
7273 _(b'tag names cannot consist entirely of whitespace')
7274 )
7274 )
7275 if opts.get(b'rev') and opts.get(b'remove'):
7275 if opts.get(b'rev') and opts.get(b'remove'):
7276 raise error.Abort(_(b"--rev and --remove are incompatible"))
7276 raise error.Abort(_(b"--rev and --remove are incompatible"))
7277 if opts.get(b'rev'):
7277 if opts.get(b'rev'):
7278 rev_ = opts[b'rev']
7278 rev_ = opts[b'rev']
7279 message = opts.get(b'message')
7279 message = opts.get(b'message')
7280 if opts.get(b'remove'):
7280 if opts.get(b'remove'):
7281 if opts.get(b'local'):
7281 if opts.get(b'local'):
7282 expectedtype = b'local'
7282 expectedtype = b'local'
7283 else:
7283 else:
7284 expectedtype = b'global'
7284 expectedtype = b'global'
7285
7285
7286 for n in names:
7286 for n in names:
7287 if repo.tagtype(n) == b'global':
7287 if repo.tagtype(n) == b'global':
7288 alltags = tagsmod.findglobaltags(ui, repo)
7288 alltags = tagsmod.findglobaltags(ui, repo)
7289 if alltags[n][0] == nullid:
7289 if alltags[n][0] == nullid:
7290 raise error.Abort(_(b"tag '%s' is already removed") % n)
7290 raise error.Abort(_(b"tag '%s' is already removed") % n)
7291 if not repo.tagtype(n):
7291 if not repo.tagtype(n):
7292 raise error.Abort(_(b"tag '%s' does not exist") % n)
7292 raise error.Abort(_(b"tag '%s' does not exist") % n)
7293 if repo.tagtype(n) != expectedtype:
7293 if repo.tagtype(n) != expectedtype:
7294 if expectedtype == b'global':
7294 if expectedtype == b'global':
7295 raise error.Abort(
7295 raise error.Abort(
7296 _(b"tag '%s' is not a global tag") % n
7296 _(b"tag '%s' is not a global tag") % n
7297 )
7297 )
7298 else:
7298 else:
7299 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7299 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7300 rev_ = b'null'
7300 rev_ = b'null'
7301 if not message:
7301 if not message:
7302 # we don't translate commit messages
7302 # we don't translate commit messages
7303 message = b'Removed tag %s' % b', '.join(names)
7303 message = b'Removed tag %s' % b', '.join(names)
7304 elif not opts.get(b'force'):
7304 elif not opts.get(b'force'):
7305 for n in names:
7305 for n in names:
7306 if n in repo.tags():
7306 if n in repo.tags():
7307 raise error.Abort(
7307 raise error.Abort(
7308 _(b"tag '%s' already exists (use -f to force)") % n
7308 _(b"tag '%s' already exists (use -f to force)") % n
7309 )
7309 )
7310 if not opts.get(b'local'):
7310 if not opts.get(b'local'):
7311 p1, p2 = repo.dirstate.parents()
7311 p1, p2 = repo.dirstate.parents()
7312 if p2 != nullid:
7312 if p2 != nullid:
7313 raise error.Abort(_(b'uncommitted merge'))
7313 raise error.Abort(_(b'uncommitted merge'))
7314 bheads = repo.branchheads()
7314 bheads = repo.branchheads()
7315 if not opts.get(b'force') and bheads and p1 not in bheads:
7315 if not opts.get(b'force') and bheads and p1 not in bheads:
7316 raise error.Abort(
7316 raise error.Abort(
7317 _(
7317 _(
7318 b'working directory is not at a branch head '
7318 b'working directory is not at a branch head '
7319 b'(use -f to force)'
7319 b'(use -f to force)'
7320 )
7320 )
7321 )
7321 )
7322 node = scmutil.revsingle(repo, rev_).node()
7322 node = scmutil.revsingle(repo, rev_).node()
7323
7323
7324 if not message:
7324 if not message:
7325 # we don't translate commit messages
7325 # we don't translate commit messages
7326 message = b'Added tag %s for changeset %s' % (
7326 message = b'Added tag %s for changeset %s' % (
7327 b', '.join(names),
7327 b', '.join(names),
7328 short(node),
7328 short(node),
7329 )
7329 )
7330
7330
7331 date = opts.get(b'date')
7331 date = opts.get(b'date')
7332 if date:
7332 if date:
7333 date = dateutil.parsedate(date)
7333 date = dateutil.parsedate(date)
7334
7334
7335 if opts.get(b'remove'):
7335 if opts.get(b'remove'):
7336 editform = b'tag.remove'
7336 editform = b'tag.remove'
7337 else:
7337 else:
7338 editform = b'tag.add'
7338 editform = b'tag.add'
7339 editor = cmdutil.getcommiteditor(
7339 editor = cmdutil.getcommiteditor(
7340 editform=editform, **pycompat.strkwargs(opts)
7340 editform=editform, **pycompat.strkwargs(opts)
7341 )
7341 )
7342
7342
7343 # don't allow tagging the null rev
7343 # don't allow tagging the null rev
7344 if (
7344 if (
7345 not opts.get(b'remove')
7345 not opts.get(b'remove')
7346 and scmutil.revsingle(repo, rev_).rev() == nullrev
7346 and scmutil.revsingle(repo, rev_).rev() == nullrev
7347 ):
7347 ):
7348 raise error.Abort(_(b"cannot tag null revision"))
7348 raise error.Abort(_(b"cannot tag null revision"))
7349
7349
7350 tagsmod.tag(
7350 tagsmod.tag(
7351 repo,
7351 repo,
7352 names,
7352 names,
7353 node,
7353 node,
7354 message,
7354 message,
7355 opts.get(b'local'),
7355 opts.get(b'local'),
7356 opts.get(b'user'),
7356 opts.get(b'user'),
7357 date,
7357 date,
7358 editor=editor,
7358 editor=editor,
7359 )
7359 )
7360
7360
7361
7361
7362 @command(
7362 @command(
7363 b'tags',
7363 b'tags',
7364 formatteropts,
7364 formatteropts,
7365 b'',
7365 b'',
7366 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7366 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7367 intents={INTENT_READONLY},
7367 intents={INTENT_READONLY},
7368 )
7368 )
7369 def tags(ui, repo, **opts):
7369 def tags(ui, repo, **opts):
7370 """list repository tags
7370 """list repository tags
7371
7371
7372 This lists both regular and local tags. When the -v/--verbose
7372 This lists both regular and local tags. When the -v/--verbose
7373 switch is used, a third column "local" is printed for local tags.
7373 switch is used, a third column "local" is printed for local tags.
7374 When the -q/--quiet switch is used, only the tag name is printed.
7374 When the -q/--quiet switch is used, only the tag name is printed.
7375
7375
7376 .. container:: verbose
7376 .. container:: verbose
7377
7377
7378 Template:
7378 Template:
7379
7379
7380 The following keywords are supported in addition to the common template
7380 The following keywords are supported in addition to the common template
7381 keywords and functions such as ``{tag}``. See also
7381 keywords and functions such as ``{tag}``. See also
7382 :hg:`help templates`.
7382 :hg:`help templates`.
7383
7383
7384 :type: String. ``local`` for local tags.
7384 :type: String. ``local`` for local tags.
7385
7385
7386 Returns 0 on success.
7386 Returns 0 on success.
7387 """
7387 """
7388
7388
7389 opts = pycompat.byteskwargs(opts)
7389 opts = pycompat.byteskwargs(opts)
7390 ui.pager(b'tags')
7390 ui.pager(b'tags')
7391 fm = ui.formatter(b'tags', opts)
7391 fm = ui.formatter(b'tags', opts)
7392 hexfunc = fm.hexfunc
7392 hexfunc = fm.hexfunc
7393
7393
7394 for t, n in reversed(repo.tagslist()):
7394 for t, n in reversed(repo.tagslist()):
7395 hn = hexfunc(n)
7395 hn = hexfunc(n)
7396 label = b'tags.normal'
7396 label = b'tags.normal'
7397 tagtype = b''
7397 tagtype = b''
7398 if repo.tagtype(t) == b'local':
7398 if repo.tagtype(t) == b'local':
7399 label = b'tags.local'
7399 label = b'tags.local'
7400 tagtype = b'local'
7400 tagtype = b'local'
7401
7401
7402 fm.startitem()
7402 fm.startitem()
7403 fm.context(repo=repo)
7403 fm.context(repo=repo)
7404 fm.write(b'tag', b'%s', t, label=label)
7404 fm.write(b'tag', b'%s', t, label=label)
7405 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7405 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7406 fm.condwrite(
7406 fm.condwrite(
7407 not ui.quiet,
7407 not ui.quiet,
7408 b'rev node',
7408 b'rev node',
7409 fmt,
7409 fmt,
7410 repo.changelog.rev(n),
7410 repo.changelog.rev(n),
7411 hn,
7411 hn,
7412 label=label,
7412 label=label,
7413 )
7413 )
7414 fm.condwrite(
7414 fm.condwrite(
7415 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7415 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7416 )
7416 )
7417 fm.plain(b'\n')
7417 fm.plain(b'\n')
7418 fm.end()
7418 fm.end()
7419
7419
7420
7420
7421 @command(
7421 @command(
7422 b'tip',
7422 b'tip',
7423 [
7423 [
7424 (b'p', b'patch', None, _(b'show patch')),
7424 (b'p', b'patch', None, _(b'show patch')),
7425 (b'g', b'git', None, _(b'use git extended diff format')),
7425 (b'g', b'git', None, _(b'use git extended diff format')),
7426 ]
7426 ]
7427 + templateopts,
7427 + templateopts,
7428 _(b'[-p] [-g]'),
7428 _(b'[-p] [-g]'),
7429 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7429 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7430 )
7430 )
7431 def tip(ui, repo, **opts):
7431 def tip(ui, repo, **opts):
7432 """show the tip revision (DEPRECATED)
7432 """show the tip revision (DEPRECATED)
7433
7433
7434 The tip revision (usually just called the tip) is the changeset
7434 The tip revision (usually just called the tip) is the changeset
7435 most recently added to the repository (and therefore the most
7435 most recently added to the repository (and therefore the most
7436 recently changed head).
7436 recently changed head).
7437
7437
7438 If you have just made a commit, that commit will be the tip. If
7438 If you have just made a commit, that commit will be the tip. If
7439 you have just pulled changes from another repository, the tip of
7439 you have just pulled changes from another repository, the tip of
7440 that repository becomes the current tip. The "tip" tag is special
7440 that repository becomes the current tip. The "tip" tag is special
7441 and cannot be renamed or assigned to a different changeset.
7441 and cannot be renamed or assigned to a different changeset.
7442
7442
7443 This command is deprecated, please use :hg:`heads` instead.
7443 This command is deprecated, please use :hg:`heads` instead.
7444
7444
7445 Returns 0 on success.
7445 Returns 0 on success.
7446 """
7446 """
7447 opts = pycompat.byteskwargs(opts)
7447 opts = pycompat.byteskwargs(opts)
7448 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7448 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7449 displayer.show(repo[b'tip'])
7449 displayer.show(repo[b'tip'])
7450 displayer.close()
7450 displayer.close()
7451
7451
7452
7452
7453 @command(
7453 @command(
7454 b'unbundle',
7454 b'unbundle',
7455 [
7455 [
7456 (
7456 (
7457 b'u',
7457 b'u',
7458 b'update',
7458 b'update',
7459 None,
7459 None,
7460 _(b'update to new branch head if changesets were unbundled'),
7460 _(b'update to new branch head if changesets were unbundled'),
7461 )
7461 )
7462 ],
7462 ],
7463 _(b'[-u] FILE...'),
7463 _(b'[-u] FILE...'),
7464 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7464 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7465 )
7465 )
7466 def unbundle(ui, repo, fname1, *fnames, **opts):
7466 def unbundle(ui, repo, fname1, *fnames, **opts):
7467 """apply one or more bundle files
7467 """apply one or more bundle files
7468
7468
7469 Apply one or more bundle files generated by :hg:`bundle`.
7469 Apply one or more bundle files generated by :hg:`bundle`.
7470
7470
7471 Returns 0 on success, 1 if an update has unresolved files.
7471 Returns 0 on success, 1 if an update has unresolved files.
7472 """
7472 """
7473 fnames = (fname1,) + fnames
7473 fnames = (fname1,) + fnames
7474
7474
7475 with repo.lock():
7475 with repo.lock():
7476 for fname in fnames:
7476 for fname in fnames:
7477 f = hg.openpath(ui, fname)
7477 f = hg.openpath(ui, fname)
7478 gen = exchange.readbundle(ui, f, fname)
7478 gen = exchange.readbundle(ui, f, fname)
7479 if isinstance(gen, streamclone.streamcloneapplier):
7479 if isinstance(gen, streamclone.streamcloneapplier):
7480 raise error.Abort(
7480 raise error.Abort(
7481 _(
7481 _(
7482 b'packed bundles cannot be applied with '
7482 b'packed bundles cannot be applied with '
7483 b'"hg unbundle"'
7483 b'"hg unbundle"'
7484 ),
7484 ),
7485 hint=_(b'use "hg debugapplystreamclonebundle"'),
7485 hint=_(b'use "hg debugapplystreamclonebundle"'),
7486 )
7486 )
7487 url = b'bundle:' + fname
7487 url = b'bundle:' + fname
7488 try:
7488 try:
7489 txnname = b'unbundle'
7489 txnname = b'unbundle'
7490 if not isinstance(gen, bundle2.unbundle20):
7490 if not isinstance(gen, bundle2.unbundle20):
7491 txnname = b'unbundle\n%s' % util.hidepassword(url)
7491 txnname = b'unbundle\n%s' % util.hidepassword(url)
7492 with repo.transaction(txnname) as tr:
7492 with repo.transaction(txnname) as tr:
7493 op = bundle2.applybundle(
7493 op = bundle2.applybundle(
7494 repo, gen, tr, source=b'unbundle', url=url
7494 repo, gen, tr, source=b'unbundle', url=url
7495 )
7495 )
7496 except error.BundleUnknownFeatureError as exc:
7496 except error.BundleUnknownFeatureError as exc:
7497 raise error.Abort(
7497 raise error.Abort(
7498 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7498 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7499 hint=_(
7499 hint=_(
7500 b"see https://mercurial-scm.org/"
7500 b"see https://mercurial-scm.org/"
7501 b"wiki/BundleFeature for more "
7501 b"wiki/BundleFeature for more "
7502 b"information"
7502 b"information"
7503 ),
7503 ),
7504 )
7504 )
7505 modheads = bundle2.combinechangegroupresults(op)
7505 modheads = bundle2.combinechangegroupresults(op)
7506
7506
7507 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7507 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7508
7508
7509
7509
7510 @command(
7510 @command(
7511 b'unshelve',
7511 b'unshelve',
7512 [
7512 [
7513 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7513 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7514 (
7514 (
7515 b'c',
7515 b'c',
7516 b'continue',
7516 b'continue',
7517 None,
7517 None,
7518 _(b'continue an incomplete unshelve operation'),
7518 _(b'continue an incomplete unshelve operation'),
7519 ),
7519 ),
7520 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7520 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7521 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7521 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7522 (
7522 (
7523 b'n',
7523 b'n',
7524 b'name',
7524 b'name',
7525 b'',
7525 b'',
7526 _(b'restore shelved change with given name'),
7526 _(b'restore shelved change with given name'),
7527 _(b'NAME'),
7527 _(b'NAME'),
7528 ),
7528 ),
7529 (b't', b'tool', b'', _(b'specify merge tool')),
7529 (b't', b'tool', b'', _(b'specify merge tool')),
7530 (
7530 (
7531 b'',
7531 b'',
7532 b'date',
7532 b'date',
7533 b'',
7533 b'',
7534 _(b'set date for temporary commits (DEPRECATED)'),
7534 _(b'set date for temporary commits (DEPRECATED)'),
7535 _(b'DATE'),
7535 _(b'DATE'),
7536 ),
7536 ),
7537 ],
7537 ],
7538 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7538 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7539 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7539 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7540 )
7540 )
7541 def unshelve(ui, repo, *shelved, **opts):
7541 def unshelve(ui, repo, *shelved, **opts):
7542 """restore a shelved change to the working directory
7542 """restore a shelved change to the working directory
7543
7543
7544 This command accepts an optional name of a shelved change to
7544 This command accepts an optional name of a shelved change to
7545 restore. If none is given, the most recent shelved change is used.
7545 restore. If none is given, the most recent shelved change is used.
7546
7546
7547 If a shelved change is applied successfully, the bundle that
7547 If a shelved change is applied successfully, the bundle that
7548 contains the shelved changes is moved to a backup location
7548 contains the shelved changes is moved to a backup location
7549 (.hg/shelve-backup).
7549 (.hg/shelve-backup).
7550
7550
7551 Since you can restore a shelved change on top of an arbitrary
7551 Since you can restore a shelved change on top of an arbitrary
7552 commit, it is possible that unshelving will result in a conflict
7552 commit, it is possible that unshelving will result in a conflict
7553 between your changes and the commits you are unshelving onto. If
7553 between your changes and the commits you are unshelving onto. If
7554 this occurs, you must resolve the conflict, then use
7554 this occurs, you must resolve the conflict, then use
7555 ``--continue`` to complete the unshelve operation. (The bundle
7555 ``--continue`` to complete the unshelve operation. (The bundle
7556 will not be moved until you successfully complete the unshelve.)
7556 will not be moved until you successfully complete the unshelve.)
7557
7557
7558 (Alternatively, you can use ``--abort`` to abandon an unshelve
7558 (Alternatively, you can use ``--abort`` to abandon an unshelve
7559 that causes a conflict. This reverts the unshelved changes, and
7559 that causes a conflict. This reverts the unshelved changes, and
7560 leaves the bundle in place.)
7560 leaves the bundle in place.)
7561
7561
7562 If bare shelved change (without interactive, include and exclude
7562 If bare shelved change (without interactive, include and exclude
7563 option) was done on newly created branch it would restore branch
7563 option) was done on newly created branch it would restore branch
7564 information to the working directory.
7564 information to the working directory.
7565
7565
7566 After a successful unshelve, the shelved changes are stored in a
7566 After a successful unshelve, the shelved changes are stored in a
7567 backup directory. Only the N most recent backups are kept. N
7567 backup directory. Only the N most recent backups are kept. N
7568 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7568 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7569 configuration option.
7569 configuration option.
7570
7570
7571 .. container:: verbose
7571 .. container:: verbose
7572
7572
7573 Timestamp in seconds is used to decide order of backups. More
7573 Timestamp in seconds is used to decide order of backups. More
7574 than ``maxbackups`` backups are kept, if same timestamp
7574 than ``maxbackups`` backups are kept, if same timestamp
7575 prevents from deciding exact order of them, for safety.
7575 prevents from deciding exact order of them, for safety.
7576
7576
7577 Selected changes can be unshelved with ``--interactive`` flag.
7577 Selected changes can be unshelved with ``--interactive`` flag.
7578 The working directory is updated with the selected changes, and
7578 The working directory is updated with the selected changes, and
7579 only the unselected changes remain shelved.
7579 only the unselected changes remain shelved.
7580 Note: The whole shelve is applied to working directory first before
7580 Note: The whole shelve is applied to working directory first before
7581 running interactively. So, this will bring up all the conflicts between
7581 running interactively. So, this will bring up all the conflicts between
7582 working directory and the shelve, irrespective of which changes will be
7582 working directory and the shelve, irrespective of which changes will be
7583 unshelved.
7583 unshelved.
7584 """
7584 """
7585 with repo.wlock():
7585 with repo.wlock():
7586 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
7586 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
7587
7587
7588
7588
7589 statemod.addunfinished(
7589 statemod.addunfinished(
7590 b'unshelve',
7590 b'unshelve',
7591 fname=b'shelvedstate',
7591 fname=b'shelvedstate',
7592 continueflag=True,
7592 continueflag=True,
7593 abortfunc=shelvemod.hgabortunshelve,
7593 abortfunc=shelvemod.hgabortunshelve,
7594 continuefunc=shelvemod.hgcontinueunshelve,
7594 continuefunc=shelvemod.hgcontinueunshelve,
7595 cmdmsg=_(b'unshelve already in progress'),
7595 cmdmsg=_(b'unshelve already in progress'),
7596 )
7596 )
7597
7597
7598
7598
7599 @command(
7599 @command(
7600 b'update|up|checkout|co',
7600 b'update|up|checkout|co',
7601 [
7601 [
7602 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7602 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7603 (b'c', b'check', None, _(b'require clean working directory')),
7603 (b'c', b'check', None, _(b'require clean working directory')),
7604 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7604 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7605 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7605 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7606 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7606 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7607 ]
7607 ]
7608 + mergetoolopts,
7608 + mergetoolopts,
7609 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7609 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7610 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7610 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7611 helpbasic=True,
7611 helpbasic=True,
7612 )
7612 )
7613 def update(ui, repo, node=None, **opts):
7613 def update(ui, repo, node=None, **opts):
7614 """update working directory (or switch revisions)
7614 """update working directory (or switch revisions)
7615
7615
7616 Update the repository's working directory to the specified
7616 Update the repository's working directory to the specified
7617 changeset. If no changeset is specified, update to the tip of the
7617 changeset. If no changeset is specified, update to the tip of the
7618 current named branch and move the active bookmark (see :hg:`help
7618 current named branch and move the active bookmark (see :hg:`help
7619 bookmarks`).
7619 bookmarks`).
7620
7620
7621 Update sets the working directory's parent revision to the specified
7621 Update sets the working directory's parent revision to the specified
7622 changeset (see :hg:`help parents`).
7622 changeset (see :hg:`help parents`).
7623
7623
7624 If the changeset is not a descendant or ancestor of the working
7624 If the changeset is not a descendant or ancestor of the working
7625 directory's parent and there are uncommitted changes, the update is
7625 directory's parent and there are uncommitted changes, the update is
7626 aborted. With the -c/--check option, the working directory is checked
7626 aborted. With the -c/--check option, the working directory is checked
7627 for uncommitted changes; if none are found, the working directory is
7627 for uncommitted changes; if none are found, the working directory is
7628 updated to the specified changeset.
7628 updated to the specified changeset.
7629
7629
7630 .. container:: verbose
7630 .. container:: verbose
7631
7631
7632 The -C/--clean, -c/--check, and -m/--merge options control what
7632 The -C/--clean, -c/--check, and -m/--merge options control what
7633 happens if the working directory contains uncommitted changes.
7633 happens if the working directory contains uncommitted changes.
7634 At most of one of them can be specified.
7634 At most of one of them can be specified.
7635
7635
7636 1. If no option is specified, and if
7636 1. If no option is specified, and if
7637 the requested changeset is an ancestor or descendant of
7637 the requested changeset is an ancestor or descendant of
7638 the working directory's parent, the uncommitted changes
7638 the working directory's parent, the uncommitted changes
7639 are merged into the requested changeset and the merged
7639 are merged into the requested changeset and the merged
7640 result is left uncommitted. If the requested changeset is
7640 result is left uncommitted. If the requested changeset is
7641 not an ancestor or descendant (that is, it is on another
7641 not an ancestor or descendant (that is, it is on another
7642 branch), the update is aborted and the uncommitted changes
7642 branch), the update is aborted and the uncommitted changes
7643 are preserved.
7643 are preserved.
7644
7644
7645 2. With the -m/--merge option, the update is allowed even if the
7645 2. With the -m/--merge option, the update is allowed even if the
7646 requested changeset is not an ancestor or descendant of
7646 requested changeset is not an ancestor or descendant of
7647 the working directory's parent.
7647 the working directory's parent.
7648
7648
7649 3. With the -c/--check option, the update is aborted and the
7649 3. With the -c/--check option, the update is aborted and the
7650 uncommitted changes are preserved.
7650 uncommitted changes are preserved.
7651
7651
7652 4. With the -C/--clean option, uncommitted changes are discarded and
7652 4. With the -C/--clean option, uncommitted changes are discarded and
7653 the working directory is updated to the requested changeset.
7653 the working directory is updated to the requested changeset.
7654
7654
7655 To cancel an uncommitted merge (and lose your changes), use
7655 To cancel an uncommitted merge (and lose your changes), use
7656 :hg:`merge --abort`.
7656 :hg:`merge --abort`.
7657
7657
7658 Use null as the changeset to remove the working directory (like
7658 Use null as the changeset to remove the working directory (like
7659 :hg:`clone -U`).
7659 :hg:`clone -U`).
7660
7660
7661 If you want to revert just one file to an older revision, use
7661 If you want to revert just one file to an older revision, use
7662 :hg:`revert [-r REV] NAME`.
7662 :hg:`revert [-r REV] NAME`.
7663
7663
7664 See :hg:`help dates` for a list of formats valid for -d/--date.
7664 See :hg:`help dates` for a list of formats valid for -d/--date.
7665
7665
7666 Returns 0 on success, 1 if there are unresolved files.
7666 Returns 0 on success, 1 if there are unresolved files.
7667 """
7667 """
7668 rev = opts.get('rev')
7668 rev = opts.get('rev')
7669 date = opts.get('date')
7669 date = opts.get('date')
7670 clean = opts.get('clean')
7670 clean = opts.get('clean')
7671 check = opts.get('check')
7671 check = opts.get('check')
7672 merge = opts.get('merge')
7672 merge = opts.get('merge')
7673 if rev and node:
7673 if rev and node:
7674 raise error.Abort(_(b"please specify just one revision"))
7674 raise error.Abort(_(b"please specify just one revision"))
7675
7675
7676 if ui.configbool(b'commands', b'update.requiredest'):
7676 if ui.configbool(b'commands', b'update.requiredest'):
7677 if not node and not rev and not date:
7677 if not node and not rev and not date:
7678 raise error.Abort(
7678 raise error.Abort(
7679 _(b'you must specify a destination'),
7679 _(b'you must specify a destination'),
7680 hint=_(b'for example: hg update ".::"'),
7680 hint=_(b'for example: hg update ".::"'),
7681 )
7681 )
7682
7682
7683 if rev is None or rev == b'':
7683 if rev is None or rev == b'':
7684 rev = node
7684 rev = node
7685
7685
7686 if date and rev is not None:
7686 if date and rev is not None:
7687 raise error.Abort(_(b"you can't specify a revision and a date"))
7687 raise error.Abort(_(b"you can't specify a revision and a date"))
7688
7688
7689 if len([x for x in (clean, check, merge) if x]) > 1:
7689 if len([x for x in (clean, check, merge) if x]) > 1:
7690 raise error.Abort(
7690 raise error.Abort(
7691 _(
7691 _(
7692 b"can only specify one of -C/--clean, -c/--check, "
7692 b"can only specify one of -C/--clean, -c/--check, "
7693 b"or -m/--merge"
7693 b"or -m/--merge"
7694 )
7694 )
7695 )
7695 )
7696
7696
7697 updatecheck = None
7697 updatecheck = None
7698 if check:
7698 if check:
7699 updatecheck = b'abort'
7699 updatecheck = b'abort'
7700 elif merge:
7700 elif merge:
7701 updatecheck = b'none'
7701 updatecheck = b'none'
7702
7702
7703 with repo.wlock():
7703 with repo.wlock():
7704 cmdutil.clearunfinished(repo)
7704 cmdutil.clearunfinished(repo)
7705 if date:
7705 if date:
7706 rev = cmdutil.finddate(ui, repo, date)
7706 rev = cmdutil.finddate(ui, repo, date)
7707
7707
7708 # if we defined a bookmark, we have to remember the original name
7708 # if we defined a bookmark, we have to remember the original name
7709 brev = rev
7709 brev = rev
7710 if rev:
7710 if rev:
7711 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7711 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7712 ctx = scmutil.revsingle(repo, rev, default=None)
7712 ctx = scmutil.revsingle(repo, rev, default=None)
7713 rev = ctx.rev()
7713 rev = ctx.rev()
7714 hidden = ctx.hidden()
7714 hidden = ctx.hidden()
7715 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7715 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7716 with ui.configoverride(overrides, b'update'):
7716 with ui.configoverride(overrides, b'update'):
7717 ret = hg.updatetotally(
7717 ret = hg.updatetotally(
7718 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7718 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7719 )
7719 )
7720 if hidden:
7720 if hidden:
7721 ctxstr = ctx.hex()[:12]
7721 ctxstr = ctx.hex()[:12]
7722 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7722 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7723
7723
7724 if ctx.obsolete():
7724 if ctx.obsolete():
7725 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7725 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7726 ui.warn(b"(%s)\n" % obsfatemsg)
7726 ui.warn(b"(%s)\n" % obsfatemsg)
7727 return ret
7727 return ret
7728
7728
7729
7729
7730 @command(
7730 @command(
7731 b'verify',
7731 b'verify',
7732 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7732 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7733 helpcategory=command.CATEGORY_MAINTENANCE,
7733 helpcategory=command.CATEGORY_MAINTENANCE,
7734 )
7734 )
7735 def verify(ui, repo, **opts):
7735 def verify(ui, repo, **opts):
7736 """verify the integrity of the repository
7736 """verify the integrity of the repository
7737
7737
7738 Verify the integrity of the current repository.
7738 Verify the integrity of the current repository.
7739
7739
7740 This will perform an extensive check of the repository's
7740 This will perform an extensive check of the repository's
7741 integrity, validating the hashes and checksums of each entry in
7741 integrity, validating the hashes and checksums of each entry in
7742 the changelog, manifest, and tracked files, as well as the
7742 the changelog, manifest, and tracked files, as well as the
7743 integrity of their crosslinks and indices.
7743 integrity of their crosslinks and indices.
7744
7744
7745 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7745 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7746 for more information about recovery from corruption of the
7746 for more information about recovery from corruption of the
7747 repository.
7747 repository.
7748
7748
7749 Returns 0 on success, 1 if errors are encountered.
7749 Returns 0 on success, 1 if errors are encountered.
7750 """
7750 """
7751 opts = pycompat.byteskwargs(opts)
7751 opts = pycompat.byteskwargs(opts)
7752
7752
7753 level = None
7753 level = None
7754 if opts[b'full']:
7754 if opts[b'full']:
7755 level = verifymod.VERIFY_FULL
7755 level = verifymod.VERIFY_FULL
7756 return hg.verify(repo, level)
7756 return hg.verify(repo, level)
7757
7757
7758
7758
7759 @command(
7759 @command(
7760 b'version',
7760 b'version',
7761 [] + formatteropts,
7761 [] + formatteropts,
7762 helpcategory=command.CATEGORY_HELP,
7762 helpcategory=command.CATEGORY_HELP,
7763 norepo=True,
7763 norepo=True,
7764 intents={INTENT_READONLY},
7764 intents={INTENT_READONLY},
7765 )
7765 )
7766 def version_(ui, **opts):
7766 def version_(ui, **opts):
7767 """output version and copyright information
7767 """output version and copyright information
7768
7768
7769 .. container:: verbose
7769 .. container:: verbose
7770
7770
7771 Template:
7771 Template:
7772
7772
7773 The following keywords are supported. See also :hg:`help templates`.
7773 The following keywords are supported. See also :hg:`help templates`.
7774
7774
7775 :extensions: List of extensions.
7775 :extensions: List of extensions.
7776 :ver: String. Version number.
7776 :ver: String. Version number.
7777
7777
7778 And each entry of ``{extensions}`` provides the following sub-keywords
7778 And each entry of ``{extensions}`` provides the following sub-keywords
7779 in addition to ``{ver}``.
7779 in addition to ``{ver}``.
7780
7780
7781 :bundled: Boolean. True if included in the release.
7781 :bundled: Boolean. True if included in the release.
7782 :name: String. Extension name.
7782 :name: String. Extension name.
7783 """
7783 """
7784 opts = pycompat.byteskwargs(opts)
7784 opts = pycompat.byteskwargs(opts)
7785 if ui.verbose:
7785 if ui.verbose:
7786 ui.pager(b'version')
7786 ui.pager(b'version')
7787 fm = ui.formatter(b"version", opts)
7787 fm = ui.formatter(b"version", opts)
7788 fm.startitem()
7788 fm.startitem()
7789 fm.write(
7789 fm.write(
7790 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7790 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7791 )
7791 )
7792 license = _(
7792 license = _(
7793 b"(see https://mercurial-scm.org for more information)\n"
7793 b"(see https://mercurial-scm.org for more information)\n"
7794 b"\nCopyright (C) 2005-2019 Matt Mackall and others\n"
7794 b"\nCopyright (C) 2005-2019 Matt Mackall and others\n"
7795 b"This is free software; see the source for copying conditions. "
7795 b"This is free software; see the source for copying conditions. "
7796 b"There is NO\nwarranty; "
7796 b"There is NO\nwarranty; "
7797 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7797 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7798 )
7798 )
7799 if not ui.quiet:
7799 if not ui.quiet:
7800 fm.plain(license)
7800 fm.plain(license)
7801
7801
7802 if ui.verbose:
7802 if ui.verbose:
7803 fm.plain(_(b"\nEnabled extensions:\n\n"))
7803 fm.plain(_(b"\nEnabled extensions:\n\n"))
7804 # format names and versions into columns
7804 # format names and versions into columns
7805 names = []
7805 names = []
7806 vers = []
7806 vers = []
7807 isinternals = []
7807 isinternals = []
7808 for name, module in extensions.extensions():
7808 for name, module in extensions.extensions():
7809 names.append(name)
7809 names.append(name)
7810 vers.append(extensions.moduleversion(module) or None)
7810 vers.append(extensions.moduleversion(module) or None)
7811 isinternals.append(extensions.ismoduleinternal(module))
7811 isinternals.append(extensions.ismoduleinternal(module))
7812 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7812 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7813 if names:
7813 if names:
7814 namefmt = b" %%-%ds " % max(len(n) for n in names)
7814 namefmt = b" %%-%ds " % max(len(n) for n in names)
7815 places = [_(b"external"), _(b"internal")]
7815 places = [_(b"external"), _(b"internal")]
7816 for n, v, p in zip(names, vers, isinternals):
7816 for n, v, p in zip(names, vers, isinternals):
7817 fn.startitem()
7817 fn.startitem()
7818 fn.condwrite(ui.verbose, b"name", namefmt, n)
7818 fn.condwrite(ui.verbose, b"name", namefmt, n)
7819 if ui.verbose:
7819 if ui.verbose:
7820 fn.plain(b"%s " % places[p])
7820 fn.plain(b"%s " % places[p])
7821 fn.data(bundled=p)
7821 fn.data(bundled=p)
7822 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7822 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7823 if ui.verbose:
7823 if ui.verbose:
7824 fn.plain(b"\n")
7824 fn.plain(b"\n")
7825 fn.end()
7825 fn.end()
7826 fm.end()
7826 fm.end()
7827
7827
7828
7828
7829 def loadcmdtable(ui, name, cmdtable):
7829 def loadcmdtable(ui, name, cmdtable):
7830 """Load command functions from specified cmdtable
7830 """Load command functions from specified cmdtable
7831 """
7831 """
7832 overrides = [cmd for cmd in cmdtable if cmd in table]
7832 overrides = [cmd for cmd in cmdtable if cmd in table]
7833 if overrides:
7833 if overrides:
7834 ui.warn(
7834 ui.warn(
7835 _(b"extension '%s' overrides commands: %s\n")
7835 _(b"extension '%s' overrides commands: %s\n")
7836 % (name, b" ".join(overrides))
7836 % (name, b" ".join(overrides))
7837 )
7837 )
7838 table.update(cmdtable)
7838 table.update(cmdtable)
@@ -1,310 +1,317 b''
1 $ hg init
1 $ hg init
2 $ cat << EOF > a
2 $ cat << EOF > a
3 > Small Mathematical Series.
3 > Small Mathematical Series.
4 > One
4 > One
5 > Two
5 > Two
6 > Three
6 > Three
7 > Four
7 > Four
8 > Five
8 > Five
9 > Hop we are done.
9 > Hop we are done.
10 > EOF
10 > EOF
11 $ hg add a
11 $ hg add a
12 $ hg commit -m ancestor
12 $ hg commit -m ancestor
13 $ cat << EOF > a
13 $ cat << EOF > a
14 > Small Mathematical Series.
14 > Small Mathematical Series.
15 > 1
15 > 1
16 > 2
16 > 2
17 > 3
17 > 3
18 > 4
18 > 4
19 > 5
19 > 5
20 > Hop we are done.
20 > Hop we are done.
21 > EOF
21 > EOF
22 $ hg commit -m branch1
22 $ hg commit -m branch1
23 $ hg co 0
23 $ hg co 0
24 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 $ cat << EOF > a
25 $ cat << EOF > a
26 > Small Mathematical Series.
26 > Small Mathematical Series.
27 > 1
27 > 1
28 > 2
28 > 2
29 > 3
29 > 3
30 > 6
30 > 6
31 > 8
31 > 8
32 > Hop we are done.
32 > Hop we are done.
33 > EOF
33 > EOF
34 $ hg commit -m branch2
34 $ hg commit -m branch2
35 created new head
35 created new head
36
36
37 $ hg merge 1
37 $ hg merge 1
38 merging a
38 merging a
39 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
39 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
40 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
40 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
41 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
41 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
42 [1]
42 [1]
43
43
44 $ hg id
44 $ hg id
45 618808747361+c0c68e4fe667+ tip
45 618808747361+c0c68e4fe667+ tip
46
46
47 $ echo "[commands]" >> $HGRCPATH
47 $ echo "[commands]" >> $HGRCPATH
48 $ echo "status.verbose=true" >> $HGRCPATH
48 $ echo "status.verbose=true" >> $HGRCPATH
49 $ hg status
49 $ hg status
50 M a
50 M a
51 ? a.orig
51 ? a.orig
52 # The repository is in an unfinished *merge* state.
52 # The repository is in an unfinished *merge* state.
53
53
54 # Unresolved merge conflicts:
54 # Unresolved merge conflicts:
55 #
55 #
56 # a
56 # a
57 #
57 #
58 # To mark files as resolved: hg resolve --mark FILE
58 # To mark files as resolved: hg resolve --mark FILE
59
59
60 # To continue: hg commit
60 # To continue: hg commit
61 # To abort: hg merge --abort
61 # To abort: hg merge --abort
62
62
63 $ hg status -Tjson
63 $ hg status -Tjson
64 [
64 [
65 {
65 {
66 "itemtype": "file",
66 "path": "a",
67 "path": "a",
67 "status": "M",
68 "status": "M",
68 "unresolved": true
69 "unresolved": true
69 },
70 },
70 {
71 {
72 "itemtype": "file",
71 "path": "a.orig",
73 "path": "a.orig",
72 "status": "?"
74 "status": "?"
75 },
76 {
77 "itemtype": "morestatus",
78 "unfinished": "merge",
79 "unfinishedmsg": "To continue: hg commit\nTo abort: hg merge --abort"
73 }
80 }
74 ]
81 ]
75
82
76 $ cat a
83 $ cat a
77 Small Mathematical Series.
84 Small Mathematical Series.
78 1
85 1
79 2
86 2
80 3
87 3
81 <<<<<<< working copy: 618808747361 - test: branch2
88 <<<<<<< working copy: 618808747361 - test: branch2
82 6
89 6
83 8
90 8
84 =======
91 =======
85 4
92 4
86 5
93 5
87 >>>>>>> merge rev: c0c68e4fe667 - test: branch1
94 >>>>>>> merge rev: c0c68e4fe667 - test: branch1
88 Hop we are done.
95 Hop we are done.
89
96
90 $ hg status --config commands.status.verbose=0
97 $ hg status --config commands.status.verbose=0
91 M a
98 M a
92 ? a.orig
99 ? a.orig
93
100
94 Verify custom conflict markers
101 Verify custom conflict markers
95
102
96 $ hg up -q --clean .
103 $ hg up -q --clean .
97 $ cat <<EOF >> .hg/hgrc
104 $ cat <<EOF >> .hg/hgrc
98 > [ui]
105 > [ui]
99 > mergemarkertemplate = '{author} {rev}'
106 > mergemarkertemplate = '{author} {rev}'
100 > EOF
107 > EOF
101
108
102 $ hg merge 1
109 $ hg merge 1
103 merging a
110 merging a
104 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
111 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
105 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
112 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
106 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
113 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
107 [1]
114 [1]
108
115
109 $ cat a
116 $ cat a
110 Small Mathematical Series.
117 Small Mathematical Series.
111 1
118 1
112 2
119 2
113 3
120 3
114 <<<<<<< working copy: test 2
121 <<<<<<< working copy: test 2
115 6
122 6
116 8
123 8
117 =======
124 =======
118 4
125 4
119 5
126 5
120 >>>>>>> merge rev: test 1
127 >>>>>>> merge rev: test 1
121 Hop we are done.
128 Hop we are done.
122
129
123 Verify line splitting of custom conflict marker which causes multiple lines
130 Verify line splitting of custom conflict marker which causes multiple lines
124
131
125 $ hg up -q --clean .
132 $ hg up -q --clean .
126 $ cat >> .hg/hgrc <<EOF
133 $ cat >> .hg/hgrc <<EOF
127 > [ui]
134 > [ui]
128 > mergemarkertemplate={author} {rev}\nfoo\nbar\nbaz
135 > mergemarkertemplate={author} {rev}\nfoo\nbar\nbaz
129 > EOF
136 > EOF
130
137
131 $ hg -q merge 1
138 $ hg -q merge 1
132 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
139 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
133 [1]
140 [1]
134
141
135 $ cat a
142 $ cat a
136 Small Mathematical Series.
143 Small Mathematical Series.
137 1
144 1
138 2
145 2
139 3
146 3
140 <<<<<<< working copy: test 2
147 <<<<<<< working copy: test 2
141 6
148 6
142 8
149 8
143 =======
150 =======
144 4
151 4
145 5
152 5
146 >>>>>>> merge rev: test 1
153 >>>>>>> merge rev: test 1
147 Hop we are done.
154 Hop we are done.
148
155
149 Verify line trimming of custom conflict marker using multi-byte characters
156 Verify line trimming of custom conflict marker using multi-byte characters
150
157
151 $ hg up -q --clean .
158 $ hg up -q --clean .
152 $ "$PYTHON" <<EOF
159 $ "$PYTHON" <<EOF
153 > fp = open('logfile', 'wb')
160 > fp = open('logfile', 'wb')
154 > fp.write(b'12345678901234567890123456789012345678901234567890' +
161 > fp.write(b'12345678901234567890123456789012345678901234567890' +
155 > b'1234567890') # there are 5 more columns for 80 columns
162 > b'1234567890') # there are 5 more columns for 80 columns
156 >
163 >
157 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
164 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
158 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
165 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
159 >
166 >
160 > fp.close()
167 > fp.close()
161 > EOF
168 > EOF
162 $ hg add logfile
169 $ hg add logfile
163 $ hg --encoding utf-8 commit --logfile logfile
170 $ hg --encoding utf-8 commit --logfile logfile
164
171
165 $ cat >> .hg/hgrc <<EOF
172 $ cat >> .hg/hgrc <<EOF
166 > [ui]
173 > [ui]
167 > mergemarkertemplate={desc|firstline}
174 > mergemarkertemplate={desc|firstline}
168 > EOF
175 > EOF
169
176
170 $ hg -q --encoding utf-8 merge 1
177 $ hg -q --encoding utf-8 merge 1
171 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
178 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
172 [1]
179 [1]
173
180
174 $ cat a
181 $ cat a
175 Small Mathematical Series.
182 Small Mathematical Series.
176 1
183 1
177 2
184 2
178 3
185 3
179 <<<<<<< working copy: 1234567890123456789012345678901234567890123456789012345...
186 <<<<<<< working copy: 1234567890123456789012345678901234567890123456789012345...
180 6
187 6
181 8
188 8
182 =======
189 =======
183 4
190 4
184 5
191 5
185 >>>>>>> merge rev: branch1
192 >>>>>>> merge rev: branch1
186 Hop we are done.
193 Hop we are done.
187
194
188 Verify basic conflict markers
195 Verify basic conflict markers
189
196
190 $ hg up -q --clean 2
197 $ hg up -q --clean 2
191 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
198 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
192
199
193 $ hg merge 1
200 $ hg merge 1
194 merging a
201 merging a
195 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
202 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
196 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
203 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
197 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
204 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
198 [1]
205 [1]
199
206
200 $ cat a
207 $ cat a
201 Small Mathematical Series.
208 Small Mathematical Series.
202 1
209 1
203 2
210 2
204 3
211 3
205 <<<<<<< working copy
212 <<<<<<< working copy
206 6
213 6
207 8
214 8
208 =======
215 =======
209 4
216 4
210 5
217 5
211 >>>>>>> merge rev
218 >>>>>>> merge rev
212 Hop we are done.
219 Hop we are done.
213
220
214 internal:merge3
221 internal:merge3
215
222
216 $ hg up -q --clean .
223 $ hg up -q --clean .
217
224
218 $ hg merge 1 --tool internal:merge3
225 $ hg merge 1 --tool internal:merge3
219 merging a
226 merging a
220 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
227 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
221 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
228 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
222 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
229 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
223 [1]
230 [1]
224 $ cat a
231 $ cat a
225 Small Mathematical Series.
232 Small Mathematical Series.
226 <<<<<<< working copy
233 <<<<<<< working copy
227 1
234 1
228 2
235 2
229 3
236 3
230 6
237 6
231 8
238 8
232 ||||||| base
239 ||||||| base
233 One
240 One
234 Two
241 Two
235 Three
242 Three
236 Four
243 Four
237 Five
244 Five
238 =======
245 =======
239 1
246 1
240 2
247 2
241 3
248 3
242 4
249 4
243 5
250 5
244 >>>>>>> merge rev
251 >>>>>>> merge rev
245 Hop we are done.
252 Hop we are done.
246
253
247 Add some unconflicting changes on each head, to make sure we really
254 Add some unconflicting changes on each head, to make sure we really
248 are merging, unlike :local and :other
255 are merging, unlike :local and :other
249
256
250 $ hg up -C
257 $ hg up -C
251 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
258 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 updated to "e0693e20f496: 123456789012345678901234567890123456789012345678901234567890????"
259 updated to "e0693e20f496: 123456789012345678901234567890123456789012345678901234567890????"
253 1 other heads for branch "default"
260 1 other heads for branch "default"
254 $ printf "\n\nEnd of file\n" >> a
261 $ printf "\n\nEnd of file\n" >> a
255 $ hg ci -m "Add some stuff at the end"
262 $ hg ci -m "Add some stuff at the end"
256 $ hg up -r 1
263 $ hg up -r 1
257 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
264 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
258 $ printf "Start of file\n\n\n" > tmp
265 $ printf "Start of file\n\n\n" > tmp
259 $ cat a >> tmp
266 $ cat a >> tmp
260 $ mv tmp a
267 $ mv tmp a
261 $ hg ci -m "Add some stuff at the beginning"
268 $ hg ci -m "Add some stuff at the beginning"
262
269
263 Now test :merge-other and :merge-local
270 Now test :merge-other and :merge-local
264
271
265 $ hg merge
272 $ hg merge
266 merging a
273 merging a
267 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
274 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
268 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
275 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
269 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
276 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
270 [1]
277 [1]
271 $ hg resolve --tool :merge-other a
278 $ hg resolve --tool :merge-other a
272 merging a
279 merging a
273 (no more unresolved files)
280 (no more unresolved files)
274 $ cat a
281 $ cat a
275 Start of file
282 Start of file
276
283
277
284
278 Small Mathematical Series.
285 Small Mathematical Series.
279 1
286 1
280 2
287 2
281 3
288 3
282 6
289 6
283 8
290 8
284 Hop we are done.
291 Hop we are done.
285
292
286
293
287 End of file
294 End of file
288
295
289 $ hg up -C
296 $ hg up -C
290 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
297 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
291 updated to "18b51d585961: Add some stuff at the beginning"
298 updated to "18b51d585961: Add some stuff at the beginning"
292 1 other heads for branch "default"
299 1 other heads for branch "default"
293 $ hg merge --tool :merge-local
300 $ hg merge --tool :merge-local
294 merging a
301 merging a
295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
302 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 (branch merge, don't forget to commit)
303 (branch merge, don't forget to commit)
297 $ cat a
304 $ cat a
298 Start of file
305 Start of file
299
306
300
307
301 Small Mathematical Series.
308 Small Mathematical Series.
302 1
309 1
303 2
310 2
304 3
311 3
305 4
312 4
306 5
313 5
307 Hop we are done.
314 Hop we are done.
308
315
309
316
310 End of file
317 End of file
@@ -1,665 +1,674 b''
1 $ hg init repo1
1 $ hg init repo1
2 $ cd repo1
2 $ cd repo1
3 $ mkdir a b a/1 b/1 b/2
3 $ mkdir a b a/1 b/1 b/2
4 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
4 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
5
5
6 hg status in repo root:
6 hg status in repo root:
7
7
8 $ hg status
8 $ hg status
9 ? a/1/in_a_1
9 ? a/1/in_a_1
10 ? a/in_a
10 ? a/in_a
11 ? b/1/in_b_1
11 ? b/1/in_b_1
12 ? b/2/in_b_2
12 ? b/2/in_b_2
13 ? b/in_b
13 ? b/in_b
14 ? in_root
14 ? in_root
15
15
16 hg status . in repo root:
16 hg status . in repo root:
17
17
18 $ hg status .
18 $ hg status .
19 ? a/1/in_a_1
19 ? a/1/in_a_1
20 ? a/in_a
20 ? a/in_a
21 ? b/1/in_b_1
21 ? b/1/in_b_1
22 ? b/2/in_b_2
22 ? b/2/in_b_2
23 ? b/in_b
23 ? b/in_b
24 ? in_root
24 ? in_root
25
25
26 $ hg status --cwd a
26 $ hg status --cwd a
27 ? a/1/in_a_1
27 ? a/1/in_a_1
28 ? a/in_a
28 ? a/in_a
29 ? b/1/in_b_1
29 ? b/1/in_b_1
30 ? b/2/in_b_2
30 ? b/2/in_b_2
31 ? b/in_b
31 ? b/in_b
32 ? in_root
32 ? in_root
33 $ hg status --cwd a .
33 $ hg status --cwd a .
34 ? 1/in_a_1
34 ? 1/in_a_1
35 ? in_a
35 ? in_a
36 $ hg status --cwd a ..
36 $ hg status --cwd a ..
37 ? 1/in_a_1
37 ? 1/in_a_1
38 ? in_a
38 ? in_a
39 ? ../b/1/in_b_1
39 ? ../b/1/in_b_1
40 ? ../b/2/in_b_2
40 ? ../b/2/in_b_2
41 ? ../b/in_b
41 ? ../b/in_b
42 ? ../in_root
42 ? ../in_root
43
43
44 $ hg status --cwd b
44 $ hg status --cwd b
45 ? a/1/in_a_1
45 ? a/1/in_a_1
46 ? a/in_a
46 ? a/in_a
47 ? b/1/in_b_1
47 ? b/1/in_b_1
48 ? b/2/in_b_2
48 ? b/2/in_b_2
49 ? b/in_b
49 ? b/in_b
50 ? in_root
50 ? in_root
51 $ hg status --cwd b .
51 $ hg status --cwd b .
52 ? 1/in_b_1
52 ? 1/in_b_1
53 ? 2/in_b_2
53 ? 2/in_b_2
54 ? in_b
54 ? in_b
55 $ hg status --cwd b ..
55 $ hg status --cwd b ..
56 ? ../a/1/in_a_1
56 ? ../a/1/in_a_1
57 ? ../a/in_a
57 ? ../a/in_a
58 ? 1/in_b_1
58 ? 1/in_b_1
59 ? 2/in_b_2
59 ? 2/in_b_2
60 ? in_b
60 ? in_b
61 ? ../in_root
61 ? ../in_root
62
62
63 $ hg status --cwd a/1
63 $ hg status --cwd a/1
64 ? a/1/in_a_1
64 ? a/1/in_a_1
65 ? a/in_a
65 ? a/in_a
66 ? b/1/in_b_1
66 ? b/1/in_b_1
67 ? b/2/in_b_2
67 ? b/2/in_b_2
68 ? b/in_b
68 ? b/in_b
69 ? in_root
69 ? in_root
70 $ hg status --cwd a/1 .
70 $ hg status --cwd a/1 .
71 ? in_a_1
71 ? in_a_1
72 $ hg status --cwd a/1 ..
72 $ hg status --cwd a/1 ..
73 ? in_a_1
73 ? in_a_1
74 ? ../in_a
74 ? ../in_a
75
75
76 $ hg status --cwd b/1
76 $ hg status --cwd b/1
77 ? a/1/in_a_1
77 ? a/1/in_a_1
78 ? a/in_a
78 ? a/in_a
79 ? b/1/in_b_1
79 ? b/1/in_b_1
80 ? b/2/in_b_2
80 ? b/2/in_b_2
81 ? b/in_b
81 ? b/in_b
82 ? in_root
82 ? in_root
83 $ hg status --cwd b/1 .
83 $ hg status --cwd b/1 .
84 ? in_b_1
84 ? in_b_1
85 $ hg status --cwd b/1 ..
85 $ hg status --cwd b/1 ..
86 ? in_b_1
86 ? in_b_1
87 ? ../2/in_b_2
87 ? ../2/in_b_2
88 ? ../in_b
88 ? ../in_b
89
89
90 $ hg status --cwd b/2
90 $ hg status --cwd b/2
91 ? a/1/in_a_1
91 ? a/1/in_a_1
92 ? a/in_a
92 ? a/in_a
93 ? b/1/in_b_1
93 ? b/1/in_b_1
94 ? b/2/in_b_2
94 ? b/2/in_b_2
95 ? b/in_b
95 ? b/in_b
96 ? in_root
96 ? in_root
97 $ hg status --cwd b/2 .
97 $ hg status --cwd b/2 .
98 ? in_b_2
98 ? in_b_2
99 $ hg status --cwd b/2 ..
99 $ hg status --cwd b/2 ..
100 ? ../1/in_b_1
100 ? ../1/in_b_1
101 ? in_b_2
101 ? in_b_2
102 ? ../in_b
102 ? ../in_b
103
103
104 combining patterns with root and patterns without a root works
104 combining patterns with root and patterns without a root works
105
105
106 $ hg st a/in_a re:.*b$
106 $ hg st a/in_a re:.*b$
107 ? a/in_a
107 ? a/in_a
108 ? b/in_b
108 ? b/in_b
109
109
110 tweaking defaults works
110 tweaking defaults works
111 $ hg status --cwd a --config ui.tweakdefaults=yes
111 $ hg status --cwd a --config ui.tweakdefaults=yes
112 ? 1/in_a_1
112 ? 1/in_a_1
113 ? in_a
113 ? in_a
114 ? ../b/1/in_b_1
114 ? ../b/1/in_b_1
115 ? ../b/2/in_b_2
115 ? ../b/2/in_b_2
116 ? ../b/in_b
116 ? ../b/in_b
117 ? ../in_root
117 ? ../in_root
118 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
118 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
119 ? a/1/in_a_1 (glob)
119 ? a/1/in_a_1 (glob)
120 ? a/in_a (glob)
120 ? a/in_a (glob)
121 ? b/1/in_b_1 (glob)
121 ? b/1/in_b_1 (glob)
122 ? b/2/in_b_2 (glob)
122 ? b/2/in_b_2 (glob)
123 ? b/in_b (glob)
123 ? b/in_b (glob)
124 ? in_root
124 ? in_root
125 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
125 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
126 ? 1/in_a_1
126 ? 1/in_a_1
127 ? in_a
127 ? in_a
128 ? ../b/1/in_b_1
128 ? ../b/1/in_b_1
129 ? ../b/2/in_b_2
129 ? ../b/2/in_b_2
130 ? ../b/in_b
130 ? ../b/in_b
131 ? ../in_root (glob)
131 ? ../in_root (glob)
132
132
133 relative paths can be requested
133 relative paths can be requested
134
134
135 $ hg status --cwd a --config ui.relative-paths=yes
135 $ hg status --cwd a --config ui.relative-paths=yes
136 ? 1/in_a_1
136 ? 1/in_a_1
137 ? in_a
137 ? in_a
138 ? ../b/1/in_b_1
138 ? ../b/1/in_b_1
139 ? ../b/2/in_b_2
139 ? ../b/2/in_b_2
140 ? ../b/in_b
140 ? ../b/in_b
141 ? ../in_root
141 ? ../in_root
142
142
143 $ hg status --cwd a . --config ui.relative-paths=legacy
143 $ hg status --cwd a . --config ui.relative-paths=legacy
144 ? 1/in_a_1
144 ? 1/in_a_1
145 ? in_a
145 ? in_a
146 $ hg status --cwd a . --config ui.relative-paths=no
146 $ hg status --cwd a . --config ui.relative-paths=no
147 ? a/1/in_a_1
147 ? a/1/in_a_1
148 ? a/in_a
148 ? a/in_a
149
149
150 commands.status.relative overrides ui.relative-paths
150 commands.status.relative overrides ui.relative-paths
151
151
152 $ cat >> $HGRCPATH <<EOF
152 $ cat >> $HGRCPATH <<EOF
153 > [ui]
153 > [ui]
154 > relative-paths = False
154 > relative-paths = False
155 > [commands]
155 > [commands]
156 > status.relative = True
156 > status.relative = True
157 > EOF
157 > EOF
158 $ hg status --cwd a
158 $ hg status --cwd a
159 ? 1/in_a_1
159 ? 1/in_a_1
160 ? in_a
160 ? in_a
161 ? ../b/1/in_b_1
161 ? ../b/1/in_b_1
162 ? ../b/2/in_b_2
162 ? ../b/2/in_b_2
163 ? ../b/in_b
163 ? ../b/in_b
164 ? ../in_root
164 ? ../in_root
165 $ HGPLAIN=1 hg status --cwd a
165 $ HGPLAIN=1 hg status --cwd a
166 ? a/1/in_a_1 (glob)
166 ? a/1/in_a_1 (glob)
167 ? a/in_a (glob)
167 ? a/in_a (glob)
168 ? b/1/in_b_1 (glob)
168 ? b/1/in_b_1 (glob)
169 ? b/2/in_b_2 (glob)
169 ? b/2/in_b_2 (glob)
170 ? b/in_b (glob)
170 ? b/in_b (glob)
171 ? in_root
171 ? in_root
172
172
173 if relative paths are explicitly off, tweakdefaults doesn't change it
173 if relative paths are explicitly off, tweakdefaults doesn't change it
174 $ cat >> $HGRCPATH <<EOF
174 $ cat >> $HGRCPATH <<EOF
175 > [commands]
175 > [commands]
176 > status.relative = False
176 > status.relative = False
177 > EOF
177 > EOF
178 $ hg status --cwd a --config ui.tweakdefaults=yes
178 $ hg status --cwd a --config ui.tweakdefaults=yes
179 ? a/1/in_a_1
179 ? a/1/in_a_1
180 ? a/in_a
180 ? a/in_a
181 ? b/1/in_b_1
181 ? b/1/in_b_1
182 ? b/2/in_b_2
182 ? b/2/in_b_2
183 ? b/in_b
183 ? b/in_b
184 ? in_root
184 ? in_root
185
185
186 $ cd ..
186 $ cd ..
187
187
188 $ hg init repo2
188 $ hg init repo2
189 $ cd repo2
189 $ cd repo2
190 $ touch modified removed deleted ignored
190 $ touch modified removed deleted ignored
191 $ echo "^ignored$" > .hgignore
191 $ echo "^ignored$" > .hgignore
192 $ hg ci -A -m 'initial checkin'
192 $ hg ci -A -m 'initial checkin'
193 adding .hgignore
193 adding .hgignore
194 adding deleted
194 adding deleted
195 adding modified
195 adding modified
196 adding removed
196 adding removed
197 $ touch modified added unknown ignored
197 $ touch modified added unknown ignored
198 $ hg add added
198 $ hg add added
199 $ hg remove removed
199 $ hg remove removed
200 $ rm deleted
200 $ rm deleted
201
201
202 hg status:
202 hg status:
203
203
204 $ hg status
204 $ hg status
205 A added
205 A added
206 R removed
206 R removed
207 ! deleted
207 ! deleted
208 ? unknown
208 ? unknown
209
209
210 hg status modified added removed deleted unknown never-existed ignored:
210 hg status modified added removed deleted unknown never-existed ignored:
211
211
212 $ hg status modified added removed deleted unknown never-existed ignored
212 $ hg status modified added removed deleted unknown never-existed ignored
213 never-existed: * (glob)
213 never-existed: * (glob)
214 A added
214 A added
215 R removed
215 R removed
216 ! deleted
216 ! deleted
217 ? unknown
217 ? unknown
218
218
219 $ hg copy modified copied
219 $ hg copy modified copied
220
220
221 hg status -C:
221 hg status -C:
222
222
223 $ hg status -C
223 $ hg status -C
224 A added
224 A added
225 A copied
225 A copied
226 modified
226 modified
227 R removed
227 R removed
228 ! deleted
228 ! deleted
229 ? unknown
229 ? unknown
230
230
231 hg status -A:
231 hg status -A:
232
232
233 $ hg status -A
233 $ hg status -A
234 A added
234 A added
235 A copied
235 A copied
236 modified
236 modified
237 R removed
237 R removed
238 ! deleted
238 ! deleted
239 ? unknown
239 ? unknown
240 I ignored
240 I ignored
241 C .hgignore
241 C .hgignore
242 C modified
242 C modified
243
243
244 $ hg status -A -T '{status} {path} {node|shortest}\n'
244 $ hg status -A -T '{status} {path} {node|shortest}\n'
245 A added ffff
245 A added ffff
246 A copied ffff
246 A copied ffff
247 R removed ffff
247 R removed ffff
248 ! deleted ffff
248 ! deleted ffff
249 ? unknown ffff
249 ? unknown ffff
250 I ignored ffff
250 I ignored ffff
251 C .hgignore ffff
251 C .hgignore ffff
252 C modified ffff
252 C modified ffff
253
253
254 $ hg status -A -Tjson
254 $ hg status -A -Tjson
255 [
255 [
256 {
256 {
257 "itemtype": "file",
257 "path": "added",
258 "path": "added",
258 "status": "A"
259 "status": "A"
259 },
260 },
260 {
261 {
262 "itemtype": "file",
261 "path": "copied",
263 "path": "copied",
262 "source": "modified",
264 "source": "modified",
263 "status": "A"
265 "status": "A"
264 },
266 },
265 {
267 {
268 "itemtype": "file",
266 "path": "removed",
269 "path": "removed",
267 "status": "R"
270 "status": "R"
268 },
271 },
269 {
272 {
273 "itemtype": "file",
270 "path": "deleted",
274 "path": "deleted",
271 "status": "!"
275 "status": "!"
272 },
276 },
273 {
277 {
278 "itemtype": "file",
274 "path": "unknown",
279 "path": "unknown",
275 "status": "?"
280 "status": "?"
276 },
281 },
277 {
282 {
283 "itemtype": "file",
278 "path": "ignored",
284 "path": "ignored",
279 "status": "I"
285 "status": "I"
280 },
286 },
281 {
287 {
288 "itemtype": "file",
282 "path": ".hgignore",
289 "path": ".hgignore",
283 "status": "C"
290 "status": "C"
284 },
291 },
285 {
292 {
293 "itemtype": "file",
286 "path": "modified",
294 "path": "modified",
287 "status": "C"
295 "status": "C"
288 }
296 }
289 ]
297 ]
290
298
291 $ hg status -A -Tpickle > pickle
299 $ hg status -A -Tpickle > pickle
292 >>> from __future__ import print_function
300 >>> from __future__ import print_function
293 >>> from mercurial import util
301 >>> from mercurial import util
294 >>> pickle = util.pickle
302 >>> pickle = util.pickle
295 >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
303 >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
296 >>> for s, p in data: print("%s %s" % (s, p))
304 >>> for s, p in data: print("%s %s" % (s, p))
297 ! deleted
305 ! deleted
298 ? pickle
306 ? pickle
299 ? unknown
307 ? unknown
300 A added
308 A added
301 A copied
309 A copied
302 C .hgignore
310 C .hgignore
303 C modified
311 C modified
304 I ignored
312 I ignored
305 R removed
313 R removed
306 $ rm pickle
314 $ rm pickle
307
315
308 $ echo "^ignoreddir$" > .hgignore
316 $ echo "^ignoreddir$" > .hgignore
309 $ mkdir ignoreddir
317 $ mkdir ignoreddir
310 $ touch ignoreddir/file
318 $ touch ignoreddir/file
311
319
312 Test templater support:
320 Test templater support:
313
321
314 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
322 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
315 [M] .hgignore
323 [M] .hgignore
316 [A] added
324 [A] added
317 [A] modified -> copied
325 [A] modified -> copied
318 [R] removed
326 [R] removed
319 [!] deleted
327 [!] deleted
320 [?] ignored
328 [?] ignored
321 [?] unknown
329 [?] unknown
322 [I] ignoreddir/file
330 [I] ignoreddir/file
323 [C] modified
331 [C] modified
324 $ hg status -AT default
332 $ hg status -AT default
325 M .hgignore
333 M .hgignore
326 A added
334 A added
327 A copied
335 A copied
328 modified
336 modified
329 R removed
337 R removed
330 ! deleted
338 ! deleted
331 ? ignored
339 ? ignored
332 ? unknown
340 ? unknown
333 I ignoreddir/file
341 I ignoreddir/file
334 C modified
342 C modified
335 $ hg status -T compact
343 $ hg status -T compact
336 abort: "status" not in template map
344 abort: "status" not in template map
337 [255]
345 [255]
338
346
339 hg status ignoreddir/file:
347 hg status ignoreddir/file:
340
348
341 $ hg status ignoreddir/file
349 $ hg status ignoreddir/file
342
350
343 hg status -i ignoreddir/file:
351 hg status -i ignoreddir/file:
344
352
345 $ hg status -i ignoreddir/file
353 $ hg status -i ignoreddir/file
346 I ignoreddir/file
354 I ignoreddir/file
347 $ cd ..
355 $ cd ..
348
356
349 Check 'status -q' and some combinations
357 Check 'status -q' and some combinations
350
358
351 $ hg init repo3
359 $ hg init repo3
352 $ cd repo3
360 $ cd repo3
353 $ touch modified removed deleted ignored
361 $ touch modified removed deleted ignored
354 $ echo "^ignored$" > .hgignore
362 $ echo "^ignored$" > .hgignore
355 $ hg commit -A -m 'initial checkin'
363 $ hg commit -A -m 'initial checkin'
356 adding .hgignore
364 adding .hgignore
357 adding deleted
365 adding deleted
358 adding modified
366 adding modified
359 adding removed
367 adding removed
360 $ touch added unknown ignored
368 $ touch added unknown ignored
361 $ hg add added
369 $ hg add added
362 $ echo "test" >> modified
370 $ echo "test" >> modified
363 $ hg remove removed
371 $ hg remove removed
364 $ rm deleted
372 $ rm deleted
365 $ hg copy modified copied
373 $ hg copy modified copied
366
374
367 Specify working directory revision explicitly, that should be the same as
375 Specify working directory revision explicitly, that should be the same as
368 "hg status"
376 "hg status"
369
377
370 $ hg status --change "wdir()"
378 $ hg status --change "wdir()"
371 M modified
379 M modified
372 A added
380 A added
373 A copied
381 A copied
374 R removed
382 R removed
375 ! deleted
383 ! deleted
376 ? unknown
384 ? unknown
377
385
378 Run status with 2 different flags.
386 Run status with 2 different flags.
379 Check if result is the same or different.
387 Check if result is the same or different.
380 If result is not as expected, raise error
388 If result is not as expected, raise error
381
389
382 $ assert() {
390 $ assert() {
383 > hg status $1 > ../a
391 > hg status $1 > ../a
384 > hg status $2 > ../b
392 > hg status $2 > ../b
385 > if diff ../a ../b > /dev/null; then
393 > if diff ../a ../b > /dev/null; then
386 > out=0
394 > out=0
387 > else
395 > else
388 > out=1
396 > out=1
389 > fi
397 > fi
390 > if [ $3 -eq 0 ]; then
398 > if [ $3 -eq 0 ]; then
391 > df="same"
399 > df="same"
392 > else
400 > else
393 > df="different"
401 > df="different"
394 > fi
402 > fi
395 > if [ $out -ne $3 ]; then
403 > if [ $out -ne $3 ]; then
396 > echo "Error on $1 and $2, should be $df."
404 > echo "Error on $1 and $2, should be $df."
397 > fi
405 > fi
398 > }
406 > }
399
407
400 Assert flag1 flag2 [0-same | 1-different]
408 Assert flag1 flag2 [0-same | 1-different]
401
409
402 $ assert "-q" "-mard" 0
410 $ assert "-q" "-mard" 0
403 $ assert "-A" "-marduicC" 0
411 $ assert "-A" "-marduicC" 0
404 $ assert "-qA" "-mardcC" 0
412 $ assert "-qA" "-mardcC" 0
405 $ assert "-qAui" "-A" 0
413 $ assert "-qAui" "-A" 0
406 $ assert "-qAu" "-marducC" 0
414 $ assert "-qAu" "-marducC" 0
407 $ assert "-qAi" "-mardicC" 0
415 $ assert "-qAi" "-mardicC" 0
408 $ assert "-qu" "-u" 0
416 $ assert "-qu" "-u" 0
409 $ assert "-q" "-u" 1
417 $ assert "-q" "-u" 1
410 $ assert "-m" "-a" 1
418 $ assert "-m" "-a" 1
411 $ assert "-r" "-d" 1
419 $ assert "-r" "-d" 1
412 $ cd ..
420 $ cd ..
413
421
414 $ hg init repo4
422 $ hg init repo4
415 $ cd repo4
423 $ cd repo4
416 $ touch modified removed deleted
424 $ touch modified removed deleted
417 $ hg ci -q -A -m 'initial checkin'
425 $ hg ci -q -A -m 'initial checkin'
418 $ touch added unknown
426 $ touch added unknown
419 $ hg add added
427 $ hg add added
420 $ hg remove removed
428 $ hg remove removed
421 $ rm deleted
429 $ rm deleted
422 $ echo x > modified
430 $ echo x > modified
423 $ hg copy modified copied
431 $ hg copy modified copied
424 $ hg ci -m 'test checkin' -d "1000001 0"
432 $ hg ci -m 'test checkin' -d "1000001 0"
425 $ rm *
433 $ rm *
426 $ touch unrelated
434 $ touch unrelated
427 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
435 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
428
436
429 hg status --change 1:
437 hg status --change 1:
430
438
431 $ hg status --change 1
439 $ hg status --change 1
432 M modified
440 M modified
433 A added
441 A added
434 A copied
442 A copied
435 R removed
443 R removed
436
444
437 hg status --change 1 unrelated:
445 hg status --change 1 unrelated:
438
446
439 $ hg status --change 1 unrelated
447 $ hg status --change 1 unrelated
440
448
441 hg status -C --change 1 added modified copied removed deleted:
449 hg status -C --change 1 added modified copied removed deleted:
442
450
443 $ hg status -C --change 1 added modified copied removed deleted
451 $ hg status -C --change 1 added modified copied removed deleted
444 M modified
452 M modified
445 A added
453 A added
446 A copied
454 A copied
447 modified
455 modified
448 R removed
456 R removed
449
457
450 hg status -A --change 1 and revset:
458 hg status -A --change 1 and revset:
451
459
452 $ hg status -A --change '1|1'
460 $ hg status -A --change '1|1'
453 M modified
461 M modified
454 A added
462 A added
455 A copied
463 A copied
456 modified
464 modified
457 R removed
465 R removed
458 C deleted
466 C deleted
459
467
460 $ cd ..
468 $ cd ..
461
469
462 hg status with --rev and reverted changes:
470 hg status with --rev and reverted changes:
463
471
464 $ hg init reverted-changes-repo
472 $ hg init reverted-changes-repo
465 $ cd reverted-changes-repo
473 $ cd reverted-changes-repo
466 $ echo a > file
474 $ echo a > file
467 $ hg add file
475 $ hg add file
468 $ hg ci -m a
476 $ hg ci -m a
469 $ echo b > file
477 $ echo b > file
470 $ hg ci -m b
478 $ hg ci -m b
471
479
472 reverted file should appear clean
480 reverted file should appear clean
473
481
474 $ hg revert -r 0 .
482 $ hg revert -r 0 .
475 reverting file
483 reverting file
476 $ hg status -A --rev 0
484 $ hg status -A --rev 0
477 C file
485 C file
478
486
479 #if execbit
487 #if execbit
480 reverted file with changed flag should appear modified
488 reverted file with changed flag should appear modified
481
489
482 $ chmod +x file
490 $ chmod +x file
483 $ hg status -A --rev 0
491 $ hg status -A --rev 0
484 M file
492 M file
485
493
486 $ hg revert -r 0 .
494 $ hg revert -r 0 .
487 reverting file
495 reverting file
488
496
489 reverted and committed file with changed flag should appear modified
497 reverted and committed file with changed flag should appear modified
490
498
491 $ hg co -C .
499 $ hg co -C .
492 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
500 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
493 $ chmod +x file
501 $ chmod +x file
494 $ hg ci -m 'change flag'
502 $ hg ci -m 'change flag'
495 $ hg status -A --rev 1 --rev 2
503 $ hg status -A --rev 1 --rev 2
496 M file
504 M file
497 $ hg diff -r 1 -r 2
505 $ hg diff -r 1 -r 2
498
506
499 #endif
507 #endif
500
508
501 $ cd ..
509 $ cd ..
502
510
503 hg status of binary file starting with '\1\n', a separator for metadata:
511 hg status of binary file starting with '\1\n', a separator for metadata:
504
512
505 $ hg init repo5
513 $ hg init repo5
506 $ cd repo5
514 $ cd repo5
507 >>> open("010a", r"wb").write(b"\1\nfoo") and None
515 >>> open("010a", r"wb").write(b"\1\nfoo") and None
508 $ hg ci -q -A -m 'initial checkin'
516 $ hg ci -q -A -m 'initial checkin'
509 $ hg status -A
517 $ hg status -A
510 C 010a
518 C 010a
511
519
512 >>> open("010a", r"wb").write(b"\1\nbar") and None
520 >>> open("010a", r"wb").write(b"\1\nbar") and None
513 $ hg status -A
521 $ hg status -A
514 M 010a
522 M 010a
515 $ hg ci -q -m 'modify 010a'
523 $ hg ci -q -m 'modify 010a'
516 $ hg status -A --rev 0:1
524 $ hg status -A --rev 0:1
517 M 010a
525 M 010a
518
526
519 $ touch empty
527 $ touch empty
520 $ hg ci -q -A -m 'add another file'
528 $ hg ci -q -A -m 'add another file'
521 $ hg status -A --rev 1:2 010a
529 $ hg status -A --rev 1:2 010a
522 C 010a
530 C 010a
523
531
524 $ cd ..
532 $ cd ..
525
533
526 test "hg status" with "directory pattern" which matches against files
534 test "hg status" with "directory pattern" which matches against files
527 only known on target revision.
535 only known on target revision.
528
536
529 $ hg init repo6
537 $ hg init repo6
530 $ cd repo6
538 $ cd repo6
531
539
532 $ echo a > a.txt
540 $ echo a > a.txt
533 $ hg add a.txt
541 $ hg add a.txt
534 $ hg commit -m '#0'
542 $ hg commit -m '#0'
535 $ mkdir -p 1/2/3/4/5
543 $ mkdir -p 1/2/3/4/5
536 $ echo b > 1/2/3/4/5/b.txt
544 $ echo b > 1/2/3/4/5/b.txt
537 $ hg add 1/2/3/4/5/b.txt
545 $ hg add 1/2/3/4/5/b.txt
538 $ hg commit -m '#1'
546 $ hg commit -m '#1'
539
547
540 $ hg update -C 0 > /dev/null
548 $ hg update -C 0 > /dev/null
541 $ hg status -A
549 $ hg status -A
542 C a.txt
550 C a.txt
543
551
544 the directory matching against specified pattern should be removed,
552 the directory matching against specified pattern should be removed,
545 because directory existence prevents 'dirstate.walk()' from showing
553 because directory existence prevents 'dirstate.walk()' from showing
546 warning message about such pattern.
554 warning message about such pattern.
547
555
548 $ test ! -d 1
556 $ test ! -d 1
549 $ hg status -A --rev 1 1/2/3/4/5/b.txt
557 $ hg status -A --rev 1 1/2/3/4/5/b.txt
550 R 1/2/3/4/5/b.txt
558 R 1/2/3/4/5/b.txt
551 $ hg status -A --rev 1 1/2/3/4/5
559 $ hg status -A --rev 1 1/2/3/4/5
552 R 1/2/3/4/5/b.txt
560 R 1/2/3/4/5/b.txt
553 $ hg status -A --rev 1 1/2/3
561 $ hg status -A --rev 1 1/2/3
554 R 1/2/3/4/5/b.txt
562 R 1/2/3/4/5/b.txt
555 $ hg status -A --rev 1 1
563 $ hg status -A --rev 1 1
556 R 1/2/3/4/5/b.txt
564 R 1/2/3/4/5/b.txt
557
565
558 $ hg status --config ui.formatdebug=True --rev 1 1
566 $ hg status --config ui.formatdebug=True --rev 1 1
559 status = [
567 status = [
560 {
568 {
569 'itemtype': 'file',
561 'path': '1/2/3/4/5/b.txt',
570 'path': '1/2/3/4/5/b.txt',
562 'status': 'R'
571 'status': 'R'
563 },
572 },
564 ]
573 ]
565
574
566 #if windows
575 #if windows
567 $ hg --config ui.slash=false status -A --rev 1 1
576 $ hg --config ui.slash=false status -A --rev 1 1
568 R 1\2\3\4\5\b.txt
577 R 1\2\3\4\5\b.txt
569 #endif
578 #endif
570
579
571 $ cd ..
580 $ cd ..
572
581
573 Status after move overwriting a file (issue4458)
582 Status after move overwriting a file (issue4458)
574 =================================================
583 =================================================
575
584
576
585
577 $ hg init issue4458
586 $ hg init issue4458
578 $ cd issue4458
587 $ cd issue4458
579 $ echo a > a
588 $ echo a > a
580 $ echo b > b
589 $ echo b > b
581 $ hg commit -Am base
590 $ hg commit -Am base
582 adding a
591 adding a
583 adding b
592 adding b
584
593
585
594
586 with --force
595 with --force
587
596
588 $ hg mv b --force a
597 $ hg mv b --force a
589 $ hg st --copies
598 $ hg st --copies
590 M a
599 M a
591 b
600 b
592 R b
601 R b
593 $ hg revert --all
602 $ hg revert --all
594 reverting a
603 reverting a
595 undeleting b
604 undeleting b
596 $ rm *.orig
605 $ rm *.orig
597
606
598 without force
607 without force
599
608
600 $ hg rm a
609 $ hg rm a
601 $ hg st --copies
610 $ hg st --copies
602 R a
611 R a
603 $ hg mv b a
612 $ hg mv b a
604 $ hg st --copies
613 $ hg st --copies
605 M a
614 M a
606 b
615 b
607 R b
616 R b
608
617
609 using ui.statuscopies setting
618 using ui.statuscopies setting
610 $ hg st --config ui.statuscopies=true
619 $ hg st --config ui.statuscopies=true
611 M a
620 M a
612 b
621 b
613 R b
622 R b
614 $ hg st --config ui.statuscopies=false
623 $ hg st --config ui.statuscopies=false
615 M a
624 M a
616 R b
625 R b
617 $ hg st --config ui.tweakdefaults=yes
626 $ hg st --config ui.tweakdefaults=yes
618 M a
627 M a
619 b
628 b
620 R b
629 R b
621
630
622 using log status template (issue5155)
631 using log status template (issue5155)
623 $ hg log -Tstatus -r 'wdir()' -C
632 $ hg log -Tstatus -r 'wdir()' -C
624 changeset: 2147483647:ffffffffffff
633 changeset: 2147483647:ffffffffffff
625 parent: 0:8c55c58b4c0e
634 parent: 0:8c55c58b4c0e
626 user: test
635 user: test
627 date: * (glob)
636 date: * (glob)
628 files:
637 files:
629 M a
638 M a
630 b
639 b
631 R b
640 R b
632
641
633 $ hg log -GTstatus -r 'wdir()' -C
642 $ hg log -GTstatus -r 'wdir()' -C
634 o changeset: 2147483647:ffffffffffff
643 o changeset: 2147483647:ffffffffffff
635 | parent: 0:8c55c58b4c0e
644 | parent: 0:8c55c58b4c0e
636 ~ user: test
645 ~ user: test
637 date: * (glob)
646 date: * (glob)
638 files:
647 files:
639 M a
648 M a
640 b
649 b
641 R b
650 R b
642
651
643
652
644 Other "bug" highlight, the revision status does not report the copy information.
653 Other "bug" highlight, the revision status does not report the copy information.
645 This is buggy behavior.
654 This is buggy behavior.
646
655
647 $ hg commit -m 'blah'
656 $ hg commit -m 'blah'
648 $ hg st --copies --change .
657 $ hg st --copies --change .
649 M a
658 M a
650 R b
659 R b
651
660
652 using log status template, the copy information is displayed correctly.
661 using log status template, the copy information is displayed correctly.
653 $ hg log -Tstatus -r. -C
662 $ hg log -Tstatus -r. -C
654 changeset: 1:6685fde43d21
663 changeset: 1:6685fde43d21
655 tag: tip
664 tag: tip
656 user: test
665 user: test
657 date: * (glob)
666 date: * (glob)
658 summary: blah
667 summary: blah
659 files:
668 files:
660 M a
669 M a
661 b
670 b
662 R b
671 R b
663
672
664
673
665 $ cd ..
674 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now