##// END OF EJS Templates
command: automatically create alias for command using '-' in names...
marmoute -
r46858:012e25ab default
parent child Browse files
Show More
@@ -1,3914 +1,3924
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 short,
19 short,
20 )
20 )
21 from .pycompat import (
21 from .pycompat import (
22 getattr,
22 getattr,
23 open,
23 open,
24 setattr,
24 setattr,
25 )
25 )
26 from .thirdparty import attr
26 from .thirdparty import attr
27
27
28 from . import (
28 from . import (
29 bookmarks,
29 bookmarks,
30 changelog,
30 changelog,
31 copies,
31 copies,
32 crecord as crecordmod,
32 crecord as crecordmod,
33 dirstateguard,
33 dirstateguard,
34 encoding,
34 encoding,
35 error,
35 error,
36 formatter,
36 formatter,
37 logcmdutil,
37 logcmdutil,
38 match as matchmod,
38 match as matchmod,
39 merge as mergemod,
39 merge as mergemod,
40 mergestate as mergestatemod,
40 mergestate as mergestatemod,
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 state as statemod,
51 state as statemod,
52 subrepoutil,
52 subrepoutil,
53 templatekw,
53 templatekw,
54 templater,
54 templater,
55 util,
55 util,
56 vfs as vfsmod,
56 vfs as vfsmod,
57 )
57 )
58
58
59 from .utils import (
59 from .utils import (
60 dateutil,
60 dateutil,
61 stringutil,
61 stringutil,
62 )
62 )
63
63
64 if pycompat.TYPE_CHECKING:
64 if pycompat.TYPE_CHECKING:
65 from typing import (
65 from typing import (
66 Any,
66 Any,
67 Dict,
67 Dict,
68 )
68 )
69
69
70 for t in (Any, Dict):
70 for t in (Any, Dict):
71 assert t
71 assert t
72
72
73 stringio = util.stringio
73 stringio = util.stringio
74
74
75 # templates of common command options
75 # templates of common command options
76
76
77 dryrunopts = [
77 dryrunopts = [
78 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
78 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
79 ]
79 ]
80
80
81 confirmopts = [
81 confirmopts = [
82 (b'', b'confirm', None, _(b'ask before applying actions')),
82 (b'', b'confirm', None, _(b'ask before applying actions')),
83 ]
83 ]
84
84
85 remoteopts = [
85 remoteopts = [
86 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
86 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
87 (
87 (
88 b'',
88 b'',
89 b'remotecmd',
89 b'remotecmd',
90 b'',
90 b'',
91 _(b'specify hg command to run on the remote side'),
91 _(b'specify hg command to run on the remote side'),
92 _(b'CMD'),
92 _(b'CMD'),
93 ),
93 ),
94 (
94 (
95 b'',
95 b'',
96 b'insecure',
96 b'insecure',
97 None,
97 None,
98 _(b'do not verify server certificate (ignoring web.cacerts config)'),
98 _(b'do not verify server certificate (ignoring web.cacerts config)'),
99 ),
99 ),
100 ]
100 ]
101
101
102 walkopts = [
102 walkopts = [
103 (
103 (
104 b'I',
104 b'I',
105 b'include',
105 b'include',
106 [],
106 [],
107 _(b'include names matching the given patterns'),
107 _(b'include names matching the given patterns'),
108 _(b'PATTERN'),
108 _(b'PATTERN'),
109 ),
109 ),
110 (
110 (
111 b'X',
111 b'X',
112 b'exclude',
112 b'exclude',
113 [],
113 [],
114 _(b'exclude names matching the given patterns'),
114 _(b'exclude names matching the given patterns'),
115 _(b'PATTERN'),
115 _(b'PATTERN'),
116 ),
116 ),
117 ]
117 ]
118
118
119 commitopts = [
119 commitopts = [
120 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
120 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
121 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
121 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
122 ]
122 ]
123
123
124 commitopts2 = [
124 commitopts2 = [
125 (
125 (
126 b'd',
126 b'd',
127 b'date',
127 b'date',
128 b'',
128 b'',
129 _(b'record the specified date as commit date'),
129 _(b'record the specified date as commit date'),
130 _(b'DATE'),
130 _(b'DATE'),
131 ),
131 ),
132 (
132 (
133 b'u',
133 b'u',
134 b'user',
134 b'user',
135 b'',
135 b'',
136 _(b'record the specified user as committer'),
136 _(b'record the specified user as committer'),
137 _(b'USER'),
137 _(b'USER'),
138 ),
138 ),
139 ]
139 ]
140
140
141 commitopts3 = [
141 commitopts3 = [
142 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
142 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
143 (b'U', b'currentuser', None, _(b'record the current user as committer')),
143 (b'U', b'currentuser', None, _(b'record the current user as committer')),
144 ]
144 ]
145
145
146 formatteropts = [
146 formatteropts = [
147 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
147 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
148 ]
148 ]
149
149
150 templateopts = [
150 templateopts = [
151 (
151 (
152 b'',
152 b'',
153 b'style',
153 b'style',
154 b'',
154 b'',
155 _(b'display using template map file (DEPRECATED)'),
155 _(b'display using template map file (DEPRECATED)'),
156 _(b'STYLE'),
156 _(b'STYLE'),
157 ),
157 ),
158 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
158 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
159 ]
159 ]
160
160
161 logopts = [
161 logopts = [
162 (b'p', b'patch', None, _(b'show patch')),
162 (b'p', b'patch', None, _(b'show patch')),
163 (b'g', b'git', None, _(b'use git extended diff format')),
163 (b'g', b'git', None, _(b'use git extended diff format')),
164 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
164 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
165 (b'M', b'no-merges', None, _(b'do not show merges')),
165 (b'M', b'no-merges', None, _(b'do not show merges')),
166 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
166 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
167 (b'G', b'graph', None, _(b"show the revision DAG")),
167 (b'G', b'graph', None, _(b"show the revision DAG")),
168 ] + templateopts
168 ] + templateopts
169
169
170 diffopts = [
170 diffopts = [
171 (b'a', b'text', None, _(b'treat all files as text')),
171 (b'a', b'text', None, _(b'treat all files as text')),
172 (
172 (
173 b'g',
173 b'g',
174 b'git',
174 b'git',
175 None,
175 None,
176 _(b'use git extended diff format (DEFAULT: diff.git)'),
176 _(b'use git extended diff format (DEFAULT: diff.git)'),
177 ),
177 ),
178 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
178 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
179 (b'', b'nodates', None, _(b'omit dates from diff headers')),
179 (b'', b'nodates', None, _(b'omit dates from diff headers')),
180 ]
180 ]
181
181
182 diffwsopts = [
182 diffwsopts = [
183 (
183 (
184 b'w',
184 b'w',
185 b'ignore-all-space',
185 b'ignore-all-space',
186 None,
186 None,
187 _(b'ignore white space when comparing lines'),
187 _(b'ignore white space when comparing lines'),
188 ),
188 ),
189 (
189 (
190 b'b',
190 b'b',
191 b'ignore-space-change',
191 b'ignore-space-change',
192 None,
192 None,
193 _(b'ignore changes in the amount of white space'),
193 _(b'ignore changes in the amount of white space'),
194 ),
194 ),
195 (
195 (
196 b'B',
196 b'B',
197 b'ignore-blank-lines',
197 b'ignore-blank-lines',
198 None,
198 None,
199 _(b'ignore changes whose lines are all blank'),
199 _(b'ignore changes whose lines are all blank'),
200 ),
200 ),
201 (
201 (
202 b'Z',
202 b'Z',
203 b'ignore-space-at-eol',
203 b'ignore-space-at-eol',
204 None,
204 None,
205 _(b'ignore changes in whitespace at EOL'),
205 _(b'ignore changes in whitespace at EOL'),
206 ),
206 ),
207 ]
207 ]
208
208
209 diffopts2 = (
209 diffopts2 = (
210 [
210 [
211 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
211 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
212 (
212 (
213 b'p',
213 b'p',
214 b'show-function',
214 b'show-function',
215 None,
215 None,
216 _(
216 _(
217 b'show which function each change is in (DEFAULT: diff.showfunc)'
217 b'show which function each change is in (DEFAULT: diff.showfunc)'
218 ),
218 ),
219 ),
219 ),
220 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
220 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
221 ]
221 ]
222 + diffwsopts
222 + diffwsopts
223 + [
223 + [
224 (
224 (
225 b'U',
225 b'U',
226 b'unified',
226 b'unified',
227 b'',
227 b'',
228 _(b'number of lines of context to show'),
228 _(b'number of lines of context to show'),
229 _(b'NUM'),
229 _(b'NUM'),
230 ),
230 ),
231 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
231 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
232 (
232 (
233 b'',
233 b'',
234 b'root',
234 b'root',
235 b'',
235 b'',
236 _(b'produce diffs relative to subdirectory'),
236 _(b'produce diffs relative to subdirectory'),
237 _(b'DIR'),
237 _(b'DIR'),
238 ),
238 ),
239 ]
239 ]
240 )
240 )
241
241
242 mergetoolopts = [
242 mergetoolopts = [
243 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
243 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
244 ]
244 ]
245
245
246 similarityopts = [
246 similarityopts = [
247 (
247 (
248 b's',
248 b's',
249 b'similarity',
249 b'similarity',
250 b'',
250 b'',
251 _(b'guess renamed files by similarity (0<=s<=100)'),
251 _(b'guess renamed files by similarity (0<=s<=100)'),
252 _(b'SIMILARITY'),
252 _(b'SIMILARITY'),
253 )
253 )
254 ]
254 ]
255
255
256 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
256 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
257
257
258 debugrevlogopts = [
258 debugrevlogopts = [
259 (b'c', b'changelog', False, _(b'open changelog')),
259 (b'c', b'changelog', False, _(b'open changelog')),
260 (b'm', b'manifest', False, _(b'open manifest')),
260 (b'm', b'manifest', False, _(b'open manifest')),
261 (b'', b'dir', b'', _(b'open directory manifest')),
261 (b'', b'dir', b'', _(b'open directory manifest')),
262 ]
262 ]
263
263
264 # special string such that everything below this line will be ingored in the
264 # special string such that everything below this line will be ingored in the
265 # editor text
265 # editor text
266 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
266 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
267
267
268
268
269 def check_at_most_one_arg(opts, *args):
269 def check_at_most_one_arg(opts, *args):
270 """abort if more than one of the arguments are in opts
270 """abort if more than one of the arguments are in opts
271
271
272 Returns the unique argument or None if none of them were specified.
272 Returns the unique argument or None if none of them were specified.
273 """
273 """
274
274
275 def to_display(name):
275 def to_display(name):
276 return pycompat.sysbytes(name).replace(b'_', b'-')
276 return pycompat.sysbytes(name).replace(b'_', b'-')
277
277
278 previous = None
278 previous = None
279 for x in args:
279 for x in args:
280 if opts.get(x):
280 if opts.get(x):
281 if previous:
281 if previous:
282 raise error.InputError(
282 raise error.InputError(
283 _(b'cannot specify both --%s and --%s')
283 _(b'cannot specify both --%s and --%s')
284 % (to_display(previous), to_display(x))
284 % (to_display(previous), to_display(x))
285 )
285 )
286 previous = x
286 previous = x
287 return previous
287 return previous
288
288
289
289
290 def check_incompatible_arguments(opts, first, others):
290 def check_incompatible_arguments(opts, first, others):
291 """abort if the first argument is given along with any of the others
291 """abort if the first argument is given along with any of the others
292
292
293 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
293 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
294 among themselves, and they're passed as a single collection.
294 among themselves, and they're passed as a single collection.
295 """
295 """
296 for other in others:
296 for other in others:
297 check_at_most_one_arg(opts, first, other)
297 check_at_most_one_arg(opts, first, other)
298
298
299
299
300 def resolvecommitoptions(ui, opts):
300 def resolvecommitoptions(ui, opts):
301 """modify commit options dict to handle related options
301 """modify commit options dict to handle related options
302
302
303 The return value indicates that ``rewrite.update-timestamp`` is the reason
303 The return value indicates that ``rewrite.update-timestamp`` is the reason
304 the ``date`` option is set.
304 the ``date`` option is set.
305 """
305 """
306 check_at_most_one_arg(opts, b'date', b'currentdate')
306 check_at_most_one_arg(opts, b'date', b'currentdate')
307 check_at_most_one_arg(opts, b'user', b'currentuser')
307 check_at_most_one_arg(opts, b'user', b'currentuser')
308
308
309 datemaydiffer = False # date-only change should be ignored?
309 datemaydiffer = False # date-only change should be ignored?
310
310
311 if opts.get(b'currentdate'):
311 if opts.get(b'currentdate'):
312 opts[b'date'] = b'%d %d' % dateutil.makedate()
312 opts[b'date'] = b'%d %d' % dateutil.makedate()
313 elif (
313 elif (
314 not opts.get(b'date')
314 not opts.get(b'date')
315 and ui.configbool(b'rewrite', b'update-timestamp')
315 and ui.configbool(b'rewrite', b'update-timestamp')
316 and opts.get(b'currentdate') is None
316 and opts.get(b'currentdate') is None
317 ):
317 ):
318 opts[b'date'] = b'%d %d' % dateutil.makedate()
318 opts[b'date'] = b'%d %d' % dateutil.makedate()
319 datemaydiffer = True
319 datemaydiffer = True
320
320
321 if opts.get(b'currentuser'):
321 if opts.get(b'currentuser'):
322 opts[b'user'] = ui.username()
322 opts[b'user'] = ui.username()
323
323
324 return datemaydiffer
324 return datemaydiffer
325
325
326
326
327 def checknotesize(ui, opts):
327 def checknotesize(ui, opts):
328 """ make sure note is of valid format """
328 """ make sure note is of valid format """
329
329
330 note = opts.get(b'note')
330 note = opts.get(b'note')
331 if not note:
331 if not note:
332 return
332 return
333
333
334 if len(note) > 255:
334 if len(note) > 255:
335 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
335 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
336 if b'\n' in note:
336 if b'\n' in note:
337 raise error.InputError(_(b"note cannot contain a newline"))
337 raise error.InputError(_(b"note cannot contain a newline"))
338
338
339
339
340 def ishunk(x):
340 def ishunk(x):
341 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
341 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
342 return isinstance(x, hunkclasses)
342 return isinstance(x, hunkclasses)
343
343
344
344
345 def newandmodified(chunks, originalchunks):
345 def newandmodified(chunks, originalchunks):
346 newlyaddedandmodifiedfiles = set()
346 newlyaddedandmodifiedfiles = set()
347 alsorestore = set()
347 alsorestore = set()
348 for chunk in chunks:
348 for chunk in chunks:
349 if (
349 if (
350 ishunk(chunk)
350 ishunk(chunk)
351 and chunk.header.isnewfile()
351 and chunk.header.isnewfile()
352 and chunk not in originalchunks
352 and chunk not in originalchunks
353 ):
353 ):
354 newlyaddedandmodifiedfiles.add(chunk.header.filename())
354 newlyaddedandmodifiedfiles.add(chunk.header.filename())
355 alsorestore.update(
355 alsorestore.update(
356 set(chunk.header.files()) - {chunk.header.filename()}
356 set(chunk.header.files()) - {chunk.header.filename()}
357 )
357 )
358 return newlyaddedandmodifiedfiles, alsorestore
358 return newlyaddedandmodifiedfiles, alsorestore
359
359
360
360
361 def parsealiases(cmd):
361 def parsealiases(cmd):
362 return cmd.split(b"|")
362 base_aliases = cmd.split(b"|")
363 all_aliases = set(base_aliases)
364 extra_aliases = []
365 for alias in base_aliases:
366 if b'-' in alias:
367 folded_alias = alias.replace(b'-', b'')
368 if folded_alias not in all_aliases:
369 all_aliases.add(folded_alias)
370 extra_aliases.append(folded_alias)
371 base_aliases.extend(extra_aliases)
372 return base_aliases
363
373
364
374
365 def setupwrapcolorwrite(ui):
375 def setupwrapcolorwrite(ui):
366 # wrap ui.write so diff output can be labeled/colorized
376 # wrap ui.write so diff output can be labeled/colorized
367 def wrapwrite(orig, *args, **kw):
377 def wrapwrite(orig, *args, **kw):
368 label = kw.pop('label', b'')
378 label = kw.pop('label', b'')
369 for chunk, l in patch.difflabel(lambda: args):
379 for chunk, l in patch.difflabel(lambda: args):
370 orig(chunk, label=label + l)
380 orig(chunk, label=label + l)
371
381
372 oldwrite = ui.write
382 oldwrite = ui.write
373
383
374 def wrap(*args, **kwargs):
384 def wrap(*args, **kwargs):
375 return wrapwrite(oldwrite, *args, **kwargs)
385 return wrapwrite(oldwrite, *args, **kwargs)
376
386
377 setattr(ui, 'write', wrap)
387 setattr(ui, 'write', wrap)
378 return oldwrite
388 return oldwrite
379
389
380
390
381 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
391 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
382 try:
392 try:
383 if usecurses:
393 if usecurses:
384 if testfile:
394 if testfile:
385 recordfn = crecordmod.testdecorator(
395 recordfn = crecordmod.testdecorator(
386 testfile, crecordmod.testchunkselector
396 testfile, crecordmod.testchunkselector
387 )
397 )
388 else:
398 else:
389 recordfn = crecordmod.chunkselector
399 recordfn = crecordmod.chunkselector
390
400
391 return crecordmod.filterpatch(
401 return crecordmod.filterpatch(
392 ui, originalhunks, recordfn, operation
402 ui, originalhunks, recordfn, operation
393 )
403 )
394 except crecordmod.fallbackerror as e:
404 except crecordmod.fallbackerror as e:
395 ui.warn(b'%s\n' % e)
405 ui.warn(b'%s\n' % e)
396 ui.warn(_(b'falling back to text mode\n'))
406 ui.warn(_(b'falling back to text mode\n'))
397
407
398 return patch.filterpatch(ui, originalhunks, match, operation)
408 return patch.filterpatch(ui, originalhunks, match, operation)
399
409
400
410
401 def recordfilter(ui, originalhunks, match, operation=None):
411 def recordfilter(ui, originalhunks, match, operation=None):
402 """Prompts the user to filter the originalhunks and return a list of
412 """Prompts the user to filter the originalhunks and return a list of
403 selected hunks.
413 selected hunks.
404 *operation* is used for to build ui messages to indicate the user what
414 *operation* is used for to build ui messages to indicate the user what
405 kind of filtering they are doing: reverting, committing, shelving, etc.
415 kind of filtering they are doing: reverting, committing, shelving, etc.
406 (see patch.filterpatch).
416 (see patch.filterpatch).
407 """
417 """
408 usecurses = crecordmod.checkcurses(ui)
418 usecurses = crecordmod.checkcurses(ui)
409 testfile = ui.config(b'experimental', b'crecordtest')
419 testfile = ui.config(b'experimental', b'crecordtest')
410 oldwrite = setupwrapcolorwrite(ui)
420 oldwrite = setupwrapcolorwrite(ui)
411 try:
421 try:
412 newchunks, newopts = filterchunks(
422 newchunks, newopts = filterchunks(
413 ui, originalhunks, usecurses, testfile, match, operation
423 ui, originalhunks, usecurses, testfile, match, operation
414 )
424 )
415 finally:
425 finally:
416 ui.write = oldwrite
426 ui.write = oldwrite
417 return newchunks, newopts
427 return newchunks, newopts
418
428
419
429
420 def dorecord(
430 def dorecord(
421 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
431 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
422 ):
432 ):
423 opts = pycompat.byteskwargs(opts)
433 opts = pycompat.byteskwargs(opts)
424 if not ui.interactive():
434 if not ui.interactive():
425 if cmdsuggest:
435 if cmdsuggest:
426 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
436 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
427 else:
437 else:
428 msg = _(b'running non-interactively')
438 msg = _(b'running non-interactively')
429 raise error.InputError(msg)
439 raise error.InputError(msg)
430
440
431 # make sure username is set before going interactive
441 # make sure username is set before going interactive
432 if not opts.get(b'user'):
442 if not opts.get(b'user'):
433 ui.username() # raise exception, username not provided
443 ui.username() # raise exception, username not provided
434
444
435 def recordfunc(ui, repo, message, match, opts):
445 def recordfunc(ui, repo, message, match, opts):
436 """This is generic record driver.
446 """This is generic record driver.
437
447
438 Its job is to interactively filter local changes, and
448 Its job is to interactively filter local changes, and
439 accordingly prepare working directory into a state in which the
449 accordingly prepare working directory into a state in which the
440 job can be delegated to a non-interactive commit command such as
450 job can be delegated to a non-interactive commit command such as
441 'commit' or 'qrefresh'.
451 'commit' or 'qrefresh'.
442
452
443 After the actual job is done by non-interactive command, the
453 After the actual job is done by non-interactive command, the
444 working directory is restored to its original state.
454 working directory is restored to its original state.
445
455
446 In the end we'll record interesting changes, and everything else
456 In the end we'll record interesting changes, and everything else
447 will be left in place, so the user can continue working.
457 will be left in place, so the user can continue working.
448 """
458 """
449 if not opts.get(b'interactive-unshelve'):
459 if not opts.get(b'interactive-unshelve'):
450 checkunfinished(repo, commit=True)
460 checkunfinished(repo, commit=True)
451 wctx = repo[None]
461 wctx = repo[None]
452 merge = len(wctx.parents()) > 1
462 merge = len(wctx.parents()) > 1
453 if merge:
463 if merge:
454 raise error.InputError(
464 raise error.InputError(
455 _(
465 _(
456 b'cannot partially commit a merge '
466 b'cannot partially commit a merge '
457 b'(use "hg commit" instead)'
467 b'(use "hg commit" instead)'
458 )
468 )
459 )
469 )
460
470
461 def fail(f, msg):
471 def fail(f, msg):
462 raise error.InputError(b'%s: %s' % (f, msg))
472 raise error.InputError(b'%s: %s' % (f, msg))
463
473
464 force = opts.get(b'force')
474 force = opts.get(b'force')
465 if not force:
475 if not force:
466 match = matchmod.badmatch(match, fail)
476 match = matchmod.badmatch(match, fail)
467
477
468 status = repo.status(match=match)
478 status = repo.status(match=match)
469
479
470 overrides = {(b'ui', b'commitsubrepos'): True}
480 overrides = {(b'ui', b'commitsubrepos'): True}
471
481
472 with repo.ui.configoverride(overrides, b'record'):
482 with repo.ui.configoverride(overrides, b'record'):
473 # subrepoutil.precommit() modifies the status
483 # subrepoutil.precommit() modifies the status
474 tmpstatus = scmutil.status(
484 tmpstatus = scmutil.status(
475 copymod.copy(status.modified),
485 copymod.copy(status.modified),
476 copymod.copy(status.added),
486 copymod.copy(status.added),
477 copymod.copy(status.removed),
487 copymod.copy(status.removed),
478 copymod.copy(status.deleted),
488 copymod.copy(status.deleted),
479 copymod.copy(status.unknown),
489 copymod.copy(status.unknown),
480 copymod.copy(status.ignored),
490 copymod.copy(status.ignored),
481 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
491 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
482 )
492 )
483
493
484 # Force allows -X subrepo to skip the subrepo.
494 # Force allows -X subrepo to skip the subrepo.
485 subs, commitsubs, newstate = subrepoutil.precommit(
495 subs, commitsubs, newstate = subrepoutil.precommit(
486 repo.ui, wctx, tmpstatus, match, force=True
496 repo.ui, wctx, tmpstatus, match, force=True
487 )
497 )
488 for s in subs:
498 for s in subs:
489 if s in commitsubs:
499 if s in commitsubs:
490 dirtyreason = wctx.sub(s).dirtyreason(True)
500 dirtyreason = wctx.sub(s).dirtyreason(True)
491 raise error.Abort(dirtyreason)
501 raise error.Abort(dirtyreason)
492
502
493 if not force:
503 if not force:
494 repo.checkcommitpatterns(wctx, match, status, fail)
504 repo.checkcommitpatterns(wctx, match, status, fail)
495 diffopts = patch.difffeatureopts(
505 diffopts = patch.difffeatureopts(
496 ui,
506 ui,
497 opts=opts,
507 opts=opts,
498 whitespace=True,
508 whitespace=True,
499 section=b'commands',
509 section=b'commands',
500 configprefix=b'commit.interactive.',
510 configprefix=b'commit.interactive.',
501 )
511 )
502 diffopts.nodates = True
512 diffopts.nodates = True
503 diffopts.git = True
513 diffopts.git = True
504 diffopts.showfunc = True
514 diffopts.showfunc = True
505 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
515 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
506 originalchunks = patch.parsepatch(originaldiff)
516 originalchunks = patch.parsepatch(originaldiff)
507 match = scmutil.match(repo[None], pats)
517 match = scmutil.match(repo[None], pats)
508
518
509 # 1. filter patch, since we are intending to apply subset of it
519 # 1. filter patch, since we are intending to apply subset of it
510 try:
520 try:
511 chunks, newopts = filterfn(ui, originalchunks, match)
521 chunks, newopts = filterfn(ui, originalchunks, match)
512 except error.PatchError as err:
522 except error.PatchError as err:
513 raise error.InputError(_(b'error parsing patch: %s') % err)
523 raise error.InputError(_(b'error parsing patch: %s') % err)
514 opts.update(newopts)
524 opts.update(newopts)
515
525
516 # We need to keep a backup of files that have been newly added and
526 # We need to keep a backup of files that have been newly added and
517 # modified during the recording process because there is a previous
527 # modified during the recording process because there is a previous
518 # version without the edit in the workdir. We also will need to restore
528 # version without the edit in the workdir. We also will need to restore
519 # files that were the sources of renames so that the patch application
529 # files that were the sources of renames so that the patch application
520 # works.
530 # works.
521 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
531 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
522 chunks, originalchunks
532 chunks, originalchunks
523 )
533 )
524 contenders = set()
534 contenders = set()
525 for h in chunks:
535 for h in chunks:
526 try:
536 try:
527 contenders.update(set(h.files()))
537 contenders.update(set(h.files()))
528 except AttributeError:
538 except AttributeError:
529 pass
539 pass
530
540
531 changed = status.modified + status.added + status.removed
541 changed = status.modified + status.added + status.removed
532 newfiles = [f for f in changed if f in contenders]
542 newfiles = [f for f in changed if f in contenders]
533 if not newfiles:
543 if not newfiles:
534 ui.status(_(b'no changes to record\n'))
544 ui.status(_(b'no changes to record\n'))
535 return 0
545 return 0
536
546
537 modified = set(status.modified)
547 modified = set(status.modified)
538
548
539 # 2. backup changed files, so we can restore them in the end
549 # 2. backup changed files, so we can restore them in the end
540
550
541 if backupall:
551 if backupall:
542 tobackup = changed
552 tobackup = changed
543 else:
553 else:
544 tobackup = [
554 tobackup = [
545 f
555 f
546 for f in newfiles
556 for f in newfiles
547 if f in modified or f in newlyaddedandmodifiedfiles
557 if f in modified or f in newlyaddedandmodifiedfiles
548 ]
558 ]
549 backups = {}
559 backups = {}
550 if tobackup:
560 if tobackup:
551 backupdir = repo.vfs.join(b'record-backups')
561 backupdir = repo.vfs.join(b'record-backups')
552 try:
562 try:
553 os.mkdir(backupdir)
563 os.mkdir(backupdir)
554 except OSError as err:
564 except OSError as err:
555 if err.errno != errno.EEXIST:
565 if err.errno != errno.EEXIST:
556 raise
566 raise
557 try:
567 try:
558 # backup continues
568 # backup continues
559 for f in tobackup:
569 for f in tobackup:
560 fd, tmpname = pycompat.mkstemp(
570 fd, tmpname = pycompat.mkstemp(
561 prefix=os.path.basename(f) + b'.', dir=backupdir
571 prefix=os.path.basename(f) + b'.', dir=backupdir
562 )
572 )
563 os.close(fd)
573 os.close(fd)
564 ui.debug(b'backup %r as %r\n' % (f, tmpname))
574 ui.debug(b'backup %r as %r\n' % (f, tmpname))
565 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
575 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
566 backups[f] = tmpname
576 backups[f] = tmpname
567
577
568 fp = stringio()
578 fp = stringio()
569 for c in chunks:
579 for c in chunks:
570 fname = c.filename()
580 fname = c.filename()
571 if fname in backups:
581 if fname in backups:
572 c.write(fp)
582 c.write(fp)
573 dopatch = fp.tell()
583 dopatch = fp.tell()
574 fp.seek(0)
584 fp.seek(0)
575
585
576 # 2.5 optionally review / modify patch in text editor
586 # 2.5 optionally review / modify patch in text editor
577 if opts.get(b'review', False):
587 if opts.get(b'review', False):
578 patchtext = (
588 patchtext = (
579 crecordmod.diffhelptext
589 crecordmod.diffhelptext
580 + crecordmod.patchhelptext
590 + crecordmod.patchhelptext
581 + fp.read()
591 + fp.read()
582 )
592 )
583 reviewedpatch = ui.edit(
593 reviewedpatch = ui.edit(
584 patchtext, b"", action=b"diff", repopath=repo.path
594 patchtext, b"", action=b"diff", repopath=repo.path
585 )
595 )
586 fp.truncate(0)
596 fp.truncate(0)
587 fp.write(reviewedpatch)
597 fp.write(reviewedpatch)
588 fp.seek(0)
598 fp.seek(0)
589
599
590 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
600 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
591 # 3a. apply filtered patch to clean repo (clean)
601 # 3a. apply filtered patch to clean repo (clean)
592 if backups:
602 if backups:
593 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
603 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
594 mergemod.revert_to(repo[b'.'], matcher=m)
604 mergemod.revert_to(repo[b'.'], matcher=m)
595
605
596 # 3b. (apply)
606 # 3b. (apply)
597 if dopatch:
607 if dopatch:
598 try:
608 try:
599 ui.debug(b'applying patch\n')
609 ui.debug(b'applying patch\n')
600 ui.debug(fp.getvalue())
610 ui.debug(fp.getvalue())
601 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
611 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
602 except error.PatchError as err:
612 except error.PatchError as err:
603 raise error.InputError(pycompat.bytestr(err))
613 raise error.InputError(pycompat.bytestr(err))
604 del fp
614 del fp
605
615
606 # 4. We prepared working directory according to filtered
616 # 4. We prepared working directory according to filtered
607 # patch. Now is the time to delegate the job to
617 # patch. Now is the time to delegate the job to
608 # commit/qrefresh or the like!
618 # commit/qrefresh or the like!
609
619
610 # Make all of the pathnames absolute.
620 # Make all of the pathnames absolute.
611 newfiles = [repo.wjoin(nf) for nf in newfiles]
621 newfiles = [repo.wjoin(nf) for nf in newfiles]
612 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
622 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
613 finally:
623 finally:
614 # 5. finally restore backed-up files
624 # 5. finally restore backed-up files
615 try:
625 try:
616 dirstate = repo.dirstate
626 dirstate = repo.dirstate
617 for realname, tmpname in pycompat.iteritems(backups):
627 for realname, tmpname in pycompat.iteritems(backups):
618 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
628 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
619
629
620 if dirstate[realname] == b'n':
630 if dirstate[realname] == b'n':
621 # without normallookup, restoring timestamp
631 # without normallookup, restoring timestamp
622 # may cause partially committed files
632 # may cause partially committed files
623 # to be treated as unmodified
633 # to be treated as unmodified
624 dirstate.normallookup(realname)
634 dirstate.normallookup(realname)
625
635
626 # copystat=True here and above are a hack to trick any
636 # copystat=True here and above are a hack to trick any
627 # editors that have f open that we haven't modified them.
637 # editors that have f open that we haven't modified them.
628 #
638 #
629 # Also note that this racy as an editor could notice the
639 # Also note that this racy as an editor could notice the
630 # file's mtime before we've finished writing it.
640 # file's mtime before we've finished writing it.
631 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
641 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
632 os.unlink(tmpname)
642 os.unlink(tmpname)
633 if tobackup:
643 if tobackup:
634 os.rmdir(backupdir)
644 os.rmdir(backupdir)
635 except OSError:
645 except OSError:
636 pass
646 pass
637
647
638 def recordinwlock(ui, repo, message, match, opts):
648 def recordinwlock(ui, repo, message, match, opts):
639 with repo.wlock():
649 with repo.wlock():
640 return recordfunc(ui, repo, message, match, opts)
650 return recordfunc(ui, repo, message, match, opts)
641
651
642 return commit(ui, repo, recordinwlock, pats, opts)
652 return commit(ui, repo, recordinwlock, pats, opts)
643
653
644
654
645 class dirnode(object):
655 class dirnode(object):
646 """
656 """
647 Represent a directory in user working copy with information required for
657 Represent a directory in user working copy with information required for
648 the purpose of tersing its status.
658 the purpose of tersing its status.
649
659
650 path is the path to the directory, without a trailing '/'
660 path is the path to the directory, without a trailing '/'
651
661
652 statuses is a set of statuses of all files in this directory (this includes
662 statuses is a set of statuses of all files in this directory (this includes
653 all the files in all the subdirectories too)
663 all the files in all the subdirectories too)
654
664
655 files is a list of files which are direct child of this directory
665 files is a list of files which are direct child of this directory
656
666
657 subdirs is a dictionary of sub-directory name as the key and it's own
667 subdirs is a dictionary of sub-directory name as the key and it's own
658 dirnode object as the value
668 dirnode object as the value
659 """
669 """
660
670
661 def __init__(self, dirpath):
671 def __init__(self, dirpath):
662 self.path = dirpath
672 self.path = dirpath
663 self.statuses = set()
673 self.statuses = set()
664 self.files = []
674 self.files = []
665 self.subdirs = {}
675 self.subdirs = {}
666
676
667 def _addfileindir(self, filename, status):
677 def _addfileindir(self, filename, status):
668 """Add a file in this directory as a direct child."""
678 """Add a file in this directory as a direct child."""
669 self.files.append((filename, status))
679 self.files.append((filename, status))
670
680
671 def addfile(self, filename, status):
681 def addfile(self, filename, status):
672 """
682 """
673 Add a file to this directory or to its direct parent directory.
683 Add a file to this directory or to its direct parent directory.
674
684
675 If the file is not direct child of this directory, we traverse to the
685 If the file is not direct child of this directory, we traverse to the
676 directory of which this file is a direct child of and add the file
686 directory of which this file is a direct child of and add the file
677 there.
687 there.
678 """
688 """
679
689
680 # the filename contains a path separator, it means it's not the direct
690 # the filename contains a path separator, it means it's not the direct
681 # child of this directory
691 # child of this directory
682 if b'/' in filename:
692 if b'/' in filename:
683 subdir, filep = filename.split(b'/', 1)
693 subdir, filep = filename.split(b'/', 1)
684
694
685 # does the dirnode object for subdir exists
695 # does the dirnode object for subdir exists
686 if subdir not in self.subdirs:
696 if subdir not in self.subdirs:
687 subdirpath = pathutil.join(self.path, subdir)
697 subdirpath = pathutil.join(self.path, subdir)
688 self.subdirs[subdir] = dirnode(subdirpath)
698 self.subdirs[subdir] = dirnode(subdirpath)
689
699
690 # try adding the file in subdir
700 # try adding the file in subdir
691 self.subdirs[subdir].addfile(filep, status)
701 self.subdirs[subdir].addfile(filep, status)
692
702
693 else:
703 else:
694 self._addfileindir(filename, status)
704 self._addfileindir(filename, status)
695
705
696 if status not in self.statuses:
706 if status not in self.statuses:
697 self.statuses.add(status)
707 self.statuses.add(status)
698
708
699 def iterfilepaths(self):
709 def iterfilepaths(self):
700 """Yield (status, path) for files directly under this directory."""
710 """Yield (status, path) for files directly under this directory."""
701 for f, st in self.files:
711 for f, st in self.files:
702 yield st, pathutil.join(self.path, f)
712 yield st, pathutil.join(self.path, f)
703
713
704 def tersewalk(self, terseargs):
714 def tersewalk(self, terseargs):
705 """
715 """
706 Yield (status, path) obtained by processing the status of this
716 Yield (status, path) obtained by processing the status of this
707 dirnode.
717 dirnode.
708
718
709 terseargs is the string of arguments passed by the user with `--terse`
719 terseargs is the string of arguments passed by the user with `--terse`
710 flag.
720 flag.
711
721
712 Following are the cases which can happen:
722 Following are the cases which can happen:
713
723
714 1) All the files in the directory (including all the files in its
724 1) All the files in the directory (including all the files in its
715 subdirectories) share the same status and the user has asked us to terse
725 subdirectories) share the same status and the user has asked us to terse
716 that status. -> yield (status, dirpath). dirpath will end in '/'.
726 that status. -> yield (status, dirpath). dirpath will end in '/'.
717
727
718 2) Otherwise, we do following:
728 2) Otherwise, we do following:
719
729
720 a) Yield (status, filepath) for all the files which are in this
730 a) Yield (status, filepath) for all the files which are in this
721 directory (only the ones in this directory, not the subdirs)
731 directory (only the ones in this directory, not the subdirs)
722
732
723 b) Recurse the function on all the subdirectories of this
733 b) Recurse the function on all the subdirectories of this
724 directory
734 directory
725 """
735 """
726
736
727 if len(self.statuses) == 1:
737 if len(self.statuses) == 1:
728 onlyst = self.statuses.pop()
738 onlyst = self.statuses.pop()
729
739
730 # Making sure we terse only when the status abbreviation is
740 # Making sure we terse only when the status abbreviation is
731 # passed as terse argument
741 # passed as terse argument
732 if onlyst in terseargs:
742 if onlyst in terseargs:
733 yield onlyst, self.path + b'/'
743 yield onlyst, self.path + b'/'
734 return
744 return
735
745
736 # add the files to status list
746 # add the files to status list
737 for st, fpath in self.iterfilepaths():
747 for st, fpath in self.iterfilepaths():
738 yield st, fpath
748 yield st, fpath
739
749
740 # recurse on the subdirs
750 # recurse on the subdirs
741 for dirobj in self.subdirs.values():
751 for dirobj in self.subdirs.values():
742 for st, fpath in dirobj.tersewalk(terseargs):
752 for st, fpath in dirobj.tersewalk(terseargs):
743 yield st, fpath
753 yield st, fpath
744
754
745
755
746 def tersedir(statuslist, terseargs):
756 def tersedir(statuslist, terseargs):
747 """
757 """
748 Terse the status if all the files in a directory shares the same status.
758 Terse the status if all the files in a directory shares the same status.
749
759
750 statuslist is scmutil.status() object which contains a list of files for
760 statuslist is scmutil.status() object which contains a list of files for
751 each status.
761 each status.
752 terseargs is string which is passed by the user as the argument to `--terse`
762 terseargs is string which is passed by the user as the argument to `--terse`
753 flag.
763 flag.
754
764
755 The function makes a tree of objects of dirnode class, and at each node it
765 The function makes a tree of objects of dirnode class, and at each node it
756 stores the information required to know whether we can terse a certain
766 stores the information required to know whether we can terse a certain
757 directory or not.
767 directory or not.
758 """
768 """
759 # the order matters here as that is used to produce final list
769 # the order matters here as that is used to produce final list
760 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
770 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
761
771
762 # checking the argument validity
772 # checking the argument validity
763 for s in pycompat.bytestr(terseargs):
773 for s in pycompat.bytestr(terseargs):
764 if s not in allst:
774 if s not in allst:
765 raise error.InputError(_(b"'%s' not recognized") % s)
775 raise error.InputError(_(b"'%s' not recognized") % s)
766
776
767 # creating a dirnode object for the root of the repo
777 # creating a dirnode object for the root of the repo
768 rootobj = dirnode(b'')
778 rootobj = dirnode(b'')
769 pstatus = (
779 pstatus = (
770 b'modified',
780 b'modified',
771 b'added',
781 b'added',
772 b'deleted',
782 b'deleted',
773 b'clean',
783 b'clean',
774 b'unknown',
784 b'unknown',
775 b'ignored',
785 b'ignored',
776 b'removed',
786 b'removed',
777 )
787 )
778
788
779 tersedict = {}
789 tersedict = {}
780 for attrname in pstatus:
790 for attrname in pstatus:
781 statuschar = attrname[0:1]
791 statuschar = attrname[0:1]
782 for f in getattr(statuslist, attrname):
792 for f in getattr(statuslist, attrname):
783 rootobj.addfile(f, statuschar)
793 rootobj.addfile(f, statuschar)
784 tersedict[statuschar] = []
794 tersedict[statuschar] = []
785
795
786 # we won't be tersing the root dir, so add files in it
796 # we won't be tersing the root dir, so add files in it
787 for st, fpath in rootobj.iterfilepaths():
797 for st, fpath in rootobj.iterfilepaths():
788 tersedict[st].append(fpath)
798 tersedict[st].append(fpath)
789
799
790 # process each sub-directory and build tersedict
800 # process each sub-directory and build tersedict
791 for subdir in rootobj.subdirs.values():
801 for subdir in rootobj.subdirs.values():
792 for st, f in subdir.tersewalk(terseargs):
802 for st, f in subdir.tersewalk(terseargs):
793 tersedict[st].append(f)
803 tersedict[st].append(f)
794
804
795 tersedlist = []
805 tersedlist = []
796 for st in allst:
806 for st in allst:
797 tersedict[st].sort()
807 tersedict[st].sort()
798 tersedlist.append(tersedict[st])
808 tersedlist.append(tersedict[st])
799
809
800 return scmutil.status(*tersedlist)
810 return scmutil.status(*tersedlist)
801
811
802
812
803 def _commentlines(raw):
813 def _commentlines(raw):
804 '''Surround lineswith a comment char and a new line'''
814 '''Surround lineswith a comment char and a new line'''
805 lines = raw.splitlines()
815 lines = raw.splitlines()
806 commentedlines = [b'# %s' % line for line in lines]
816 commentedlines = [b'# %s' % line for line in lines]
807 return b'\n'.join(commentedlines) + b'\n'
817 return b'\n'.join(commentedlines) + b'\n'
808
818
809
819
810 @attr.s(frozen=True)
820 @attr.s(frozen=True)
811 class morestatus(object):
821 class morestatus(object):
812 reporoot = attr.ib()
822 reporoot = attr.ib()
813 unfinishedop = attr.ib()
823 unfinishedop = attr.ib()
814 unfinishedmsg = attr.ib()
824 unfinishedmsg = attr.ib()
815 activemerge = attr.ib()
825 activemerge = attr.ib()
816 unresolvedpaths = attr.ib()
826 unresolvedpaths = attr.ib()
817 _formattedpaths = attr.ib(init=False, default=set())
827 _formattedpaths = attr.ib(init=False, default=set())
818 _label = b'status.morestatus'
828 _label = b'status.morestatus'
819
829
820 def formatfile(self, path, fm):
830 def formatfile(self, path, fm):
821 self._formattedpaths.add(path)
831 self._formattedpaths.add(path)
822 if self.activemerge and path in self.unresolvedpaths:
832 if self.activemerge and path in self.unresolvedpaths:
823 fm.data(unresolved=True)
833 fm.data(unresolved=True)
824
834
825 def formatfooter(self, fm):
835 def formatfooter(self, fm):
826 if self.unfinishedop or self.unfinishedmsg:
836 if self.unfinishedop or self.unfinishedmsg:
827 fm.startitem()
837 fm.startitem()
828 fm.data(itemtype=b'morestatus')
838 fm.data(itemtype=b'morestatus')
829
839
830 if self.unfinishedop:
840 if self.unfinishedop:
831 fm.data(unfinished=self.unfinishedop)
841 fm.data(unfinished=self.unfinishedop)
832 statemsg = (
842 statemsg = (
833 _(b'The repository is in an unfinished *%s* state.')
843 _(b'The repository is in an unfinished *%s* state.')
834 % self.unfinishedop
844 % self.unfinishedop
835 )
845 )
836 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
846 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
837 if self.unfinishedmsg:
847 if self.unfinishedmsg:
838 fm.data(unfinishedmsg=self.unfinishedmsg)
848 fm.data(unfinishedmsg=self.unfinishedmsg)
839
849
840 # May also start new data items.
850 # May also start new data items.
841 self._formatconflicts(fm)
851 self._formatconflicts(fm)
842
852
843 if self.unfinishedmsg:
853 if self.unfinishedmsg:
844 fm.plain(
854 fm.plain(
845 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
855 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
846 )
856 )
847
857
848 def _formatconflicts(self, fm):
858 def _formatconflicts(self, fm):
849 if not self.activemerge:
859 if not self.activemerge:
850 return
860 return
851
861
852 if self.unresolvedpaths:
862 if self.unresolvedpaths:
853 mergeliststr = b'\n'.join(
863 mergeliststr = b'\n'.join(
854 [
864 [
855 b' %s'
865 b' %s'
856 % util.pathto(self.reporoot, encoding.getcwd(), path)
866 % util.pathto(self.reporoot, encoding.getcwd(), path)
857 for path in self.unresolvedpaths
867 for path in self.unresolvedpaths
858 ]
868 ]
859 )
869 )
860 msg = (
870 msg = (
861 _(
871 _(
862 '''Unresolved merge conflicts:
872 '''Unresolved merge conflicts:
863
873
864 %s
874 %s
865
875
866 To mark files as resolved: hg resolve --mark FILE'''
876 To mark files as resolved: hg resolve --mark FILE'''
867 )
877 )
868 % mergeliststr
878 % mergeliststr
869 )
879 )
870
880
871 # If any paths with unresolved conflicts were not previously
881 # If any paths with unresolved conflicts were not previously
872 # formatted, output them now.
882 # formatted, output them now.
873 for f in self.unresolvedpaths:
883 for f in self.unresolvedpaths:
874 if f in self._formattedpaths:
884 if f in self._formattedpaths:
875 # Already output.
885 # Already output.
876 continue
886 continue
877 fm.startitem()
887 fm.startitem()
878 # We can't claim to know the status of the file - it may just
888 # We can't claim to know the status of the file - it may just
879 # have been in one of the states that were not requested for
889 # have been in one of the states that were not requested for
880 # display, so it could be anything.
890 # display, so it could be anything.
881 fm.data(itemtype=b'file', path=f, unresolved=True)
891 fm.data(itemtype=b'file', path=f, unresolved=True)
882
892
883 else:
893 else:
884 msg = _(b'No unresolved merge conflicts.')
894 msg = _(b'No unresolved merge conflicts.')
885
895
886 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
896 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
887
897
888
898
889 def readmorestatus(repo):
899 def readmorestatus(repo):
890 """Returns a morestatus object if the repo has unfinished state."""
900 """Returns a morestatus object if the repo has unfinished state."""
891 statetuple = statemod.getrepostate(repo)
901 statetuple = statemod.getrepostate(repo)
892 mergestate = mergestatemod.mergestate.read(repo)
902 mergestate = mergestatemod.mergestate.read(repo)
893 activemerge = mergestate.active()
903 activemerge = mergestate.active()
894 if not statetuple and not activemerge:
904 if not statetuple and not activemerge:
895 return None
905 return None
896
906
897 unfinishedop = unfinishedmsg = unresolved = None
907 unfinishedop = unfinishedmsg = unresolved = None
898 if statetuple:
908 if statetuple:
899 unfinishedop, unfinishedmsg = statetuple
909 unfinishedop, unfinishedmsg = statetuple
900 if activemerge:
910 if activemerge:
901 unresolved = sorted(mergestate.unresolved())
911 unresolved = sorted(mergestate.unresolved())
902 return morestatus(
912 return morestatus(
903 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
913 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
904 )
914 )
905
915
906
916
907 def findpossible(cmd, table, strict=False):
917 def findpossible(cmd, table, strict=False):
908 """
918 """
909 Return cmd -> (aliases, command table entry)
919 Return cmd -> (aliases, command table entry)
910 for each matching command.
920 for each matching command.
911 Return debug commands (or their aliases) only if no normal command matches.
921 Return debug commands (or their aliases) only if no normal command matches.
912 """
922 """
913 choice = {}
923 choice = {}
914 debugchoice = {}
924 debugchoice = {}
915
925
916 if cmd in table:
926 if cmd in table:
917 # short-circuit exact matches, "log" alias beats "log|history"
927 # short-circuit exact matches, "log" alias beats "log|history"
918 keys = [cmd]
928 keys = [cmd]
919 else:
929 else:
920 keys = table.keys()
930 keys = table.keys()
921
931
922 allcmds = []
932 allcmds = []
923 for e in keys:
933 for e in keys:
924 aliases = parsealiases(e)
934 aliases = parsealiases(e)
925 allcmds.extend(aliases)
935 allcmds.extend(aliases)
926 found = None
936 found = None
927 if cmd in aliases:
937 if cmd in aliases:
928 found = cmd
938 found = cmd
929 elif not strict:
939 elif not strict:
930 for a in aliases:
940 for a in aliases:
931 if a.startswith(cmd):
941 if a.startswith(cmd):
932 found = a
942 found = a
933 break
943 break
934 if found is not None:
944 if found is not None:
935 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
945 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
936 debugchoice[found] = (aliases, table[e])
946 debugchoice[found] = (aliases, table[e])
937 else:
947 else:
938 choice[found] = (aliases, table[e])
948 choice[found] = (aliases, table[e])
939
949
940 if not choice and debugchoice:
950 if not choice and debugchoice:
941 choice = debugchoice
951 choice = debugchoice
942
952
943 return choice, allcmds
953 return choice, allcmds
944
954
945
955
946 def findcmd(cmd, table, strict=True):
956 def findcmd(cmd, table, strict=True):
947 """Return (aliases, command table entry) for command string."""
957 """Return (aliases, command table entry) for command string."""
948 choice, allcmds = findpossible(cmd, table, strict)
958 choice, allcmds = findpossible(cmd, table, strict)
949
959
950 if cmd in choice:
960 if cmd in choice:
951 return choice[cmd]
961 return choice[cmd]
952
962
953 if len(choice) > 1:
963 if len(choice) > 1:
954 clist = sorted(choice)
964 clist = sorted(choice)
955 raise error.AmbiguousCommand(cmd, clist)
965 raise error.AmbiguousCommand(cmd, clist)
956
966
957 if choice:
967 if choice:
958 return list(choice.values())[0]
968 return list(choice.values())[0]
959
969
960 raise error.UnknownCommand(cmd, allcmds)
970 raise error.UnknownCommand(cmd, allcmds)
961
971
962
972
963 def changebranch(ui, repo, revs, label, opts):
973 def changebranch(ui, repo, revs, label, opts):
964 """ Change the branch name of given revs to label """
974 """ Change the branch name of given revs to label """
965
975
966 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
976 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
967 # abort in case of uncommitted merge or dirty wdir
977 # abort in case of uncommitted merge or dirty wdir
968 bailifchanged(repo)
978 bailifchanged(repo)
969 revs = scmutil.revrange(repo, revs)
979 revs = scmutil.revrange(repo, revs)
970 if not revs:
980 if not revs:
971 raise error.InputError(b"empty revision set")
981 raise error.InputError(b"empty revision set")
972 roots = repo.revs(b'roots(%ld)', revs)
982 roots = repo.revs(b'roots(%ld)', revs)
973 if len(roots) > 1:
983 if len(roots) > 1:
974 raise error.InputError(
984 raise error.InputError(
975 _(b"cannot change branch of non-linear revisions")
985 _(b"cannot change branch of non-linear revisions")
976 )
986 )
977 rewriteutil.precheck(repo, revs, b'change branch of')
987 rewriteutil.precheck(repo, revs, b'change branch of')
978
988
979 root = repo[roots.first()]
989 root = repo[roots.first()]
980 rpb = {parent.branch() for parent in root.parents()}
990 rpb = {parent.branch() for parent in root.parents()}
981 if (
991 if (
982 not opts.get(b'force')
992 not opts.get(b'force')
983 and label not in rpb
993 and label not in rpb
984 and label in repo.branchmap()
994 and label in repo.branchmap()
985 ):
995 ):
986 raise error.InputError(
996 raise error.InputError(
987 _(b"a branch of the same name already exists")
997 _(b"a branch of the same name already exists")
988 )
998 )
989
999
990 if repo.revs(b'obsolete() and %ld', revs):
1000 if repo.revs(b'obsolete() and %ld', revs):
991 raise error.InputError(
1001 raise error.InputError(
992 _(b"cannot change branch of a obsolete changeset")
1002 _(b"cannot change branch of a obsolete changeset")
993 )
1003 )
994
1004
995 # make sure only topological heads
1005 # make sure only topological heads
996 if repo.revs(b'heads(%ld) - head()', revs):
1006 if repo.revs(b'heads(%ld) - head()', revs):
997 raise error.InputError(
1007 raise error.InputError(
998 _(b"cannot change branch in middle of a stack")
1008 _(b"cannot change branch in middle of a stack")
999 )
1009 )
1000
1010
1001 replacements = {}
1011 replacements = {}
1002 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1012 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1003 # mercurial.subrepo -> mercurial.cmdutil
1013 # mercurial.subrepo -> mercurial.cmdutil
1004 from . import context
1014 from . import context
1005
1015
1006 for rev in revs:
1016 for rev in revs:
1007 ctx = repo[rev]
1017 ctx = repo[rev]
1008 oldbranch = ctx.branch()
1018 oldbranch = ctx.branch()
1009 # check if ctx has same branch
1019 # check if ctx has same branch
1010 if oldbranch == label:
1020 if oldbranch == label:
1011 continue
1021 continue
1012
1022
1013 def filectxfn(repo, newctx, path):
1023 def filectxfn(repo, newctx, path):
1014 try:
1024 try:
1015 return ctx[path]
1025 return ctx[path]
1016 except error.ManifestLookupError:
1026 except error.ManifestLookupError:
1017 return None
1027 return None
1018
1028
1019 ui.debug(
1029 ui.debug(
1020 b"changing branch of '%s' from '%s' to '%s'\n"
1030 b"changing branch of '%s' from '%s' to '%s'\n"
1021 % (hex(ctx.node()), oldbranch, label)
1031 % (hex(ctx.node()), oldbranch, label)
1022 )
1032 )
1023 extra = ctx.extra()
1033 extra = ctx.extra()
1024 extra[b'branch_change'] = hex(ctx.node())
1034 extra[b'branch_change'] = hex(ctx.node())
1025 # While changing branch of set of linear commits, make sure that
1035 # While changing branch of set of linear commits, make sure that
1026 # we base our commits on new parent rather than old parent which
1036 # we base our commits on new parent rather than old parent which
1027 # was obsoleted while changing the branch
1037 # was obsoleted while changing the branch
1028 p1 = ctx.p1().node()
1038 p1 = ctx.p1().node()
1029 p2 = ctx.p2().node()
1039 p2 = ctx.p2().node()
1030 if p1 in replacements:
1040 if p1 in replacements:
1031 p1 = replacements[p1][0]
1041 p1 = replacements[p1][0]
1032 if p2 in replacements:
1042 if p2 in replacements:
1033 p2 = replacements[p2][0]
1043 p2 = replacements[p2][0]
1034
1044
1035 mc = context.memctx(
1045 mc = context.memctx(
1036 repo,
1046 repo,
1037 (p1, p2),
1047 (p1, p2),
1038 ctx.description(),
1048 ctx.description(),
1039 ctx.files(),
1049 ctx.files(),
1040 filectxfn,
1050 filectxfn,
1041 user=ctx.user(),
1051 user=ctx.user(),
1042 date=ctx.date(),
1052 date=ctx.date(),
1043 extra=extra,
1053 extra=extra,
1044 branch=label,
1054 branch=label,
1045 )
1055 )
1046
1056
1047 newnode = repo.commitctx(mc)
1057 newnode = repo.commitctx(mc)
1048 replacements[ctx.node()] = (newnode,)
1058 replacements[ctx.node()] = (newnode,)
1049 ui.debug(b'new node id is %s\n' % hex(newnode))
1059 ui.debug(b'new node id is %s\n' % hex(newnode))
1050
1060
1051 # create obsmarkers and move bookmarks
1061 # create obsmarkers and move bookmarks
1052 scmutil.cleanupnodes(
1062 scmutil.cleanupnodes(
1053 repo, replacements, b'branch-change', fixphase=True
1063 repo, replacements, b'branch-change', fixphase=True
1054 )
1064 )
1055
1065
1056 # move the working copy too
1066 # move the working copy too
1057 wctx = repo[None]
1067 wctx = repo[None]
1058 # in-progress merge is a bit too complex for now.
1068 # in-progress merge is a bit too complex for now.
1059 if len(wctx.parents()) == 1:
1069 if len(wctx.parents()) == 1:
1060 newid = replacements.get(wctx.p1().node())
1070 newid = replacements.get(wctx.p1().node())
1061 if newid is not None:
1071 if newid is not None:
1062 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1072 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1063 # mercurial.cmdutil
1073 # mercurial.cmdutil
1064 from . import hg
1074 from . import hg
1065
1075
1066 hg.update(repo, newid[0], quietempty=True)
1076 hg.update(repo, newid[0], quietempty=True)
1067
1077
1068 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1078 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1069
1079
1070
1080
1071 def findrepo(p):
1081 def findrepo(p):
1072 while not os.path.isdir(os.path.join(p, b".hg")):
1082 while not os.path.isdir(os.path.join(p, b".hg")):
1073 oldp, p = p, os.path.dirname(p)
1083 oldp, p = p, os.path.dirname(p)
1074 if p == oldp:
1084 if p == oldp:
1075 return None
1085 return None
1076
1086
1077 return p
1087 return p
1078
1088
1079
1089
1080 def bailifchanged(repo, merge=True, hint=None):
1090 def bailifchanged(repo, merge=True, hint=None):
1081 """enforce the precondition that working directory must be clean.
1091 """enforce the precondition that working directory must be clean.
1082
1092
1083 'merge' can be set to false if a pending uncommitted merge should be
1093 'merge' can be set to false if a pending uncommitted merge should be
1084 ignored (such as when 'update --check' runs).
1094 ignored (such as when 'update --check' runs).
1085
1095
1086 'hint' is the usual hint given to Abort exception.
1096 'hint' is the usual hint given to Abort exception.
1087 """
1097 """
1088
1098
1089 if merge and repo.dirstate.p2() != nullid:
1099 if merge and repo.dirstate.p2() != nullid:
1090 raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
1100 raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
1091 st = repo.status()
1101 st = repo.status()
1092 if st.modified or st.added or st.removed or st.deleted:
1102 if st.modified or st.added or st.removed or st.deleted:
1093 raise error.StateError(_(b'uncommitted changes'), hint=hint)
1103 raise error.StateError(_(b'uncommitted changes'), hint=hint)
1094 ctx = repo[None]
1104 ctx = repo[None]
1095 for s in sorted(ctx.substate):
1105 for s in sorted(ctx.substate):
1096 ctx.sub(s).bailifchanged(hint=hint)
1106 ctx.sub(s).bailifchanged(hint=hint)
1097
1107
1098
1108
1099 def logmessage(ui, opts):
1109 def logmessage(ui, opts):
1100 """ get the log message according to -m and -l option """
1110 """ get the log message according to -m and -l option """
1101
1111
1102 check_at_most_one_arg(opts, b'message', b'logfile')
1112 check_at_most_one_arg(opts, b'message', b'logfile')
1103
1113
1104 message = opts.get(b'message')
1114 message = opts.get(b'message')
1105 logfile = opts.get(b'logfile')
1115 logfile = opts.get(b'logfile')
1106
1116
1107 if not message and logfile:
1117 if not message and logfile:
1108 try:
1118 try:
1109 if isstdiofilename(logfile):
1119 if isstdiofilename(logfile):
1110 message = ui.fin.read()
1120 message = ui.fin.read()
1111 else:
1121 else:
1112 message = b'\n'.join(util.readfile(logfile).splitlines())
1122 message = b'\n'.join(util.readfile(logfile).splitlines())
1113 except IOError as inst:
1123 except IOError as inst:
1114 raise error.Abort(
1124 raise error.Abort(
1115 _(b"can't read commit message '%s': %s")
1125 _(b"can't read commit message '%s': %s")
1116 % (logfile, encoding.strtolocal(inst.strerror))
1126 % (logfile, encoding.strtolocal(inst.strerror))
1117 )
1127 )
1118 return message
1128 return message
1119
1129
1120
1130
1121 def mergeeditform(ctxorbool, baseformname):
1131 def mergeeditform(ctxorbool, baseformname):
1122 """return appropriate editform name (referencing a committemplate)
1132 """return appropriate editform name (referencing a committemplate)
1123
1133
1124 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1134 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1125 merging is committed.
1135 merging is committed.
1126
1136
1127 This returns baseformname with '.merge' appended if it is a merge,
1137 This returns baseformname with '.merge' appended if it is a merge,
1128 otherwise '.normal' is appended.
1138 otherwise '.normal' is appended.
1129 """
1139 """
1130 if isinstance(ctxorbool, bool):
1140 if isinstance(ctxorbool, bool):
1131 if ctxorbool:
1141 if ctxorbool:
1132 return baseformname + b".merge"
1142 return baseformname + b".merge"
1133 elif len(ctxorbool.parents()) > 1:
1143 elif len(ctxorbool.parents()) > 1:
1134 return baseformname + b".merge"
1144 return baseformname + b".merge"
1135
1145
1136 return baseformname + b".normal"
1146 return baseformname + b".normal"
1137
1147
1138
1148
1139 def getcommiteditor(
1149 def getcommiteditor(
1140 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1150 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1141 ):
1151 ):
1142 """get appropriate commit message editor according to '--edit' option
1152 """get appropriate commit message editor according to '--edit' option
1143
1153
1144 'finishdesc' is a function to be called with edited commit message
1154 'finishdesc' is a function to be called with edited commit message
1145 (= 'description' of the new changeset) just after editing, but
1155 (= 'description' of the new changeset) just after editing, but
1146 before checking empty-ness. It should return actual text to be
1156 before checking empty-ness. It should return actual text to be
1147 stored into history. This allows to change description before
1157 stored into history. This allows to change description before
1148 storing.
1158 storing.
1149
1159
1150 'extramsg' is a extra message to be shown in the editor instead of
1160 'extramsg' is a extra message to be shown in the editor instead of
1151 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1161 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1152 is automatically added.
1162 is automatically added.
1153
1163
1154 'editform' is a dot-separated list of names, to distinguish
1164 'editform' is a dot-separated list of names, to distinguish
1155 the purpose of commit text editing.
1165 the purpose of commit text editing.
1156
1166
1157 'getcommiteditor' returns 'commitforceeditor' regardless of
1167 'getcommiteditor' returns 'commitforceeditor' regardless of
1158 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1168 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1159 they are specific for usage in MQ.
1169 they are specific for usage in MQ.
1160 """
1170 """
1161 if edit or finishdesc or extramsg:
1171 if edit or finishdesc or extramsg:
1162 return lambda r, c, s: commitforceeditor(
1172 return lambda r, c, s: commitforceeditor(
1163 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1173 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1164 )
1174 )
1165 elif editform:
1175 elif editform:
1166 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1176 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1167 else:
1177 else:
1168 return commiteditor
1178 return commiteditor
1169
1179
1170
1180
1171 def _escapecommandtemplate(tmpl):
1181 def _escapecommandtemplate(tmpl):
1172 parts = []
1182 parts = []
1173 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1183 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1174 if typ == b'string':
1184 if typ == b'string':
1175 parts.append(stringutil.escapestr(tmpl[start:end]))
1185 parts.append(stringutil.escapestr(tmpl[start:end]))
1176 else:
1186 else:
1177 parts.append(tmpl[start:end])
1187 parts.append(tmpl[start:end])
1178 return b''.join(parts)
1188 return b''.join(parts)
1179
1189
1180
1190
1181 def rendercommandtemplate(ui, tmpl, props):
1191 def rendercommandtemplate(ui, tmpl, props):
1182 r"""Expand a literal template 'tmpl' in a way suitable for command line
1192 r"""Expand a literal template 'tmpl' in a way suitable for command line
1183
1193
1184 '\' in outermost string is not taken as an escape character because it
1194 '\' in outermost string is not taken as an escape character because it
1185 is a directory separator on Windows.
1195 is a directory separator on Windows.
1186
1196
1187 >>> from . import ui as uimod
1197 >>> from . import ui as uimod
1188 >>> ui = uimod.ui()
1198 >>> ui = uimod.ui()
1189 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1199 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1190 'c:\\foo'
1200 'c:\\foo'
1191 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1201 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1192 'c:{path}'
1202 'c:{path}'
1193 """
1203 """
1194 if not tmpl:
1204 if not tmpl:
1195 return tmpl
1205 return tmpl
1196 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1206 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1197 return t.renderdefault(props)
1207 return t.renderdefault(props)
1198
1208
1199
1209
1200 def rendertemplate(ctx, tmpl, props=None):
1210 def rendertemplate(ctx, tmpl, props=None):
1201 """Expand a literal template 'tmpl' byte-string against one changeset
1211 """Expand a literal template 'tmpl' byte-string against one changeset
1202
1212
1203 Each props item must be a stringify-able value or a callable returning
1213 Each props item must be a stringify-able value or a callable returning
1204 such value, i.e. no bare list nor dict should be passed.
1214 such value, i.e. no bare list nor dict should be passed.
1205 """
1215 """
1206 repo = ctx.repo()
1216 repo = ctx.repo()
1207 tres = formatter.templateresources(repo.ui, repo)
1217 tres = formatter.templateresources(repo.ui, repo)
1208 t = formatter.maketemplater(
1218 t = formatter.maketemplater(
1209 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1219 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1210 )
1220 )
1211 mapping = {b'ctx': ctx}
1221 mapping = {b'ctx': ctx}
1212 if props:
1222 if props:
1213 mapping.update(props)
1223 mapping.update(props)
1214 return t.renderdefault(mapping)
1224 return t.renderdefault(mapping)
1215
1225
1216
1226
1217 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1227 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1218 """Format a changeset summary (one line)."""
1228 """Format a changeset summary (one line)."""
1219 spec = None
1229 spec = None
1220 if command:
1230 if command:
1221 spec = ui.config(
1231 spec = ui.config(
1222 b'command-templates', b'oneline-summary.%s' % command, None
1232 b'command-templates', b'oneline-summary.%s' % command, None
1223 )
1233 )
1224 if not spec:
1234 if not spec:
1225 spec = ui.config(b'command-templates', b'oneline-summary')
1235 spec = ui.config(b'command-templates', b'oneline-summary')
1226 if not spec:
1236 if not spec:
1227 spec = default_spec
1237 spec = default_spec
1228 if not spec:
1238 if not spec:
1229 spec = (
1239 spec = (
1230 b'{separate(" ", '
1240 b'{separate(" ", '
1231 b'label("oneline-summary.changeset", "{rev}:{node|short}")'
1241 b'label("oneline-summary.changeset", "{rev}:{node|short}")'
1232 b', '
1242 b', '
1233 b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names % "{label("oneline-summary.{namespace}", name)}", " "))}"), " ")'
1243 b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names % "{label("oneline-summary.{namespace}", name)}", " "))}"), " ")'
1234 b')} '
1244 b')} '
1235 b'"{label("oneline-summary.desc", desc|firstline)}"'
1245 b'"{label("oneline-summary.desc", desc|firstline)}"'
1236 )
1246 )
1237 text = rendertemplate(ctx, spec)
1247 text = rendertemplate(ctx, spec)
1238 return text.split(b'\n')[0]
1248 return text.split(b'\n')[0]
1239
1249
1240
1250
1241 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1251 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1242 r"""Convert old-style filename format string to template string
1252 r"""Convert old-style filename format string to template string
1243
1253
1244 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1254 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1245 'foo-{reporoot|basename}-{seqno}.patch'
1255 'foo-{reporoot|basename}-{seqno}.patch'
1246 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1256 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1247 '{rev}{tags % "{tag}"}{node}'
1257 '{rev}{tags % "{tag}"}{node}'
1248
1258
1249 '\' in outermost strings has to be escaped because it is a directory
1259 '\' in outermost strings has to be escaped because it is a directory
1250 separator on Windows:
1260 separator on Windows:
1251
1261
1252 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1262 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1253 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1263 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1254 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1264 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1255 '\\\\\\\\foo\\\\bar.patch'
1265 '\\\\\\\\foo\\\\bar.patch'
1256 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1266 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1257 '\\\\{tags % "{tag}"}'
1267 '\\\\{tags % "{tag}"}'
1258
1268
1259 but inner strings follow the template rules (i.e. '\' is taken as an
1269 but inner strings follow the template rules (i.e. '\' is taken as an
1260 escape character):
1270 escape character):
1261
1271
1262 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1272 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1263 '{"c:\\tmp"}'
1273 '{"c:\\tmp"}'
1264 """
1274 """
1265 expander = {
1275 expander = {
1266 b'H': b'{node}',
1276 b'H': b'{node}',
1267 b'R': b'{rev}',
1277 b'R': b'{rev}',
1268 b'h': b'{node|short}',
1278 b'h': b'{node|short}',
1269 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1279 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1270 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1280 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1271 b'%': b'%',
1281 b'%': b'%',
1272 b'b': b'{reporoot|basename}',
1282 b'b': b'{reporoot|basename}',
1273 }
1283 }
1274 if total is not None:
1284 if total is not None:
1275 expander[b'N'] = b'{total}'
1285 expander[b'N'] = b'{total}'
1276 if seqno is not None:
1286 if seqno is not None:
1277 expander[b'n'] = b'{seqno}'
1287 expander[b'n'] = b'{seqno}'
1278 if total is not None and seqno is not None:
1288 if total is not None and seqno is not None:
1279 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1289 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1280 if pathname is not None:
1290 if pathname is not None:
1281 expander[b's'] = b'{pathname|basename}'
1291 expander[b's'] = b'{pathname|basename}'
1282 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1292 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1283 expander[b'p'] = b'{pathname}'
1293 expander[b'p'] = b'{pathname}'
1284
1294
1285 newname = []
1295 newname = []
1286 for typ, start, end in templater.scantemplate(pat, raw=True):
1296 for typ, start, end in templater.scantemplate(pat, raw=True):
1287 if typ != b'string':
1297 if typ != b'string':
1288 newname.append(pat[start:end])
1298 newname.append(pat[start:end])
1289 continue
1299 continue
1290 i = start
1300 i = start
1291 while i < end:
1301 while i < end:
1292 n = pat.find(b'%', i, end)
1302 n = pat.find(b'%', i, end)
1293 if n < 0:
1303 if n < 0:
1294 newname.append(stringutil.escapestr(pat[i:end]))
1304 newname.append(stringutil.escapestr(pat[i:end]))
1295 break
1305 break
1296 newname.append(stringutil.escapestr(pat[i:n]))
1306 newname.append(stringutil.escapestr(pat[i:n]))
1297 if n + 2 > end:
1307 if n + 2 > end:
1298 raise error.Abort(
1308 raise error.Abort(
1299 _(b"incomplete format spec in output filename")
1309 _(b"incomplete format spec in output filename")
1300 )
1310 )
1301 c = pat[n + 1 : n + 2]
1311 c = pat[n + 1 : n + 2]
1302 i = n + 2
1312 i = n + 2
1303 try:
1313 try:
1304 newname.append(expander[c])
1314 newname.append(expander[c])
1305 except KeyError:
1315 except KeyError:
1306 raise error.Abort(
1316 raise error.Abort(
1307 _(b"invalid format spec '%%%s' in output filename") % c
1317 _(b"invalid format spec '%%%s' in output filename") % c
1308 )
1318 )
1309 return b''.join(newname)
1319 return b''.join(newname)
1310
1320
1311
1321
1312 def makefilename(ctx, pat, **props):
1322 def makefilename(ctx, pat, **props):
1313 if not pat:
1323 if not pat:
1314 return pat
1324 return pat
1315 tmpl = _buildfntemplate(pat, **props)
1325 tmpl = _buildfntemplate(pat, **props)
1316 # BUG: alias expansion shouldn't be made against template fragments
1326 # BUG: alias expansion shouldn't be made against template fragments
1317 # rewritten from %-format strings, but we have no easy way to partially
1327 # rewritten from %-format strings, but we have no easy way to partially
1318 # disable the expansion.
1328 # disable the expansion.
1319 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1329 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1320
1330
1321
1331
1322 def isstdiofilename(pat):
1332 def isstdiofilename(pat):
1323 """True if the given pat looks like a filename denoting stdin/stdout"""
1333 """True if the given pat looks like a filename denoting stdin/stdout"""
1324 return not pat or pat == b'-'
1334 return not pat or pat == b'-'
1325
1335
1326
1336
1327 class _unclosablefile(object):
1337 class _unclosablefile(object):
1328 def __init__(self, fp):
1338 def __init__(self, fp):
1329 self._fp = fp
1339 self._fp = fp
1330
1340
1331 def close(self):
1341 def close(self):
1332 pass
1342 pass
1333
1343
1334 def __iter__(self):
1344 def __iter__(self):
1335 return iter(self._fp)
1345 return iter(self._fp)
1336
1346
1337 def __getattr__(self, attr):
1347 def __getattr__(self, attr):
1338 return getattr(self._fp, attr)
1348 return getattr(self._fp, attr)
1339
1349
1340 def __enter__(self):
1350 def __enter__(self):
1341 return self
1351 return self
1342
1352
1343 def __exit__(self, exc_type, exc_value, exc_tb):
1353 def __exit__(self, exc_type, exc_value, exc_tb):
1344 pass
1354 pass
1345
1355
1346
1356
1347 def makefileobj(ctx, pat, mode=b'wb', **props):
1357 def makefileobj(ctx, pat, mode=b'wb', **props):
1348 writable = mode not in (b'r', b'rb')
1358 writable = mode not in (b'r', b'rb')
1349
1359
1350 if isstdiofilename(pat):
1360 if isstdiofilename(pat):
1351 repo = ctx.repo()
1361 repo = ctx.repo()
1352 if writable:
1362 if writable:
1353 fp = repo.ui.fout
1363 fp = repo.ui.fout
1354 else:
1364 else:
1355 fp = repo.ui.fin
1365 fp = repo.ui.fin
1356 return _unclosablefile(fp)
1366 return _unclosablefile(fp)
1357 fn = makefilename(ctx, pat, **props)
1367 fn = makefilename(ctx, pat, **props)
1358 return open(fn, mode)
1368 return open(fn, mode)
1359
1369
1360
1370
1361 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1371 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1362 """opens the changelog, manifest, a filelog or a given revlog"""
1372 """opens the changelog, manifest, a filelog or a given revlog"""
1363 cl = opts[b'changelog']
1373 cl = opts[b'changelog']
1364 mf = opts[b'manifest']
1374 mf = opts[b'manifest']
1365 dir = opts[b'dir']
1375 dir = opts[b'dir']
1366 msg = None
1376 msg = None
1367 if cl and mf:
1377 if cl and mf:
1368 msg = _(b'cannot specify --changelog and --manifest at the same time')
1378 msg = _(b'cannot specify --changelog and --manifest at the same time')
1369 elif cl and dir:
1379 elif cl and dir:
1370 msg = _(b'cannot specify --changelog and --dir at the same time')
1380 msg = _(b'cannot specify --changelog and --dir at the same time')
1371 elif cl or mf or dir:
1381 elif cl or mf or dir:
1372 if file_:
1382 if file_:
1373 msg = _(b'cannot specify filename with --changelog or --manifest')
1383 msg = _(b'cannot specify filename with --changelog or --manifest')
1374 elif not repo:
1384 elif not repo:
1375 msg = _(
1385 msg = _(
1376 b'cannot specify --changelog or --manifest or --dir '
1386 b'cannot specify --changelog or --manifest or --dir '
1377 b'without a repository'
1387 b'without a repository'
1378 )
1388 )
1379 if msg:
1389 if msg:
1380 raise error.InputError(msg)
1390 raise error.InputError(msg)
1381
1391
1382 r = None
1392 r = None
1383 if repo:
1393 if repo:
1384 if cl:
1394 if cl:
1385 r = repo.unfiltered().changelog
1395 r = repo.unfiltered().changelog
1386 elif dir:
1396 elif dir:
1387 if not scmutil.istreemanifest(repo):
1397 if not scmutil.istreemanifest(repo):
1388 raise error.InputError(
1398 raise error.InputError(
1389 _(
1399 _(
1390 b"--dir can only be used on repos with "
1400 b"--dir can only be used on repos with "
1391 b"treemanifest enabled"
1401 b"treemanifest enabled"
1392 )
1402 )
1393 )
1403 )
1394 if not dir.endswith(b'/'):
1404 if not dir.endswith(b'/'):
1395 dir = dir + b'/'
1405 dir = dir + b'/'
1396 dirlog = repo.manifestlog.getstorage(dir)
1406 dirlog = repo.manifestlog.getstorage(dir)
1397 if len(dirlog):
1407 if len(dirlog):
1398 r = dirlog
1408 r = dirlog
1399 elif mf:
1409 elif mf:
1400 r = repo.manifestlog.getstorage(b'')
1410 r = repo.manifestlog.getstorage(b'')
1401 elif file_:
1411 elif file_:
1402 filelog = repo.file(file_)
1412 filelog = repo.file(file_)
1403 if len(filelog):
1413 if len(filelog):
1404 r = filelog
1414 r = filelog
1405
1415
1406 # Not all storage may be revlogs. If requested, try to return an actual
1416 # Not all storage may be revlogs. If requested, try to return an actual
1407 # revlog instance.
1417 # revlog instance.
1408 if returnrevlog:
1418 if returnrevlog:
1409 if isinstance(r, revlog.revlog):
1419 if isinstance(r, revlog.revlog):
1410 pass
1420 pass
1411 elif util.safehasattr(r, b'_revlog'):
1421 elif util.safehasattr(r, b'_revlog'):
1412 r = r._revlog # pytype: disable=attribute-error
1422 r = r._revlog # pytype: disable=attribute-error
1413 elif r is not None:
1423 elif r is not None:
1414 raise error.InputError(
1424 raise error.InputError(
1415 _(b'%r does not appear to be a revlog') % r
1425 _(b'%r does not appear to be a revlog') % r
1416 )
1426 )
1417
1427
1418 if not r:
1428 if not r:
1419 if not returnrevlog:
1429 if not returnrevlog:
1420 raise error.InputError(_(b'cannot give path to non-revlog'))
1430 raise error.InputError(_(b'cannot give path to non-revlog'))
1421
1431
1422 if not file_:
1432 if not file_:
1423 raise error.CommandError(cmd, _(b'invalid arguments'))
1433 raise error.CommandError(cmd, _(b'invalid arguments'))
1424 if not os.path.isfile(file_):
1434 if not os.path.isfile(file_):
1425 raise error.InputError(_(b"revlog '%s' not found") % file_)
1435 raise error.InputError(_(b"revlog '%s' not found") % file_)
1426 r = revlog.revlog(
1436 r = revlog.revlog(
1427 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1437 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1428 )
1438 )
1429 return r
1439 return r
1430
1440
1431
1441
1432 def openrevlog(repo, cmd, file_, opts):
1442 def openrevlog(repo, cmd, file_, opts):
1433 """Obtain a revlog backing storage of an item.
1443 """Obtain a revlog backing storage of an item.
1434
1444
1435 This is similar to ``openstorage()`` except it always returns a revlog.
1445 This is similar to ``openstorage()`` except it always returns a revlog.
1436
1446
1437 In most cases, a caller cares about the main storage object - not the
1447 In most cases, a caller cares about the main storage object - not the
1438 revlog backing it. Therefore, this function should only be used by code
1448 revlog backing it. Therefore, this function should only be used by code
1439 that needs to examine low-level revlog implementation details. e.g. debug
1449 that needs to examine low-level revlog implementation details. e.g. debug
1440 commands.
1450 commands.
1441 """
1451 """
1442 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1452 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1443
1453
1444
1454
1445 def copy(ui, repo, pats, opts, rename=False):
1455 def copy(ui, repo, pats, opts, rename=False):
1446 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1456 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1447
1457
1448 # called with the repo lock held
1458 # called with the repo lock held
1449 #
1459 #
1450 # hgsep => pathname that uses "/" to separate directories
1460 # hgsep => pathname that uses "/" to separate directories
1451 # ossep => pathname that uses os.sep to separate directories
1461 # ossep => pathname that uses os.sep to separate directories
1452 cwd = repo.getcwd()
1462 cwd = repo.getcwd()
1453 targets = {}
1463 targets = {}
1454 forget = opts.get(b"forget")
1464 forget = opts.get(b"forget")
1455 after = opts.get(b"after")
1465 after = opts.get(b"after")
1456 dryrun = opts.get(b"dry_run")
1466 dryrun = opts.get(b"dry_run")
1457 rev = opts.get(b'at_rev')
1467 rev = opts.get(b'at_rev')
1458 if rev:
1468 if rev:
1459 if not forget and not after:
1469 if not forget and not after:
1460 # TODO: Remove this restriction and make it also create the copy
1470 # TODO: Remove this restriction and make it also create the copy
1461 # targets (and remove the rename source if rename==True).
1471 # targets (and remove the rename source if rename==True).
1462 raise error.InputError(_(b'--at-rev requires --after'))
1472 raise error.InputError(_(b'--at-rev requires --after'))
1463 ctx = scmutil.revsingle(repo, rev)
1473 ctx = scmutil.revsingle(repo, rev)
1464 if len(ctx.parents()) > 1:
1474 if len(ctx.parents()) > 1:
1465 raise error.InputError(
1475 raise error.InputError(
1466 _(b'cannot mark/unmark copy in merge commit')
1476 _(b'cannot mark/unmark copy in merge commit')
1467 )
1477 )
1468 else:
1478 else:
1469 ctx = repo[None]
1479 ctx = repo[None]
1470
1480
1471 pctx = ctx.p1()
1481 pctx = ctx.p1()
1472
1482
1473 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1483 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1474
1484
1475 if forget:
1485 if forget:
1476 if ctx.rev() is None:
1486 if ctx.rev() is None:
1477 new_ctx = ctx
1487 new_ctx = ctx
1478 else:
1488 else:
1479 if len(ctx.parents()) > 1:
1489 if len(ctx.parents()) > 1:
1480 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1490 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1481 # avoid cycle context -> subrepo -> cmdutil
1491 # avoid cycle context -> subrepo -> cmdutil
1482 from . import context
1492 from . import context
1483
1493
1484 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1494 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1485 new_ctx = context.overlayworkingctx(repo)
1495 new_ctx = context.overlayworkingctx(repo)
1486 new_ctx.setbase(ctx.p1())
1496 new_ctx.setbase(ctx.p1())
1487 mergemod.graft(repo, ctx, wctx=new_ctx)
1497 mergemod.graft(repo, ctx, wctx=new_ctx)
1488
1498
1489 match = scmutil.match(ctx, pats, opts)
1499 match = scmutil.match(ctx, pats, opts)
1490
1500
1491 current_copies = ctx.p1copies()
1501 current_copies = ctx.p1copies()
1492 current_copies.update(ctx.p2copies())
1502 current_copies.update(ctx.p2copies())
1493
1503
1494 uipathfn = scmutil.getuipathfn(repo)
1504 uipathfn = scmutil.getuipathfn(repo)
1495 for f in ctx.walk(match):
1505 for f in ctx.walk(match):
1496 if f in current_copies:
1506 if f in current_copies:
1497 new_ctx[f].markcopied(None)
1507 new_ctx[f].markcopied(None)
1498 elif match.exact(f):
1508 elif match.exact(f):
1499 ui.warn(
1509 ui.warn(
1500 _(
1510 _(
1501 b'%s: not unmarking as copy - file is not marked as copied\n'
1511 b'%s: not unmarking as copy - file is not marked as copied\n'
1502 )
1512 )
1503 % uipathfn(f)
1513 % uipathfn(f)
1504 )
1514 )
1505
1515
1506 if ctx.rev() is not None:
1516 if ctx.rev() is not None:
1507 with repo.lock():
1517 with repo.lock():
1508 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1518 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1509 new_node = mem_ctx.commit()
1519 new_node = mem_ctx.commit()
1510
1520
1511 if repo.dirstate.p1() == ctx.node():
1521 if repo.dirstate.p1() == ctx.node():
1512 with repo.dirstate.parentchange():
1522 with repo.dirstate.parentchange():
1513 scmutil.movedirstate(repo, repo[new_node])
1523 scmutil.movedirstate(repo, repo[new_node])
1514 replacements = {ctx.node(): [new_node]}
1524 replacements = {ctx.node(): [new_node]}
1515 scmutil.cleanupnodes(
1525 scmutil.cleanupnodes(
1516 repo, replacements, b'uncopy', fixphase=True
1526 repo, replacements, b'uncopy', fixphase=True
1517 )
1527 )
1518
1528
1519 return
1529 return
1520
1530
1521 pats = scmutil.expandpats(pats)
1531 pats = scmutil.expandpats(pats)
1522 if not pats:
1532 if not pats:
1523 raise error.InputError(_(b'no source or destination specified'))
1533 raise error.InputError(_(b'no source or destination specified'))
1524 if len(pats) == 1:
1534 if len(pats) == 1:
1525 raise error.InputError(_(b'no destination specified'))
1535 raise error.InputError(_(b'no destination specified'))
1526 dest = pats.pop()
1536 dest = pats.pop()
1527
1537
1528 def walkpat(pat):
1538 def walkpat(pat):
1529 srcs = []
1539 srcs = []
1530 # TODO: Inline and simplify the non-working-copy version of this code
1540 # TODO: Inline and simplify the non-working-copy version of this code
1531 # since it shares very little with the working-copy version of it.
1541 # since it shares very little with the working-copy version of it.
1532 ctx_to_walk = ctx if ctx.rev() is None else pctx
1542 ctx_to_walk = ctx if ctx.rev() is None else pctx
1533 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1543 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1534 for abs in ctx_to_walk.walk(m):
1544 for abs in ctx_to_walk.walk(m):
1535 rel = uipathfn(abs)
1545 rel = uipathfn(abs)
1536 exact = m.exact(abs)
1546 exact = m.exact(abs)
1537 if abs not in ctx:
1547 if abs not in ctx:
1538 if abs in pctx:
1548 if abs in pctx:
1539 if not after:
1549 if not after:
1540 if exact:
1550 if exact:
1541 ui.warn(
1551 ui.warn(
1542 _(
1552 _(
1543 b'%s: not copying - file has been marked '
1553 b'%s: not copying - file has been marked '
1544 b'for remove\n'
1554 b'for remove\n'
1545 )
1555 )
1546 % rel
1556 % rel
1547 )
1557 )
1548 continue
1558 continue
1549 else:
1559 else:
1550 if exact:
1560 if exact:
1551 ui.warn(
1561 ui.warn(
1552 _(b'%s: not copying - file is not managed\n') % rel
1562 _(b'%s: not copying - file is not managed\n') % rel
1553 )
1563 )
1554 continue
1564 continue
1555
1565
1556 # abs: hgsep
1566 # abs: hgsep
1557 # rel: ossep
1567 # rel: ossep
1558 srcs.append((abs, rel, exact))
1568 srcs.append((abs, rel, exact))
1559 return srcs
1569 return srcs
1560
1570
1561 if ctx.rev() is not None:
1571 if ctx.rev() is not None:
1562 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1572 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1563 absdest = pathutil.canonpath(repo.root, cwd, dest)
1573 absdest = pathutil.canonpath(repo.root, cwd, dest)
1564 if ctx.hasdir(absdest):
1574 if ctx.hasdir(absdest):
1565 raise error.InputError(
1575 raise error.InputError(
1566 _(b'%s: --at-rev does not support a directory as destination')
1576 _(b'%s: --at-rev does not support a directory as destination')
1567 % uipathfn(absdest)
1577 % uipathfn(absdest)
1568 )
1578 )
1569 if absdest not in ctx:
1579 if absdest not in ctx:
1570 raise error.InputError(
1580 raise error.InputError(
1571 _(b'%s: copy destination does not exist in %s')
1581 _(b'%s: copy destination does not exist in %s')
1572 % (uipathfn(absdest), ctx)
1582 % (uipathfn(absdest), ctx)
1573 )
1583 )
1574
1584
1575 # avoid cycle context -> subrepo -> cmdutil
1585 # avoid cycle context -> subrepo -> cmdutil
1576 from . import context
1586 from . import context
1577
1587
1578 copylist = []
1588 copylist = []
1579 for pat in pats:
1589 for pat in pats:
1580 srcs = walkpat(pat)
1590 srcs = walkpat(pat)
1581 if not srcs:
1591 if not srcs:
1582 continue
1592 continue
1583 for abs, rel, exact in srcs:
1593 for abs, rel, exact in srcs:
1584 copylist.append(abs)
1594 copylist.append(abs)
1585
1595
1586 if not copylist:
1596 if not copylist:
1587 raise error.InputError(_(b'no files to copy'))
1597 raise error.InputError(_(b'no files to copy'))
1588 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1598 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1589 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1599 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1590 # existing functions below.
1600 # existing functions below.
1591 if len(copylist) != 1:
1601 if len(copylist) != 1:
1592 raise error.InputError(_(b'--at-rev requires a single source'))
1602 raise error.InputError(_(b'--at-rev requires a single source'))
1593
1603
1594 new_ctx = context.overlayworkingctx(repo)
1604 new_ctx = context.overlayworkingctx(repo)
1595 new_ctx.setbase(ctx.p1())
1605 new_ctx.setbase(ctx.p1())
1596 mergemod.graft(repo, ctx, wctx=new_ctx)
1606 mergemod.graft(repo, ctx, wctx=new_ctx)
1597
1607
1598 new_ctx.markcopied(absdest, copylist[0])
1608 new_ctx.markcopied(absdest, copylist[0])
1599
1609
1600 with repo.lock():
1610 with repo.lock():
1601 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1611 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1602 new_node = mem_ctx.commit()
1612 new_node = mem_ctx.commit()
1603
1613
1604 if repo.dirstate.p1() == ctx.node():
1614 if repo.dirstate.p1() == ctx.node():
1605 with repo.dirstate.parentchange():
1615 with repo.dirstate.parentchange():
1606 scmutil.movedirstate(repo, repo[new_node])
1616 scmutil.movedirstate(repo, repo[new_node])
1607 replacements = {ctx.node(): [new_node]}
1617 replacements = {ctx.node(): [new_node]}
1608 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1618 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1609
1619
1610 return
1620 return
1611
1621
1612 # abssrc: hgsep
1622 # abssrc: hgsep
1613 # relsrc: ossep
1623 # relsrc: ossep
1614 # otarget: ossep
1624 # otarget: ossep
1615 def copyfile(abssrc, relsrc, otarget, exact):
1625 def copyfile(abssrc, relsrc, otarget, exact):
1616 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1626 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1617 if b'/' in abstarget:
1627 if b'/' in abstarget:
1618 # We cannot normalize abstarget itself, this would prevent
1628 # We cannot normalize abstarget itself, this would prevent
1619 # case only renames, like a => A.
1629 # case only renames, like a => A.
1620 abspath, absname = abstarget.rsplit(b'/', 1)
1630 abspath, absname = abstarget.rsplit(b'/', 1)
1621 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1631 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1622 reltarget = repo.pathto(abstarget, cwd)
1632 reltarget = repo.pathto(abstarget, cwd)
1623 target = repo.wjoin(abstarget)
1633 target = repo.wjoin(abstarget)
1624 src = repo.wjoin(abssrc)
1634 src = repo.wjoin(abssrc)
1625 state = repo.dirstate[abstarget]
1635 state = repo.dirstate[abstarget]
1626
1636
1627 scmutil.checkportable(ui, abstarget)
1637 scmutil.checkportable(ui, abstarget)
1628
1638
1629 # check for collisions
1639 # check for collisions
1630 prevsrc = targets.get(abstarget)
1640 prevsrc = targets.get(abstarget)
1631 if prevsrc is not None:
1641 if prevsrc is not None:
1632 ui.warn(
1642 ui.warn(
1633 _(b'%s: not overwriting - %s collides with %s\n')
1643 _(b'%s: not overwriting - %s collides with %s\n')
1634 % (
1644 % (
1635 reltarget,
1645 reltarget,
1636 repo.pathto(abssrc, cwd),
1646 repo.pathto(abssrc, cwd),
1637 repo.pathto(prevsrc, cwd),
1647 repo.pathto(prevsrc, cwd),
1638 )
1648 )
1639 )
1649 )
1640 return True # report a failure
1650 return True # report a failure
1641
1651
1642 # check for overwrites
1652 # check for overwrites
1643 exists = os.path.lexists(target)
1653 exists = os.path.lexists(target)
1644 samefile = False
1654 samefile = False
1645 if exists and abssrc != abstarget:
1655 if exists and abssrc != abstarget:
1646 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1656 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1647 abstarget
1657 abstarget
1648 ):
1658 ):
1649 if not rename:
1659 if not rename:
1650 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1660 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1651 return True # report a failure
1661 return True # report a failure
1652 exists = False
1662 exists = False
1653 samefile = True
1663 samefile = True
1654
1664
1655 if not after and exists or after and state in b'mn':
1665 if not after and exists or after and state in b'mn':
1656 if not opts[b'force']:
1666 if not opts[b'force']:
1657 if state in b'mn':
1667 if state in b'mn':
1658 msg = _(b'%s: not overwriting - file already committed\n')
1668 msg = _(b'%s: not overwriting - file already committed\n')
1659 if after:
1669 if after:
1660 flags = b'--after --force'
1670 flags = b'--after --force'
1661 else:
1671 else:
1662 flags = b'--force'
1672 flags = b'--force'
1663 if rename:
1673 if rename:
1664 hint = (
1674 hint = (
1665 _(
1675 _(
1666 b"('hg rename %s' to replace the file by "
1676 b"('hg rename %s' to replace the file by "
1667 b'recording a rename)\n'
1677 b'recording a rename)\n'
1668 )
1678 )
1669 % flags
1679 % flags
1670 )
1680 )
1671 else:
1681 else:
1672 hint = (
1682 hint = (
1673 _(
1683 _(
1674 b"('hg copy %s' to replace the file by "
1684 b"('hg copy %s' to replace the file by "
1675 b'recording a copy)\n'
1685 b'recording a copy)\n'
1676 )
1686 )
1677 % flags
1687 % flags
1678 )
1688 )
1679 else:
1689 else:
1680 msg = _(b'%s: not overwriting - file exists\n')
1690 msg = _(b'%s: not overwriting - file exists\n')
1681 if rename:
1691 if rename:
1682 hint = _(
1692 hint = _(
1683 b"('hg rename --after' to record the rename)\n"
1693 b"('hg rename --after' to record the rename)\n"
1684 )
1694 )
1685 else:
1695 else:
1686 hint = _(b"('hg copy --after' to record the copy)\n")
1696 hint = _(b"('hg copy --after' to record the copy)\n")
1687 ui.warn(msg % reltarget)
1697 ui.warn(msg % reltarget)
1688 ui.warn(hint)
1698 ui.warn(hint)
1689 return True # report a failure
1699 return True # report a failure
1690
1700
1691 if after:
1701 if after:
1692 if not exists:
1702 if not exists:
1693 if rename:
1703 if rename:
1694 ui.warn(
1704 ui.warn(
1695 _(b'%s: not recording move - %s does not exist\n')
1705 _(b'%s: not recording move - %s does not exist\n')
1696 % (relsrc, reltarget)
1706 % (relsrc, reltarget)
1697 )
1707 )
1698 else:
1708 else:
1699 ui.warn(
1709 ui.warn(
1700 _(b'%s: not recording copy - %s does not exist\n')
1710 _(b'%s: not recording copy - %s does not exist\n')
1701 % (relsrc, reltarget)
1711 % (relsrc, reltarget)
1702 )
1712 )
1703 return True # report a failure
1713 return True # report a failure
1704 elif not dryrun:
1714 elif not dryrun:
1705 try:
1715 try:
1706 if exists:
1716 if exists:
1707 os.unlink(target)
1717 os.unlink(target)
1708 targetdir = os.path.dirname(target) or b'.'
1718 targetdir = os.path.dirname(target) or b'.'
1709 if not os.path.isdir(targetdir):
1719 if not os.path.isdir(targetdir):
1710 os.makedirs(targetdir)
1720 os.makedirs(targetdir)
1711 if samefile:
1721 if samefile:
1712 tmp = target + b"~hgrename"
1722 tmp = target + b"~hgrename"
1713 os.rename(src, tmp)
1723 os.rename(src, tmp)
1714 os.rename(tmp, target)
1724 os.rename(tmp, target)
1715 else:
1725 else:
1716 # Preserve stat info on renames, not on copies; this matches
1726 # Preserve stat info on renames, not on copies; this matches
1717 # Linux CLI behavior.
1727 # Linux CLI behavior.
1718 util.copyfile(src, target, copystat=rename)
1728 util.copyfile(src, target, copystat=rename)
1719 srcexists = True
1729 srcexists = True
1720 except IOError as inst:
1730 except IOError as inst:
1721 if inst.errno == errno.ENOENT:
1731 if inst.errno == errno.ENOENT:
1722 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1732 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1723 srcexists = False
1733 srcexists = False
1724 else:
1734 else:
1725 ui.warn(
1735 ui.warn(
1726 _(b'%s: cannot copy - %s\n')
1736 _(b'%s: cannot copy - %s\n')
1727 % (relsrc, encoding.strtolocal(inst.strerror))
1737 % (relsrc, encoding.strtolocal(inst.strerror))
1728 )
1738 )
1729 return True # report a failure
1739 return True # report a failure
1730
1740
1731 if ui.verbose or not exact:
1741 if ui.verbose or not exact:
1732 if rename:
1742 if rename:
1733 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1743 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1734 else:
1744 else:
1735 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1745 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1736
1746
1737 targets[abstarget] = abssrc
1747 targets[abstarget] = abssrc
1738
1748
1739 # fix up dirstate
1749 # fix up dirstate
1740 scmutil.dirstatecopy(
1750 scmutil.dirstatecopy(
1741 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1751 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1742 )
1752 )
1743 if rename and not dryrun:
1753 if rename and not dryrun:
1744 if not after and srcexists and not samefile:
1754 if not after and srcexists and not samefile:
1745 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1755 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1746 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1756 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1747 ctx.forget([abssrc])
1757 ctx.forget([abssrc])
1748
1758
1749 # pat: ossep
1759 # pat: ossep
1750 # dest ossep
1760 # dest ossep
1751 # srcs: list of (hgsep, hgsep, ossep, bool)
1761 # srcs: list of (hgsep, hgsep, ossep, bool)
1752 # return: function that takes hgsep and returns ossep
1762 # return: function that takes hgsep and returns ossep
1753 def targetpathfn(pat, dest, srcs):
1763 def targetpathfn(pat, dest, srcs):
1754 if os.path.isdir(pat):
1764 if os.path.isdir(pat):
1755 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1765 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1756 abspfx = util.localpath(abspfx)
1766 abspfx = util.localpath(abspfx)
1757 if destdirexists:
1767 if destdirexists:
1758 striplen = len(os.path.split(abspfx)[0])
1768 striplen = len(os.path.split(abspfx)[0])
1759 else:
1769 else:
1760 striplen = len(abspfx)
1770 striplen = len(abspfx)
1761 if striplen:
1771 if striplen:
1762 striplen += len(pycompat.ossep)
1772 striplen += len(pycompat.ossep)
1763 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1773 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1764 elif destdirexists:
1774 elif destdirexists:
1765 res = lambda p: os.path.join(
1775 res = lambda p: os.path.join(
1766 dest, os.path.basename(util.localpath(p))
1776 dest, os.path.basename(util.localpath(p))
1767 )
1777 )
1768 else:
1778 else:
1769 res = lambda p: dest
1779 res = lambda p: dest
1770 return res
1780 return res
1771
1781
1772 # pat: ossep
1782 # pat: ossep
1773 # dest ossep
1783 # dest ossep
1774 # srcs: list of (hgsep, hgsep, ossep, bool)
1784 # srcs: list of (hgsep, hgsep, ossep, bool)
1775 # return: function that takes hgsep and returns ossep
1785 # return: function that takes hgsep and returns ossep
1776 def targetpathafterfn(pat, dest, srcs):
1786 def targetpathafterfn(pat, dest, srcs):
1777 if matchmod.patkind(pat):
1787 if matchmod.patkind(pat):
1778 # a mercurial pattern
1788 # a mercurial pattern
1779 res = lambda p: os.path.join(
1789 res = lambda p: os.path.join(
1780 dest, os.path.basename(util.localpath(p))
1790 dest, os.path.basename(util.localpath(p))
1781 )
1791 )
1782 else:
1792 else:
1783 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1793 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1784 if len(abspfx) < len(srcs[0][0]):
1794 if len(abspfx) < len(srcs[0][0]):
1785 # A directory. Either the target path contains the last
1795 # A directory. Either the target path contains the last
1786 # component of the source path or it does not.
1796 # component of the source path or it does not.
1787 def evalpath(striplen):
1797 def evalpath(striplen):
1788 score = 0
1798 score = 0
1789 for s in srcs:
1799 for s in srcs:
1790 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1800 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1791 if os.path.lexists(t):
1801 if os.path.lexists(t):
1792 score += 1
1802 score += 1
1793 return score
1803 return score
1794
1804
1795 abspfx = util.localpath(abspfx)
1805 abspfx = util.localpath(abspfx)
1796 striplen = len(abspfx)
1806 striplen = len(abspfx)
1797 if striplen:
1807 if striplen:
1798 striplen += len(pycompat.ossep)
1808 striplen += len(pycompat.ossep)
1799 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1809 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1800 score = evalpath(striplen)
1810 score = evalpath(striplen)
1801 striplen1 = len(os.path.split(abspfx)[0])
1811 striplen1 = len(os.path.split(abspfx)[0])
1802 if striplen1:
1812 if striplen1:
1803 striplen1 += len(pycompat.ossep)
1813 striplen1 += len(pycompat.ossep)
1804 if evalpath(striplen1) > score:
1814 if evalpath(striplen1) > score:
1805 striplen = striplen1
1815 striplen = striplen1
1806 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1816 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1807 else:
1817 else:
1808 # a file
1818 # a file
1809 if destdirexists:
1819 if destdirexists:
1810 res = lambda p: os.path.join(
1820 res = lambda p: os.path.join(
1811 dest, os.path.basename(util.localpath(p))
1821 dest, os.path.basename(util.localpath(p))
1812 )
1822 )
1813 else:
1823 else:
1814 res = lambda p: dest
1824 res = lambda p: dest
1815 return res
1825 return res
1816
1826
1817 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1827 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1818 if not destdirexists:
1828 if not destdirexists:
1819 if len(pats) > 1 or matchmod.patkind(pats[0]):
1829 if len(pats) > 1 or matchmod.patkind(pats[0]):
1820 raise error.InputError(
1830 raise error.InputError(
1821 _(
1831 _(
1822 b'with multiple sources, destination must be an '
1832 b'with multiple sources, destination must be an '
1823 b'existing directory'
1833 b'existing directory'
1824 )
1834 )
1825 )
1835 )
1826 if util.endswithsep(dest):
1836 if util.endswithsep(dest):
1827 raise error.InputError(
1837 raise error.InputError(
1828 _(b'destination %s is not a directory') % dest
1838 _(b'destination %s is not a directory') % dest
1829 )
1839 )
1830
1840
1831 tfn = targetpathfn
1841 tfn = targetpathfn
1832 if after:
1842 if after:
1833 tfn = targetpathafterfn
1843 tfn = targetpathafterfn
1834 copylist = []
1844 copylist = []
1835 for pat in pats:
1845 for pat in pats:
1836 srcs = walkpat(pat)
1846 srcs = walkpat(pat)
1837 if not srcs:
1847 if not srcs:
1838 continue
1848 continue
1839 copylist.append((tfn(pat, dest, srcs), srcs))
1849 copylist.append((tfn(pat, dest, srcs), srcs))
1840 if not copylist:
1850 if not copylist:
1841 raise error.InputError(_(b'no files to copy'))
1851 raise error.InputError(_(b'no files to copy'))
1842
1852
1843 errors = 0
1853 errors = 0
1844 for targetpath, srcs in copylist:
1854 for targetpath, srcs in copylist:
1845 for abssrc, relsrc, exact in srcs:
1855 for abssrc, relsrc, exact in srcs:
1846 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1856 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1847 errors += 1
1857 errors += 1
1848
1858
1849 return errors != 0
1859 return errors != 0
1850
1860
1851
1861
1852 ## facility to let extension process additional data into an import patch
1862 ## facility to let extension process additional data into an import patch
1853 # list of identifier to be executed in order
1863 # list of identifier to be executed in order
1854 extrapreimport = [] # run before commit
1864 extrapreimport = [] # run before commit
1855 extrapostimport = [] # run after commit
1865 extrapostimport = [] # run after commit
1856 # mapping from identifier to actual import function
1866 # mapping from identifier to actual import function
1857 #
1867 #
1858 # 'preimport' are run before the commit is made and are provided the following
1868 # 'preimport' are run before the commit is made and are provided the following
1859 # arguments:
1869 # arguments:
1860 # - repo: the localrepository instance,
1870 # - repo: the localrepository instance,
1861 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1871 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1862 # - extra: the future extra dictionary of the changeset, please mutate it,
1872 # - extra: the future extra dictionary of the changeset, please mutate it,
1863 # - opts: the import options.
1873 # - opts: the import options.
1864 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1874 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1865 # mutation of in memory commit and more. Feel free to rework the code to get
1875 # mutation of in memory commit and more. Feel free to rework the code to get
1866 # there.
1876 # there.
1867 extrapreimportmap = {}
1877 extrapreimportmap = {}
1868 # 'postimport' are run after the commit is made and are provided the following
1878 # 'postimport' are run after the commit is made and are provided the following
1869 # argument:
1879 # argument:
1870 # - ctx: the changectx created by import.
1880 # - ctx: the changectx created by import.
1871 extrapostimportmap = {}
1881 extrapostimportmap = {}
1872
1882
1873
1883
1874 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1884 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1875 """Utility function used by commands.import to import a single patch
1885 """Utility function used by commands.import to import a single patch
1876
1886
1877 This function is explicitly defined here to help the evolve extension to
1887 This function is explicitly defined here to help the evolve extension to
1878 wrap this part of the import logic.
1888 wrap this part of the import logic.
1879
1889
1880 The API is currently a bit ugly because it a simple code translation from
1890 The API is currently a bit ugly because it a simple code translation from
1881 the import command. Feel free to make it better.
1891 the import command. Feel free to make it better.
1882
1892
1883 :patchdata: a dictionary containing parsed patch data (such as from
1893 :patchdata: a dictionary containing parsed patch data (such as from
1884 ``patch.extract()``)
1894 ``patch.extract()``)
1885 :parents: nodes that will be parent of the created commit
1895 :parents: nodes that will be parent of the created commit
1886 :opts: the full dict of option passed to the import command
1896 :opts: the full dict of option passed to the import command
1887 :msgs: list to save commit message to.
1897 :msgs: list to save commit message to.
1888 (used in case we need to save it when failing)
1898 (used in case we need to save it when failing)
1889 :updatefunc: a function that update a repo to a given node
1899 :updatefunc: a function that update a repo to a given node
1890 updatefunc(<repo>, <node>)
1900 updatefunc(<repo>, <node>)
1891 """
1901 """
1892 # avoid cycle context -> subrepo -> cmdutil
1902 # avoid cycle context -> subrepo -> cmdutil
1893 from . import context
1903 from . import context
1894
1904
1895 tmpname = patchdata.get(b'filename')
1905 tmpname = patchdata.get(b'filename')
1896 message = patchdata.get(b'message')
1906 message = patchdata.get(b'message')
1897 user = opts.get(b'user') or patchdata.get(b'user')
1907 user = opts.get(b'user') or patchdata.get(b'user')
1898 date = opts.get(b'date') or patchdata.get(b'date')
1908 date = opts.get(b'date') or patchdata.get(b'date')
1899 branch = patchdata.get(b'branch')
1909 branch = patchdata.get(b'branch')
1900 nodeid = patchdata.get(b'nodeid')
1910 nodeid = patchdata.get(b'nodeid')
1901 p1 = patchdata.get(b'p1')
1911 p1 = patchdata.get(b'p1')
1902 p2 = patchdata.get(b'p2')
1912 p2 = patchdata.get(b'p2')
1903
1913
1904 nocommit = opts.get(b'no_commit')
1914 nocommit = opts.get(b'no_commit')
1905 importbranch = opts.get(b'import_branch')
1915 importbranch = opts.get(b'import_branch')
1906 update = not opts.get(b'bypass')
1916 update = not opts.get(b'bypass')
1907 strip = opts[b"strip"]
1917 strip = opts[b"strip"]
1908 prefix = opts[b"prefix"]
1918 prefix = opts[b"prefix"]
1909 sim = float(opts.get(b'similarity') or 0)
1919 sim = float(opts.get(b'similarity') or 0)
1910
1920
1911 if not tmpname:
1921 if not tmpname:
1912 return None, None, False
1922 return None, None, False
1913
1923
1914 rejects = False
1924 rejects = False
1915
1925
1916 cmdline_message = logmessage(ui, opts)
1926 cmdline_message = logmessage(ui, opts)
1917 if cmdline_message:
1927 if cmdline_message:
1918 # pickup the cmdline msg
1928 # pickup the cmdline msg
1919 message = cmdline_message
1929 message = cmdline_message
1920 elif message:
1930 elif message:
1921 # pickup the patch msg
1931 # pickup the patch msg
1922 message = message.strip()
1932 message = message.strip()
1923 else:
1933 else:
1924 # launch the editor
1934 # launch the editor
1925 message = None
1935 message = None
1926 ui.debug(b'message:\n%s\n' % (message or b''))
1936 ui.debug(b'message:\n%s\n' % (message or b''))
1927
1937
1928 if len(parents) == 1:
1938 if len(parents) == 1:
1929 parents.append(repo[nullid])
1939 parents.append(repo[nullid])
1930 if opts.get(b'exact'):
1940 if opts.get(b'exact'):
1931 if not nodeid or not p1:
1941 if not nodeid or not p1:
1932 raise error.InputError(_(b'not a Mercurial patch'))
1942 raise error.InputError(_(b'not a Mercurial patch'))
1933 p1 = repo[p1]
1943 p1 = repo[p1]
1934 p2 = repo[p2 or nullid]
1944 p2 = repo[p2 or nullid]
1935 elif p2:
1945 elif p2:
1936 try:
1946 try:
1937 p1 = repo[p1]
1947 p1 = repo[p1]
1938 p2 = repo[p2]
1948 p2 = repo[p2]
1939 # Without any options, consider p2 only if the
1949 # Without any options, consider p2 only if the
1940 # patch is being applied on top of the recorded
1950 # patch is being applied on top of the recorded
1941 # first parent.
1951 # first parent.
1942 if p1 != parents[0]:
1952 if p1 != parents[0]:
1943 p1 = parents[0]
1953 p1 = parents[0]
1944 p2 = repo[nullid]
1954 p2 = repo[nullid]
1945 except error.RepoError:
1955 except error.RepoError:
1946 p1, p2 = parents
1956 p1, p2 = parents
1947 if p2.node() == nullid:
1957 if p2.node() == nullid:
1948 ui.warn(
1958 ui.warn(
1949 _(
1959 _(
1950 b"warning: import the patch as a normal revision\n"
1960 b"warning: import the patch as a normal revision\n"
1951 b"(use --exact to import the patch as a merge)\n"
1961 b"(use --exact to import the patch as a merge)\n"
1952 )
1962 )
1953 )
1963 )
1954 else:
1964 else:
1955 p1, p2 = parents
1965 p1, p2 = parents
1956
1966
1957 n = None
1967 n = None
1958 if update:
1968 if update:
1959 if p1 != parents[0]:
1969 if p1 != parents[0]:
1960 updatefunc(repo, p1.node())
1970 updatefunc(repo, p1.node())
1961 if p2 != parents[1]:
1971 if p2 != parents[1]:
1962 repo.setparents(p1.node(), p2.node())
1972 repo.setparents(p1.node(), p2.node())
1963
1973
1964 if opts.get(b'exact') or importbranch:
1974 if opts.get(b'exact') or importbranch:
1965 repo.dirstate.setbranch(branch or b'default')
1975 repo.dirstate.setbranch(branch or b'default')
1966
1976
1967 partial = opts.get(b'partial', False)
1977 partial = opts.get(b'partial', False)
1968 files = set()
1978 files = set()
1969 try:
1979 try:
1970 patch.patch(
1980 patch.patch(
1971 ui,
1981 ui,
1972 repo,
1982 repo,
1973 tmpname,
1983 tmpname,
1974 strip=strip,
1984 strip=strip,
1975 prefix=prefix,
1985 prefix=prefix,
1976 files=files,
1986 files=files,
1977 eolmode=None,
1987 eolmode=None,
1978 similarity=sim / 100.0,
1988 similarity=sim / 100.0,
1979 )
1989 )
1980 except error.PatchError as e:
1990 except error.PatchError as e:
1981 if not partial:
1991 if not partial:
1982 raise error.Abort(pycompat.bytestr(e))
1992 raise error.Abort(pycompat.bytestr(e))
1983 if partial:
1993 if partial:
1984 rejects = True
1994 rejects = True
1985
1995
1986 files = list(files)
1996 files = list(files)
1987 if nocommit:
1997 if nocommit:
1988 if message:
1998 if message:
1989 msgs.append(message)
1999 msgs.append(message)
1990 else:
2000 else:
1991 if opts.get(b'exact') or p2:
2001 if opts.get(b'exact') or p2:
1992 # If you got here, you either use --force and know what
2002 # If you got here, you either use --force and know what
1993 # you are doing or used --exact or a merge patch while
2003 # you are doing or used --exact or a merge patch while
1994 # being updated to its first parent.
2004 # being updated to its first parent.
1995 m = None
2005 m = None
1996 else:
2006 else:
1997 m = scmutil.matchfiles(repo, files or [])
2007 m = scmutil.matchfiles(repo, files or [])
1998 editform = mergeeditform(repo[None], b'import.normal')
2008 editform = mergeeditform(repo[None], b'import.normal')
1999 if opts.get(b'exact'):
2009 if opts.get(b'exact'):
2000 editor = None
2010 editor = None
2001 else:
2011 else:
2002 editor = getcommiteditor(
2012 editor = getcommiteditor(
2003 editform=editform, **pycompat.strkwargs(opts)
2013 editform=editform, **pycompat.strkwargs(opts)
2004 )
2014 )
2005 extra = {}
2015 extra = {}
2006 for idfunc in extrapreimport:
2016 for idfunc in extrapreimport:
2007 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
2017 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
2008 overrides = {}
2018 overrides = {}
2009 if partial:
2019 if partial:
2010 overrides[(b'ui', b'allowemptycommit')] = True
2020 overrides[(b'ui', b'allowemptycommit')] = True
2011 if opts.get(b'secret'):
2021 if opts.get(b'secret'):
2012 overrides[(b'phases', b'new-commit')] = b'secret'
2022 overrides[(b'phases', b'new-commit')] = b'secret'
2013 with repo.ui.configoverride(overrides, b'import'):
2023 with repo.ui.configoverride(overrides, b'import'):
2014 n = repo.commit(
2024 n = repo.commit(
2015 message, user, date, match=m, editor=editor, extra=extra
2025 message, user, date, match=m, editor=editor, extra=extra
2016 )
2026 )
2017 for idfunc in extrapostimport:
2027 for idfunc in extrapostimport:
2018 extrapostimportmap[idfunc](repo[n])
2028 extrapostimportmap[idfunc](repo[n])
2019 else:
2029 else:
2020 if opts.get(b'exact') or importbranch:
2030 if opts.get(b'exact') or importbranch:
2021 branch = branch or b'default'
2031 branch = branch or b'default'
2022 else:
2032 else:
2023 branch = p1.branch()
2033 branch = p1.branch()
2024 store = patch.filestore()
2034 store = patch.filestore()
2025 try:
2035 try:
2026 files = set()
2036 files = set()
2027 try:
2037 try:
2028 patch.patchrepo(
2038 patch.patchrepo(
2029 ui,
2039 ui,
2030 repo,
2040 repo,
2031 p1,
2041 p1,
2032 store,
2042 store,
2033 tmpname,
2043 tmpname,
2034 strip,
2044 strip,
2035 prefix,
2045 prefix,
2036 files,
2046 files,
2037 eolmode=None,
2047 eolmode=None,
2038 )
2048 )
2039 except error.PatchError as e:
2049 except error.PatchError as e:
2040 raise error.Abort(stringutil.forcebytestr(e))
2050 raise error.Abort(stringutil.forcebytestr(e))
2041 if opts.get(b'exact'):
2051 if opts.get(b'exact'):
2042 editor = None
2052 editor = None
2043 else:
2053 else:
2044 editor = getcommiteditor(editform=b'import.bypass')
2054 editor = getcommiteditor(editform=b'import.bypass')
2045 memctx = context.memctx(
2055 memctx = context.memctx(
2046 repo,
2056 repo,
2047 (p1.node(), p2.node()),
2057 (p1.node(), p2.node()),
2048 message,
2058 message,
2049 files=files,
2059 files=files,
2050 filectxfn=store,
2060 filectxfn=store,
2051 user=user,
2061 user=user,
2052 date=date,
2062 date=date,
2053 branch=branch,
2063 branch=branch,
2054 editor=editor,
2064 editor=editor,
2055 )
2065 )
2056
2066
2057 overrides = {}
2067 overrides = {}
2058 if opts.get(b'secret'):
2068 if opts.get(b'secret'):
2059 overrides[(b'phases', b'new-commit')] = b'secret'
2069 overrides[(b'phases', b'new-commit')] = b'secret'
2060 with repo.ui.configoverride(overrides, b'import'):
2070 with repo.ui.configoverride(overrides, b'import'):
2061 n = memctx.commit()
2071 n = memctx.commit()
2062 finally:
2072 finally:
2063 store.close()
2073 store.close()
2064 if opts.get(b'exact') and nocommit:
2074 if opts.get(b'exact') and nocommit:
2065 # --exact with --no-commit is still useful in that it does merge
2075 # --exact with --no-commit is still useful in that it does merge
2066 # and branch bits
2076 # and branch bits
2067 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2077 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2068 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2078 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2069 raise error.Abort(_(b'patch is damaged or loses information'))
2079 raise error.Abort(_(b'patch is damaged or loses information'))
2070 msg = _(b'applied to working directory')
2080 msg = _(b'applied to working directory')
2071 if n:
2081 if n:
2072 # i18n: refers to a short changeset id
2082 # i18n: refers to a short changeset id
2073 msg = _(b'created %s') % short(n)
2083 msg = _(b'created %s') % short(n)
2074 return msg, n, rejects
2084 return msg, n, rejects
2075
2085
2076
2086
2077 # facility to let extensions include additional data in an exported patch
2087 # facility to let extensions include additional data in an exported patch
2078 # list of identifiers to be executed in order
2088 # list of identifiers to be executed in order
2079 extraexport = []
2089 extraexport = []
2080 # mapping from identifier to actual export function
2090 # mapping from identifier to actual export function
2081 # function as to return a string to be added to the header or None
2091 # function as to return a string to be added to the header or None
2082 # it is given two arguments (sequencenumber, changectx)
2092 # it is given two arguments (sequencenumber, changectx)
2083 extraexportmap = {}
2093 extraexportmap = {}
2084
2094
2085
2095
2086 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2096 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2087 node = scmutil.binnode(ctx)
2097 node = scmutil.binnode(ctx)
2088 parents = [p.node() for p in ctx.parents() if p]
2098 parents = [p.node() for p in ctx.parents() if p]
2089 branch = ctx.branch()
2099 branch = ctx.branch()
2090 if switch_parent:
2100 if switch_parent:
2091 parents.reverse()
2101 parents.reverse()
2092
2102
2093 if parents:
2103 if parents:
2094 prev = parents[0]
2104 prev = parents[0]
2095 else:
2105 else:
2096 prev = nullid
2106 prev = nullid
2097
2107
2098 fm.context(ctx=ctx)
2108 fm.context(ctx=ctx)
2099 fm.plain(b'# HG changeset patch\n')
2109 fm.plain(b'# HG changeset patch\n')
2100 fm.write(b'user', b'# User %s\n', ctx.user())
2110 fm.write(b'user', b'# User %s\n', ctx.user())
2101 fm.plain(b'# Date %d %d\n' % ctx.date())
2111 fm.plain(b'# Date %d %d\n' % ctx.date())
2102 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2112 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2103 fm.condwrite(
2113 fm.condwrite(
2104 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2114 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2105 )
2115 )
2106 fm.write(b'node', b'# Node ID %s\n', hex(node))
2116 fm.write(b'node', b'# Node ID %s\n', hex(node))
2107 fm.plain(b'# Parent %s\n' % hex(prev))
2117 fm.plain(b'# Parent %s\n' % hex(prev))
2108 if len(parents) > 1:
2118 if len(parents) > 1:
2109 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2119 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2110 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2120 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2111
2121
2112 # TODO: redesign extraexportmap function to support formatter
2122 # TODO: redesign extraexportmap function to support formatter
2113 for headerid in extraexport:
2123 for headerid in extraexport:
2114 header = extraexportmap[headerid](seqno, ctx)
2124 header = extraexportmap[headerid](seqno, ctx)
2115 if header is not None:
2125 if header is not None:
2116 fm.plain(b'# %s\n' % header)
2126 fm.plain(b'# %s\n' % header)
2117
2127
2118 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2128 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2119 fm.plain(b'\n')
2129 fm.plain(b'\n')
2120
2130
2121 if fm.isplain():
2131 if fm.isplain():
2122 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2132 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2123 for chunk, label in chunkiter:
2133 for chunk, label in chunkiter:
2124 fm.plain(chunk, label=label)
2134 fm.plain(chunk, label=label)
2125 else:
2135 else:
2126 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2136 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2127 # TODO: make it structured?
2137 # TODO: make it structured?
2128 fm.data(diff=b''.join(chunkiter))
2138 fm.data(diff=b''.join(chunkiter))
2129
2139
2130
2140
2131 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2141 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2132 """Export changesets to stdout or a single file"""
2142 """Export changesets to stdout or a single file"""
2133 for seqno, rev in enumerate(revs, 1):
2143 for seqno, rev in enumerate(revs, 1):
2134 ctx = repo[rev]
2144 ctx = repo[rev]
2135 if not dest.startswith(b'<'):
2145 if not dest.startswith(b'<'):
2136 repo.ui.note(b"%s\n" % dest)
2146 repo.ui.note(b"%s\n" % dest)
2137 fm.startitem()
2147 fm.startitem()
2138 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2148 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2139
2149
2140
2150
2141 def _exportfntemplate(
2151 def _exportfntemplate(
2142 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2152 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2143 ):
2153 ):
2144 """Export changesets to possibly multiple files"""
2154 """Export changesets to possibly multiple files"""
2145 total = len(revs)
2155 total = len(revs)
2146 revwidth = max(len(str(rev)) for rev in revs)
2156 revwidth = max(len(str(rev)) for rev in revs)
2147 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2157 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2148
2158
2149 for seqno, rev in enumerate(revs, 1):
2159 for seqno, rev in enumerate(revs, 1):
2150 ctx = repo[rev]
2160 ctx = repo[rev]
2151 dest = makefilename(
2161 dest = makefilename(
2152 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2162 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2153 )
2163 )
2154 filemap.setdefault(dest, []).append((seqno, rev))
2164 filemap.setdefault(dest, []).append((seqno, rev))
2155
2165
2156 for dest in filemap:
2166 for dest in filemap:
2157 with formatter.maybereopen(basefm, dest) as fm:
2167 with formatter.maybereopen(basefm, dest) as fm:
2158 repo.ui.note(b"%s\n" % dest)
2168 repo.ui.note(b"%s\n" % dest)
2159 for seqno, rev in filemap[dest]:
2169 for seqno, rev in filemap[dest]:
2160 fm.startitem()
2170 fm.startitem()
2161 ctx = repo[rev]
2171 ctx = repo[rev]
2162 _exportsingle(
2172 _exportsingle(
2163 repo, ctx, fm, match, switch_parent, seqno, diffopts
2173 repo, ctx, fm, match, switch_parent, seqno, diffopts
2164 )
2174 )
2165
2175
2166
2176
2167 def _prefetchchangedfiles(repo, revs, match):
2177 def _prefetchchangedfiles(repo, revs, match):
2168 allfiles = set()
2178 allfiles = set()
2169 for rev in revs:
2179 for rev in revs:
2170 for file in repo[rev].files():
2180 for file in repo[rev].files():
2171 if not match or match(file):
2181 if not match or match(file):
2172 allfiles.add(file)
2182 allfiles.add(file)
2173 match = scmutil.matchfiles(repo, allfiles)
2183 match = scmutil.matchfiles(repo, allfiles)
2174 revmatches = [(rev, match) for rev in revs]
2184 revmatches = [(rev, match) for rev in revs]
2175 scmutil.prefetchfiles(repo, revmatches)
2185 scmutil.prefetchfiles(repo, revmatches)
2176
2186
2177
2187
2178 def export(
2188 def export(
2179 repo,
2189 repo,
2180 revs,
2190 revs,
2181 basefm,
2191 basefm,
2182 fntemplate=b'hg-%h.patch',
2192 fntemplate=b'hg-%h.patch',
2183 switch_parent=False,
2193 switch_parent=False,
2184 opts=None,
2194 opts=None,
2185 match=None,
2195 match=None,
2186 ):
2196 ):
2187 """export changesets as hg patches
2197 """export changesets as hg patches
2188
2198
2189 Args:
2199 Args:
2190 repo: The repository from which we're exporting revisions.
2200 repo: The repository from which we're exporting revisions.
2191 revs: A list of revisions to export as revision numbers.
2201 revs: A list of revisions to export as revision numbers.
2192 basefm: A formatter to which patches should be written.
2202 basefm: A formatter to which patches should be written.
2193 fntemplate: An optional string to use for generating patch file names.
2203 fntemplate: An optional string to use for generating patch file names.
2194 switch_parent: If True, show diffs against second parent when not nullid.
2204 switch_parent: If True, show diffs against second parent when not nullid.
2195 Default is false, which always shows diff against p1.
2205 Default is false, which always shows diff against p1.
2196 opts: diff options to use for generating the patch.
2206 opts: diff options to use for generating the patch.
2197 match: If specified, only export changes to files matching this matcher.
2207 match: If specified, only export changes to files matching this matcher.
2198
2208
2199 Returns:
2209 Returns:
2200 Nothing.
2210 Nothing.
2201
2211
2202 Side Effect:
2212 Side Effect:
2203 "HG Changeset Patch" data is emitted to one of the following
2213 "HG Changeset Patch" data is emitted to one of the following
2204 destinations:
2214 destinations:
2205 fntemplate specified: Each rev is written to a unique file named using
2215 fntemplate specified: Each rev is written to a unique file named using
2206 the given template.
2216 the given template.
2207 Otherwise: All revs will be written to basefm.
2217 Otherwise: All revs will be written to basefm.
2208 """
2218 """
2209 _prefetchchangedfiles(repo, revs, match)
2219 _prefetchchangedfiles(repo, revs, match)
2210
2220
2211 if not fntemplate:
2221 if not fntemplate:
2212 _exportfile(
2222 _exportfile(
2213 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2223 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2214 )
2224 )
2215 else:
2225 else:
2216 _exportfntemplate(
2226 _exportfntemplate(
2217 repo, revs, basefm, fntemplate, switch_parent, opts, match
2227 repo, revs, basefm, fntemplate, switch_parent, opts, match
2218 )
2228 )
2219
2229
2220
2230
2221 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2231 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2222 """Export changesets to the given file stream"""
2232 """Export changesets to the given file stream"""
2223 _prefetchchangedfiles(repo, revs, match)
2233 _prefetchchangedfiles(repo, revs, match)
2224
2234
2225 dest = getattr(fp, 'name', b'<unnamed>')
2235 dest = getattr(fp, 'name', b'<unnamed>')
2226 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2236 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2227 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2237 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2228
2238
2229
2239
2230 def showmarker(fm, marker, index=None):
2240 def showmarker(fm, marker, index=None):
2231 """utility function to display obsolescence marker in a readable way
2241 """utility function to display obsolescence marker in a readable way
2232
2242
2233 To be used by debug function."""
2243 To be used by debug function."""
2234 if index is not None:
2244 if index is not None:
2235 fm.write(b'index', b'%i ', index)
2245 fm.write(b'index', b'%i ', index)
2236 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2246 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2237 succs = marker.succnodes()
2247 succs = marker.succnodes()
2238 fm.condwrite(
2248 fm.condwrite(
2239 succs,
2249 succs,
2240 b'succnodes',
2250 b'succnodes',
2241 b'%s ',
2251 b'%s ',
2242 fm.formatlist(map(hex, succs), name=b'node'),
2252 fm.formatlist(map(hex, succs), name=b'node'),
2243 )
2253 )
2244 fm.write(b'flag', b'%X ', marker.flags())
2254 fm.write(b'flag', b'%X ', marker.flags())
2245 parents = marker.parentnodes()
2255 parents = marker.parentnodes()
2246 if parents is not None:
2256 if parents is not None:
2247 fm.write(
2257 fm.write(
2248 b'parentnodes',
2258 b'parentnodes',
2249 b'{%s} ',
2259 b'{%s} ',
2250 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2260 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2251 )
2261 )
2252 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2262 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2253 meta = marker.metadata().copy()
2263 meta = marker.metadata().copy()
2254 meta.pop(b'date', None)
2264 meta.pop(b'date', None)
2255 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2265 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2256 fm.write(
2266 fm.write(
2257 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2267 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2258 )
2268 )
2259 fm.plain(b'\n')
2269 fm.plain(b'\n')
2260
2270
2261
2271
2262 def finddate(ui, repo, date):
2272 def finddate(ui, repo, date):
2263 """Find the tipmost changeset that matches the given date spec"""
2273 """Find the tipmost changeset that matches the given date spec"""
2264 mrevs = repo.revs(b'date(%s)', date)
2274 mrevs = repo.revs(b'date(%s)', date)
2265 try:
2275 try:
2266 rev = mrevs.max()
2276 rev = mrevs.max()
2267 except ValueError:
2277 except ValueError:
2268 raise error.InputError(_(b"revision matching date not found"))
2278 raise error.InputError(_(b"revision matching date not found"))
2269
2279
2270 ui.status(
2280 ui.status(
2271 _(b"found revision %d from %s\n")
2281 _(b"found revision %d from %s\n")
2272 % (rev, dateutil.datestr(repo[rev].date()))
2282 % (rev, dateutil.datestr(repo[rev].date()))
2273 )
2283 )
2274 return b'%d' % rev
2284 return b'%d' % rev
2275
2285
2276
2286
2277 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2287 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2278 bad = []
2288 bad = []
2279
2289
2280 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2290 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2281 names = []
2291 names = []
2282 wctx = repo[None]
2292 wctx = repo[None]
2283 cca = None
2293 cca = None
2284 abort, warn = scmutil.checkportabilityalert(ui)
2294 abort, warn = scmutil.checkportabilityalert(ui)
2285 if abort or warn:
2295 if abort or warn:
2286 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2296 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2287
2297
2288 match = repo.narrowmatch(match, includeexact=True)
2298 match = repo.narrowmatch(match, includeexact=True)
2289 badmatch = matchmod.badmatch(match, badfn)
2299 badmatch = matchmod.badmatch(match, badfn)
2290 dirstate = repo.dirstate
2300 dirstate = repo.dirstate
2291 # We don't want to just call wctx.walk here, since it would return a lot of
2301 # We don't want to just call wctx.walk here, since it would return a lot of
2292 # clean files, which we aren't interested in and takes time.
2302 # clean files, which we aren't interested in and takes time.
2293 for f in sorted(
2303 for f in sorted(
2294 dirstate.walk(
2304 dirstate.walk(
2295 badmatch,
2305 badmatch,
2296 subrepos=sorted(wctx.substate),
2306 subrepos=sorted(wctx.substate),
2297 unknown=True,
2307 unknown=True,
2298 ignored=False,
2308 ignored=False,
2299 full=False,
2309 full=False,
2300 )
2310 )
2301 ):
2311 ):
2302 exact = match.exact(f)
2312 exact = match.exact(f)
2303 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2313 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2304 if cca:
2314 if cca:
2305 cca(f)
2315 cca(f)
2306 names.append(f)
2316 names.append(f)
2307 if ui.verbose or not exact:
2317 if ui.verbose or not exact:
2308 ui.status(
2318 ui.status(
2309 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2319 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2310 )
2320 )
2311
2321
2312 for subpath in sorted(wctx.substate):
2322 for subpath in sorted(wctx.substate):
2313 sub = wctx.sub(subpath)
2323 sub = wctx.sub(subpath)
2314 try:
2324 try:
2315 submatch = matchmod.subdirmatcher(subpath, match)
2325 submatch = matchmod.subdirmatcher(subpath, match)
2316 subprefix = repo.wvfs.reljoin(prefix, subpath)
2326 subprefix = repo.wvfs.reljoin(prefix, subpath)
2317 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2327 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2318 if opts.get('subrepos'):
2328 if opts.get('subrepos'):
2319 bad.extend(
2329 bad.extend(
2320 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2330 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2321 )
2331 )
2322 else:
2332 else:
2323 bad.extend(
2333 bad.extend(
2324 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2334 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2325 )
2335 )
2326 except error.LookupError:
2336 except error.LookupError:
2327 ui.status(
2337 ui.status(
2328 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2338 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2329 )
2339 )
2330
2340
2331 if not opts.get('dry_run'):
2341 if not opts.get('dry_run'):
2332 rejected = wctx.add(names, prefix)
2342 rejected = wctx.add(names, prefix)
2333 bad.extend(f for f in rejected if f in match.files())
2343 bad.extend(f for f in rejected if f in match.files())
2334 return bad
2344 return bad
2335
2345
2336
2346
2337 def addwebdirpath(repo, serverpath, webconf):
2347 def addwebdirpath(repo, serverpath, webconf):
2338 webconf[serverpath] = repo.root
2348 webconf[serverpath] = repo.root
2339 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2349 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2340
2350
2341 for r in repo.revs(b'filelog("path:.hgsub")'):
2351 for r in repo.revs(b'filelog("path:.hgsub")'):
2342 ctx = repo[r]
2352 ctx = repo[r]
2343 for subpath in ctx.substate:
2353 for subpath in ctx.substate:
2344 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2354 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2345
2355
2346
2356
2347 def forget(
2357 def forget(
2348 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2358 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2349 ):
2359 ):
2350 if dryrun and interactive:
2360 if dryrun and interactive:
2351 raise error.InputError(
2361 raise error.InputError(
2352 _(b"cannot specify both --dry-run and --interactive")
2362 _(b"cannot specify both --dry-run and --interactive")
2353 )
2363 )
2354 bad = []
2364 bad = []
2355 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2365 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2356 wctx = repo[None]
2366 wctx = repo[None]
2357 forgot = []
2367 forgot = []
2358
2368
2359 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2369 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2360 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2370 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2361 if explicitonly:
2371 if explicitonly:
2362 forget = [f for f in forget if match.exact(f)]
2372 forget = [f for f in forget if match.exact(f)]
2363
2373
2364 for subpath in sorted(wctx.substate):
2374 for subpath in sorted(wctx.substate):
2365 sub = wctx.sub(subpath)
2375 sub = wctx.sub(subpath)
2366 submatch = matchmod.subdirmatcher(subpath, match)
2376 submatch = matchmod.subdirmatcher(subpath, match)
2367 subprefix = repo.wvfs.reljoin(prefix, subpath)
2377 subprefix = repo.wvfs.reljoin(prefix, subpath)
2368 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2378 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2369 try:
2379 try:
2370 subbad, subforgot = sub.forget(
2380 subbad, subforgot = sub.forget(
2371 submatch,
2381 submatch,
2372 subprefix,
2382 subprefix,
2373 subuipathfn,
2383 subuipathfn,
2374 dryrun=dryrun,
2384 dryrun=dryrun,
2375 interactive=interactive,
2385 interactive=interactive,
2376 )
2386 )
2377 bad.extend([subpath + b'/' + f for f in subbad])
2387 bad.extend([subpath + b'/' + f for f in subbad])
2378 forgot.extend([subpath + b'/' + f for f in subforgot])
2388 forgot.extend([subpath + b'/' + f for f in subforgot])
2379 except error.LookupError:
2389 except error.LookupError:
2380 ui.status(
2390 ui.status(
2381 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2391 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2382 )
2392 )
2383
2393
2384 if not explicitonly:
2394 if not explicitonly:
2385 for f in match.files():
2395 for f in match.files():
2386 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2396 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2387 if f not in forgot:
2397 if f not in forgot:
2388 if repo.wvfs.exists(f):
2398 if repo.wvfs.exists(f):
2389 # Don't complain if the exact case match wasn't given.
2399 # Don't complain if the exact case match wasn't given.
2390 # But don't do this until after checking 'forgot', so
2400 # But don't do this until after checking 'forgot', so
2391 # that subrepo files aren't normalized, and this op is
2401 # that subrepo files aren't normalized, and this op is
2392 # purely from data cached by the status walk above.
2402 # purely from data cached by the status walk above.
2393 if repo.dirstate.normalize(f) in repo.dirstate:
2403 if repo.dirstate.normalize(f) in repo.dirstate:
2394 continue
2404 continue
2395 ui.warn(
2405 ui.warn(
2396 _(
2406 _(
2397 b'not removing %s: '
2407 b'not removing %s: '
2398 b'file is already untracked\n'
2408 b'file is already untracked\n'
2399 )
2409 )
2400 % uipathfn(f)
2410 % uipathfn(f)
2401 )
2411 )
2402 bad.append(f)
2412 bad.append(f)
2403
2413
2404 if interactive:
2414 if interactive:
2405 responses = _(
2415 responses = _(
2406 b'[Ynsa?]'
2416 b'[Ynsa?]'
2407 b'$$ &Yes, forget this file'
2417 b'$$ &Yes, forget this file'
2408 b'$$ &No, skip this file'
2418 b'$$ &No, skip this file'
2409 b'$$ &Skip remaining files'
2419 b'$$ &Skip remaining files'
2410 b'$$ Include &all remaining files'
2420 b'$$ Include &all remaining files'
2411 b'$$ &? (display help)'
2421 b'$$ &? (display help)'
2412 )
2422 )
2413 for filename in forget[:]:
2423 for filename in forget[:]:
2414 r = ui.promptchoice(
2424 r = ui.promptchoice(
2415 _(b'forget %s %s') % (uipathfn(filename), responses)
2425 _(b'forget %s %s') % (uipathfn(filename), responses)
2416 )
2426 )
2417 if r == 4: # ?
2427 if r == 4: # ?
2418 while r == 4:
2428 while r == 4:
2419 for c, t in ui.extractchoices(responses)[1]:
2429 for c, t in ui.extractchoices(responses)[1]:
2420 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2430 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2421 r = ui.promptchoice(
2431 r = ui.promptchoice(
2422 _(b'forget %s %s') % (uipathfn(filename), responses)
2432 _(b'forget %s %s') % (uipathfn(filename), responses)
2423 )
2433 )
2424 if r == 0: # yes
2434 if r == 0: # yes
2425 continue
2435 continue
2426 elif r == 1: # no
2436 elif r == 1: # no
2427 forget.remove(filename)
2437 forget.remove(filename)
2428 elif r == 2: # Skip
2438 elif r == 2: # Skip
2429 fnindex = forget.index(filename)
2439 fnindex = forget.index(filename)
2430 del forget[fnindex:]
2440 del forget[fnindex:]
2431 break
2441 break
2432 elif r == 3: # All
2442 elif r == 3: # All
2433 break
2443 break
2434
2444
2435 for f in forget:
2445 for f in forget:
2436 if ui.verbose or not match.exact(f) or interactive:
2446 if ui.verbose or not match.exact(f) or interactive:
2437 ui.status(
2447 ui.status(
2438 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2448 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2439 )
2449 )
2440
2450
2441 if not dryrun:
2451 if not dryrun:
2442 rejected = wctx.forget(forget, prefix)
2452 rejected = wctx.forget(forget, prefix)
2443 bad.extend(f for f in rejected if f in match.files())
2453 bad.extend(f for f in rejected if f in match.files())
2444 forgot.extend(f for f in forget if f not in rejected)
2454 forgot.extend(f for f in forget if f not in rejected)
2445 return bad, forgot
2455 return bad, forgot
2446
2456
2447
2457
2448 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2458 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2449 ret = 1
2459 ret = 1
2450
2460
2451 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2461 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2452 if fm.isplain() and not needsfctx:
2462 if fm.isplain() and not needsfctx:
2453 # Fast path. The speed-up comes from skipping the formatter, and batching
2463 # Fast path. The speed-up comes from skipping the formatter, and batching
2454 # calls to ui.write.
2464 # calls to ui.write.
2455 buf = []
2465 buf = []
2456 for f in ctx.matches(m):
2466 for f in ctx.matches(m):
2457 buf.append(fmt % uipathfn(f))
2467 buf.append(fmt % uipathfn(f))
2458 if len(buf) > 100:
2468 if len(buf) > 100:
2459 ui.write(b''.join(buf))
2469 ui.write(b''.join(buf))
2460 del buf[:]
2470 del buf[:]
2461 ret = 0
2471 ret = 0
2462 if buf:
2472 if buf:
2463 ui.write(b''.join(buf))
2473 ui.write(b''.join(buf))
2464 else:
2474 else:
2465 for f in ctx.matches(m):
2475 for f in ctx.matches(m):
2466 fm.startitem()
2476 fm.startitem()
2467 fm.context(ctx=ctx)
2477 fm.context(ctx=ctx)
2468 if needsfctx:
2478 if needsfctx:
2469 fc = ctx[f]
2479 fc = ctx[f]
2470 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2480 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2471 fm.data(path=f)
2481 fm.data(path=f)
2472 fm.plain(fmt % uipathfn(f))
2482 fm.plain(fmt % uipathfn(f))
2473 ret = 0
2483 ret = 0
2474
2484
2475 for subpath in sorted(ctx.substate):
2485 for subpath in sorted(ctx.substate):
2476 submatch = matchmod.subdirmatcher(subpath, m)
2486 submatch = matchmod.subdirmatcher(subpath, m)
2477 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2487 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2478 if subrepos or m.exact(subpath) or any(submatch.files()):
2488 if subrepos or m.exact(subpath) or any(submatch.files()):
2479 sub = ctx.sub(subpath)
2489 sub = ctx.sub(subpath)
2480 try:
2490 try:
2481 recurse = m.exact(subpath) or subrepos
2491 recurse = m.exact(subpath) or subrepos
2482 if (
2492 if (
2483 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2493 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2484 == 0
2494 == 0
2485 ):
2495 ):
2486 ret = 0
2496 ret = 0
2487 except error.LookupError:
2497 except error.LookupError:
2488 ui.status(
2498 ui.status(
2489 _(b"skipping missing subrepository: %s\n")
2499 _(b"skipping missing subrepository: %s\n")
2490 % uipathfn(subpath)
2500 % uipathfn(subpath)
2491 )
2501 )
2492
2502
2493 return ret
2503 return ret
2494
2504
2495
2505
2496 def remove(
2506 def remove(
2497 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2507 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2498 ):
2508 ):
2499 ret = 0
2509 ret = 0
2500 s = repo.status(match=m, clean=True)
2510 s = repo.status(match=m, clean=True)
2501 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2511 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2502
2512
2503 wctx = repo[None]
2513 wctx = repo[None]
2504
2514
2505 if warnings is None:
2515 if warnings is None:
2506 warnings = []
2516 warnings = []
2507 warn = True
2517 warn = True
2508 else:
2518 else:
2509 warn = False
2519 warn = False
2510
2520
2511 subs = sorted(wctx.substate)
2521 subs = sorted(wctx.substate)
2512 progress = ui.makeprogress(
2522 progress = ui.makeprogress(
2513 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2523 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2514 )
2524 )
2515 for subpath in subs:
2525 for subpath in subs:
2516 submatch = matchmod.subdirmatcher(subpath, m)
2526 submatch = matchmod.subdirmatcher(subpath, m)
2517 subprefix = repo.wvfs.reljoin(prefix, subpath)
2527 subprefix = repo.wvfs.reljoin(prefix, subpath)
2518 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2528 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2519 if subrepos or m.exact(subpath) or any(submatch.files()):
2529 if subrepos or m.exact(subpath) or any(submatch.files()):
2520 progress.increment()
2530 progress.increment()
2521 sub = wctx.sub(subpath)
2531 sub = wctx.sub(subpath)
2522 try:
2532 try:
2523 if sub.removefiles(
2533 if sub.removefiles(
2524 submatch,
2534 submatch,
2525 subprefix,
2535 subprefix,
2526 subuipathfn,
2536 subuipathfn,
2527 after,
2537 after,
2528 force,
2538 force,
2529 subrepos,
2539 subrepos,
2530 dryrun,
2540 dryrun,
2531 warnings,
2541 warnings,
2532 ):
2542 ):
2533 ret = 1
2543 ret = 1
2534 except error.LookupError:
2544 except error.LookupError:
2535 warnings.append(
2545 warnings.append(
2536 _(b"skipping missing subrepository: %s\n")
2546 _(b"skipping missing subrepository: %s\n")
2537 % uipathfn(subpath)
2547 % uipathfn(subpath)
2538 )
2548 )
2539 progress.complete()
2549 progress.complete()
2540
2550
2541 # warn about failure to delete explicit files/dirs
2551 # warn about failure to delete explicit files/dirs
2542 deleteddirs = pathutil.dirs(deleted)
2552 deleteddirs = pathutil.dirs(deleted)
2543 files = m.files()
2553 files = m.files()
2544 progress = ui.makeprogress(
2554 progress = ui.makeprogress(
2545 _(b'deleting'), total=len(files), unit=_(b'files')
2555 _(b'deleting'), total=len(files), unit=_(b'files')
2546 )
2556 )
2547 for f in files:
2557 for f in files:
2548
2558
2549 def insubrepo():
2559 def insubrepo():
2550 for subpath in wctx.substate:
2560 for subpath in wctx.substate:
2551 if f.startswith(subpath + b'/'):
2561 if f.startswith(subpath + b'/'):
2552 return True
2562 return True
2553 return False
2563 return False
2554
2564
2555 progress.increment()
2565 progress.increment()
2556 isdir = f in deleteddirs or wctx.hasdir(f)
2566 isdir = f in deleteddirs or wctx.hasdir(f)
2557 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2567 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2558 continue
2568 continue
2559
2569
2560 if repo.wvfs.exists(f):
2570 if repo.wvfs.exists(f):
2561 if repo.wvfs.isdir(f):
2571 if repo.wvfs.isdir(f):
2562 warnings.append(
2572 warnings.append(
2563 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2573 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2564 )
2574 )
2565 else:
2575 else:
2566 warnings.append(
2576 warnings.append(
2567 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2577 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2568 )
2578 )
2569 # missing files will generate a warning elsewhere
2579 # missing files will generate a warning elsewhere
2570 ret = 1
2580 ret = 1
2571 progress.complete()
2581 progress.complete()
2572
2582
2573 if force:
2583 if force:
2574 list = modified + deleted + clean + added
2584 list = modified + deleted + clean + added
2575 elif after:
2585 elif after:
2576 list = deleted
2586 list = deleted
2577 remaining = modified + added + clean
2587 remaining = modified + added + clean
2578 progress = ui.makeprogress(
2588 progress = ui.makeprogress(
2579 _(b'skipping'), total=len(remaining), unit=_(b'files')
2589 _(b'skipping'), total=len(remaining), unit=_(b'files')
2580 )
2590 )
2581 for f in remaining:
2591 for f in remaining:
2582 progress.increment()
2592 progress.increment()
2583 if ui.verbose or (f in files):
2593 if ui.verbose or (f in files):
2584 warnings.append(
2594 warnings.append(
2585 _(b'not removing %s: file still exists\n') % uipathfn(f)
2595 _(b'not removing %s: file still exists\n') % uipathfn(f)
2586 )
2596 )
2587 ret = 1
2597 ret = 1
2588 progress.complete()
2598 progress.complete()
2589 else:
2599 else:
2590 list = deleted + clean
2600 list = deleted + clean
2591 progress = ui.makeprogress(
2601 progress = ui.makeprogress(
2592 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2602 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2593 )
2603 )
2594 for f in modified:
2604 for f in modified:
2595 progress.increment()
2605 progress.increment()
2596 warnings.append(
2606 warnings.append(
2597 _(
2607 _(
2598 b'not removing %s: file is modified (use -f'
2608 b'not removing %s: file is modified (use -f'
2599 b' to force removal)\n'
2609 b' to force removal)\n'
2600 )
2610 )
2601 % uipathfn(f)
2611 % uipathfn(f)
2602 )
2612 )
2603 ret = 1
2613 ret = 1
2604 for f in added:
2614 for f in added:
2605 progress.increment()
2615 progress.increment()
2606 warnings.append(
2616 warnings.append(
2607 _(
2617 _(
2608 b"not removing %s: file has been marked for add"
2618 b"not removing %s: file has been marked for add"
2609 b" (use 'hg forget' to undo add)\n"
2619 b" (use 'hg forget' to undo add)\n"
2610 )
2620 )
2611 % uipathfn(f)
2621 % uipathfn(f)
2612 )
2622 )
2613 ret = 1
2623 ret = 1
2614 progress.complete()
2624 progress.complete()
2615
2625
2616 list = sorted(list)
2626 list = sorted(list)
2617 progress = ui.makeprogress(
2627 progress = ui.makeprogress(
2618 _(b'deleting'), total=len(list), unit=_(b'files')
2628 _(b'deleting'), total=len(list), unit=_(b'files')
2619 )
2629 )
2620 for f in list:
2630 for f in list:
2621 if ui.verbose or not m.exact(f):
2631 if ui.verbose or not m.exact(f):
2622 progress.increment()
2632 progress.increment()
2623 ui.status(
2633 ui.status(
2624 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2634 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2625 )
2635 )
2626 progress.complete()
2636 progress.complete()
2627
2637
2628 if not dryrun:
2638 if not dryrun:
2629 with repo.wlock():
2639 with repo.wlock():
2630 if not after:
2640 if not after:
2631 for f in list:
2641 for f in list:
2632 if f in added:
2642 if f in added:
2633 continue # we never unlink added files on remove
2643 continue # we never unlink added files on remove
2634 rmdir = repo.ui.configbool(
2644 rmdir = repo.ui.configbool(
2635 b'experimental', b'removeemptydirs'
2645 b'experimental', b'removeemptydirs'
2636 )
2646 )
2637 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2647 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2638 repo[None].forget(list)
2648 repo[None].forget(list)
2639
2649
2640 if warn:
2650 if warn:
2641 for warning in warnings:
2651 for warning in warnings:
2642 ui.warn(warning)
2652 ui.warn(warning)
2643
2653
2644 return ret
2654 return ret
2645
2655
2646
2656
2647 def _catfmtneedsdata(fm):
2657 def _catfmtneedsdata(fm):
2648 return not fm.datahint() or b'data' in fm.datahint()
2658 return not fm.datahint() or b'data' in fm.datahint()
2649
2659
2650
2660
2651 def _updatecatformatter(fm, ctx, matcher, path, decode):
2661 def _updatecatformatter(fm, ctx, matcher, path, decode):
2652 """Hook for adding data to the formatter used by ``hg cat``.
2662 """Hook for adding data to the formatter used by ``hg cat``.
2653
2663
2654 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2664 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2655 this method first."""
2665 this method first."""
2656
2666
2657 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2667 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2658 # wasn't requested.
2668 # wasn't requested.
2659 data = b''
2669 data = b''
2660 if _catfmtneedsdata(fm):
2670 if _catfmtneedsdata(fm):
2661 data = ctx[path].data()
2671 data = ctx[path].data()
2662 if decode:
2672 if decode:
2663 data = ctx.repo().wwritedata(path, data)
2673 data = ctx.repo().wwritedata(path, data)
2664 fm.startitem()
2674 fm.startitem()
2665 fm.context(ctx=ctx)
2675 fm.context(ctx=ctx)
2666 fm.write(b'data', b'%s', data)
2676 fm.write(b'data', b'%s', data)
2667 fm.data(path=path)
2677 fm.data(path=path)
2668
2678
2669
2679
2670 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2680 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2671 err = 1
2681 err = 1
2672 opts = pycompat.byteskwargs(opts)
2682 opts = pycompat.byteskwargs(opts)
2673
2683
2674 def write(path):
2684 def write(path):
2675 filename = None
2685 filename = None
2676 if fntemplate:
2686 if fntemplate:
2677 filename = makefilename(
2687 filename = makefilename(
2678 ctx, fntemplate, pathname=os.path.join(prefix, path)
2688 ctx, fntemplate, pathname=os.path.join(prefix, path)
2679 )
2689 )
2680 # attempt to create the directory if it does not already exist
2690 # attempt to create the directory if it does not already exist
2681 try:
2691 try:
2682 os.makedirs(os.path.dirname(filename))
2692 os.makedirs(os.path.dirname(filename))
2683 except OSError:
2693 except OSError:
2684 pass
2694 pass
2685 with formatter.maybereopen(basefm, filename) as fm:
2695 with formatter.maybereopen(basefm, filename) as fm:
2686 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2696 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2687
2697
2688 # Automation often uses hg cat on single files, so special case it
2698 # Automation often uses hg cat on single files, so special case it
2689 # for performance to avoid the cost of parsing the manifest.
2699 # for performance to avoid the cost of parsing the manifest.
2690 if len(matcher.files()) == 1 and not matcher.anypats():
2700 if len(matcher.files()) == 1 and not matcher.anypats():
2691 file = matcher.files()[0]
2701 file = matcher.files()[0]
2692 mfl = repo.manifestlog
2702 mfl = repo.manifestlog
2693 mfnode = ctx.manifestnode()
2703 mfnode = ctx.manifestnode()
2694 try:
2704 try:
2695 if mfnode and mfl[mfnode].find(file)[0]:
2705 if mfnode and mfl[mfnode].find(file)[0]:
2696 if _catfmtneedsdata(basefm):
2706 if _catfmtneedsdata(basefm):
2697 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2707 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2698 write(file)
2708 write(file)
2699 return 0
2709 return 0
2700 except KeyError:
2710 except KeyError:
2701 pass
2711 pass
2702
2712
2703 if _catfmtneedsdata(basefm):
2713 if _catfmtneedsdata(basefm):
2704 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2714 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2705
2715
2706 for abs in ctx.walk(matcher):
2716 for abs in ctx.walk(matcher):
2707 write(abs)
2717 write(abs)
2708 err = 0
2718 err = 0
2709
2719
2710 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2720 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2711 for subpath in sorted(ctx.substate):
2721 for subpath in sorted(ctx.substate):
2712 sub = ctx.sub(subpath)
2722 sub = ctx.sub(subpath)
2713 try:
2723 try:
2714 submatch = matchmod.subdirmatcher(subpath, matcher)
2724 submatch = matchmod.subdirmatcher(subpath, matcher)
2715 subprefix = os.path.join(prefix, subpath)
2725 subprefix = os.path.join(prefix, subpath)
2716 if not sub.cat(
2726 if not sub.cat(
2717 submatch,
2727 submatch,
2718 basefm,
2728 basefm,
2719 fntemplate,
2729 fntemplate,
2720 subprefix,
2730 subprefix,
2721 **pycompat.strkwargs(opts)
2731 **pycompat.strkwargs(opts)
2722 ):
2732 ):
2723 err = 0
2733 err = 0
2724 except error.RepoLookupError:
2734 except error.RepoLookupError:
2725 ui.status(
2735 ui.status(
2726 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2736 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2727 )
2737 )
2728
2738
2729 return err
2739 return err
2730
2740
2731
2741
2732 def commit(ui, repo, commitfunc, pats, opts):
2742 def commit(ui, repo, commitfunc, pats, opts):
2733 '''commit the specified files or all outstanding changes'''
2743 '''commit the specified files or all outstanding changes'''
2734 date = opts.get(b'date')
2744 date = opts.get(b'date')
2735 if date:
2745 if date:
2736 opts[b'date'] = dateutil.parsedate(date)
2746 opts[b'date'] = dateutil.parsedate(date)
2737 message = logmessage(ui, opts)
2747 message = logmessage(ui, opts)
2738 matcher = scmutil.match(repo[None], pats, opts)
2748 matcher = scmutil.match(repo[None], pats, opts)
2739
2749
2740 dsguard = None
2750 dsguard = None
2741 # extract addremove carefully -- this function can be called from a command
2751 # extract addremove carefully -- this function can be called from a command
2742 # that doesn't support addremove
2752 # that doesn't support addremove
2743 if opts.get(b'addremove'):
2753 if opts.get(b'addremove'):
2744 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2754 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2745 with dsguard or util.nullcontextmanager():
2755 with dsguard or util.nullcontextmanager():
2746 if dsguard:
2756 if dsguard:
2747 relative = scmutil.anypats(pats, opts)
2757 relative = scmutil.anypats(pats, opts)
2748 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2758 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2749 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2759 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2750 raise error.Abort(
2760 raise error.Abort(
2751 _(b"failed to mark all new/missing files as added/removed")
2761 _(b"failed to mark all new/missing files as added/removed")
2752 )
2762 )
2753
2763
2754 return commitfunc(ui, repo, message, matcher, opts)
2764 return commitfunc(ui, repo, message, matcher, opts)
2755
2765
2756
2766
2757 def samefile(f, ctx1, ctx2):
2767 def samefile(f, ctx1, ctx2):
2758 if f in ctx1.manifest():
2768 if f in ctx1.manifest():
2759 a = ctx1.filectx(f)
2769 a = ctx1.filectx(f)
2760 if f in ctx2.manifest():
2770 if f in ctx2.manifest():
2761 b = ctx2.filectx(f)
2771 b = ctx2.filectx(f)
2762 return not a.cmp(b) and a.flags() == b.flags()
2772 return not a.cmp(b) and a.flags() == b.flags()
2763 else:
2773 else:
2764 return False
2774 return False
2765 else:
2775 else:
2766 return f not in ctx2.manifest()
2776 return f not in ctx2.manifest()
2767
2777
2768
2778
2769 def amend(ui, repo, old, extra, pats, opts):
2779 def amend(ui, repo, old, extra, pats, opts):
2770 # avoid cycle context -> subrepo -> cmdutil
2780 # avoid cycle context -> subrepo -> cmdutil
2771 from . import context
2781 from . import context
2772
2782
2773 # amend will reuse the existing user if not specified, but the obsolete
2783 # amend will reuse the existing user if not specified, but the obsolete
2774 # marker creation requires that the current user's name is specified.
2784 # marker creation requires that the current user's name is specified.
2775 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2785 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2776 ui.username() # raise exception if username not set
2786 ui.username() # raise exception if username not set
2777
2787
2778 ui.note(_(b'amending changeset %s\n') % old)
2788 ui.note(_(b'amending changeset %s\n') % old)
2779 base = old.p1()
2789 base = old.p1()
2780
2790
2781 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2791 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2782 # Participating changesets:
2792 # Participating changesets:
2783 #
2793 #
2784 # wctx o - workingctx that contains changes from working copy
2794 # wctx o - workingctx that contains changes from working copy
2785 # | to go into amending commit
2795 # | to go into amending commit
2786 # |
2796 # |
2787 # old o - changeset to amend
2797 # old o - changeset to amend
2788 # |
2798 # |
2789 # base o - first parent of the changeset to amend
2799 # base o - first parent of the changeset to amend
2790 wctx = repo[None]
2800 wctx = repo[None]
2791
2801
2792 # Copy to avoid mutating input
2802 # Copy to avoid mutating input
2793 extra = extra.copy()
2803 extra = extra.copy()
2794 # Update extra dict from amended commit (e.g. to preserve graft
2804 # Update extra dict from amended commit (e.g. to preserve graft
2795 # source)
2805 # source)
2796 extra.update(old.extra())
2806 extra.update(old.extra())
2797
2807
2798 # Also update it from the from the wctx
2808 # Also update it from the from the wctx
2799 extra.update(wctx.extra())
2809 extra.update(wctx.extra())
2800
2810
2801 # date-only change should be ignored?
2811 # date-only change should be ignored?
2802 datemaydiffer = resolvecommitoptions(ui, opts)
2812 datemaydiffer = resolvecommitoptions(ui, opts)
2803
2813
2804 date = old.date()
2814 date = old.date()
2805 if opts.get(b'date'):
2815 if opts.get(b'date'):
2806 date = dateutil.parsedate(opts.get(b'date'))
2816 date = dateutil.parsedate(opts.get(b'date'))
2807 user = opts.get(b'user') or old.user()
2817 user = opts.get(b'user') or old.user()
2808
2818
2809 if len(old.parents()) > 1:
2819 if len(old.parents()) > 1:
2810 # ctx.files() isn't reliable for merges, so fall back to the
2820 # ctx.files() isn't reliable for merges, so fall back to the
2811 # slower repo.status() method
2821 # slower repo.status() method
2812 st = base.status(old)
2822 st = base.status(old)
2813 files = set(st.modified) | set(st.added) | set(st.removed)
2823 files = set(st.modified) | set(st.added) | set(st.removed)
2814 else:
2824 else:
2815 files = set(old.files())
2825 files = set(old.files())
2816
2826
2817 # add/remove the files to the working copy if the "addremove" option
2827 # add/remove the files to the working copy if the "addremove" option
2818 # was specified.
2828 # was specified.
2819 matcher = scmutil.match(wctx, pats, opts)
2829 matcher = scmutil.match(wctx, pats, opts)
2820 relative = scmutil.anypats(pats, opts)
2830 relative = scmutil.anypats(pats, opts)
2821 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2831 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2822 if opts.get(b'addremove') and scmutil.addremove(
2832 if opts.get(b'addremove') and scmutil.addremove(
2823 repo, matcher, b"", uipathfn, opts
2833 repo, matcher, b"", uipathfn, opts
2824 ):
2834 ):
2825 raise error.Abort(
2835 raise error.Abort(
2826 _(b"failed to mark all new/missing files as added/removed")
2836 _(b"failed to mark all new/missing files as added/removed")
2827 )
2837 )
2828
2838
2829 # Check subrepos. This depends on in-place wctx._status update in
2839 # Check subrepos. This depends on in-place wctx._status update in
2830 # subrepo.precommit(). To minimize the risk of this hack, we do
2840 # subrepo.precommit(). To minimize the risk of this hack, we do
2831 # nothing if .hgsub does not exist.
2841 # nothing if .hgsub does not exist.
2832 if b'.hgsub' in wctx or b'.hgsub' in old:
2842 if b'.hgsub' in wctx or b'.hgsub' in old:
2833 subs, commitsubs, newsubstate = subrepoutil.precommit(
2843 subs, commitsubs, newsubstate = subrepoutil.precommit(
2834 ui, wctx, wctx._status, matcher
2844 ui, wctx, wctx._status, matcher
2835 )
2845 )
2836 # amend should abort if commitsubrepos is enabled
2846 # amend should abort if commitsubrepos is enabled
2837 assert not commitsubs
2847 assert not commitsubs
2838 if subs:
2848 if subs:
2839 subrepoutil.writestate(repo, newsubstate)
2849 subrepoutil.writestate(repo, newsubstate)
2840
2850
2841 ms = mergestatemod.mergestate.read(repo)
2851 ms = mergestatemod.mergestate.read(repo)
2842 mergeutil.checkunresolved(ms)
2852 mergeutil.checkunresolved(ms)
2843
2853
2844 filestoamend = {f for f in wctx.files() if matcher(f)}
2854 filestoamend = {f for f in wctx.files() if matcher(f)}
2845
2855
2846 changes = len(filestoamend) > 0
2856 changes = len(filestoamend) > 0
2847 if changes:
2857 if changes:
2848 # Recompute copies (avoid recording a -> b -> a)
2858 # Recompute copies (avoid recording a -> b -> a)
2849 copied = copies.pathcopies(base, wctx, matcher)
2859 copied = copies.pathcopies(base, wctx, matcher)
2850 if old.p2:
2860 if old.p2:
2851 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2861 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2852
2862
2853 # Prune files which were reverted by the updates: if old
2863 # Prune files which were reverted by the updates: if old
2854 # introduced file X and the file was renamed in the working
2864 # introduced file X and the file was renamed in the working
2855 # copy, then those two files are the same and
2865 # copy, then those two files are the same and
2856 # we can discard X from our list of files. Likewise if X
2866 # we can discard X from our list of files. Likewise if X
2857 # was removed, it's no longer relevant. If X is missing (aka
2867 # was removed, it's no longer relevant. If X is missing (aka
2858 # deleted), old X must be preserved.
2868 # deleted), old X must be preserved.
2859 files.update(filestoamend)
2869 files.update(filestoamend)
2860 files = [
2870 files = [
2861 f
2871 f
2862 for f in files
2872 for f in files
2863 if (f not in filestoamend or not samefile(f, wctx, base))
2873 if (f not in filestoamend or not samefile(f, wctx, base))
2864 ]
2874 ]
2865
2875
2866 def filectxfn(repo, ctx_, path):
2876 def filectxfn(repo, ctx_, path):
2867 try:
2877 try:
2868 # If the file being considered is not amongst the files
2878 # If the file being considered is not amongst the files
2869 # to be amended, we should return the file context from the
2879 # to be amended, we should return the file context from the
2870 # old changeset. This avoids issues when only some files in
2880 # old changeset. This avoids issues when only some files in
2871 # the working copy are being amended but there are also
2881 # the working copy are being amended but there are also
2872 # changes to other files from the old changeset.
2882 # changes to other files from the old changeset.
2873 if path not in filestoamend:
2883 if path not in filestoamend:
2874 return old.filectx(path)
2884 return old.filectx(path)
2875
2885
2876 # Return None for removed files.
2886 # Return None for removed files.
2877 if path in wctx.removed():
2887 if path in wctx.removed():
2878 return None
2888 return None
2879
2889
2880 fctx = wctx[path]
2890 fctx = wctx[path]
2881 flags = fctx.flags()
2891 flags = fctx.flags()
2882 mctx = context.memfilectx(
2892 mctx = context.memfilectx(
2883 repo,
2893 repo,
2884 ctx_,
2894 ctx_,
2885 fctx.path(),
2895 fctx.path(),
2886 fctx.data(),
2896 fctx.data(),
2887 islink=b'l' in flags,
2897 islink=b'l' in flags,
2888 isexec=b'x' in flags,
2898 isexec=b'x' in flags,
2889 copysource=copied.get(path),
2899 copysource=copied.get(path),
2890 )
2900 )
2891 return mctx
2901 return mctx
2892 except KeyError:
2902 except KeyError:
2893 return None
2903 return None
2894
2904
2895 else:
2905 else:
2896 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2906 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2897
2907
2898 # Use version of files as in the old cset
2908 # Use version of files as in the old cset
2899 def filectxfn(repo, ctx_, path):
2909 def filectxfn(repo, ctx_, path):
2900 try:
2910 try:
2901 return old.filectx(path)
2911 return old.filectx(path)
2902 except KeyError:
2912 except KeyError:
2903 return None
2913 return None
2904
2914
2905 # See if we got a message from -m or -l, if not, open the editor with
2915 # See if we got a message from -m or -l, if not, open the editor with
2906 # the message of the changeset to amend.
2916 # the message of the changeset to amend.
2907 message = logmessage(ui, opts)
2917 message = logmessage(ui, opts)
2908
2918
2909 editform = mergeeditform(old, b'commit.amend')
2919 editform = mergeeditform(old, b'commit.amend')
2910
2920
2911 if not message:
2921 if not message:
2912 message = old.description()
2922 message = old.description()
2913 # Default if message isn't provided and --edit is not passed is to
2923 # Default if message isn't provided and --edit is not passed is to
2914 # invoke editor, but allow --no-edit. If somehow we don't have any
2924 # invoke editor, but allow --no-edit. If somehow we don't have any
2915 # description, let's always start the editor.
2925 # description, let's always start the editor.
2916 doedit = not message or opts.get(b'edit') in [True, None]
2926 doedit = not message or opts.get(b'edit') in [True, None]
2917 else:
2927 else:
2918 # Default if message is provided is to not invoke editor, but allow
2928 # Default if message is provided is to not invoke editor, but allow
2919 # --edit.
2929 # --edit.
2920 doedit = opts.get(b'edit') is True
2930 doedit = opts.get(b'edit') is True
2921 editor = getcommiteditor(edit=doedit, editform=editform)
2931 editor = getcommiteditor(edit=doedit, editform=editform)
2922
2932
2923 pureextra = extra.copy()
2933 pureextra = extra.copy()
2924 extra[b'amend_source'] = old.hex()
2934 extra[b'amend_source'] = old.hex()
2925
2935
2926 new = context.memctx(
2936 new = context.memctx(
2927 repo,
2937 repo,
2928 parents=[base.node(), old.p2().node()],
2938 parents=[base.node(), old.p2().node()],
2929 text=message,
2939 text=message,
2930 files=files,
2940 files=files,
2931 filectxfn=filectxfn,
2941 filectxfn=filectxfn,
2932 user=user,
2942 user=user,
2933 date=date,
2943 date=date,
2934 extra=extra,
2944 extra=extra,
2935 editor=editor,
2945 editor=editor,
2936 )
2946 )
2937
2947
2938 newdesc = changelog.stripdesc(new.description())
2948 newdesc = changelog.stripdesc(new.description())
2939 if (
2949 if (
2940 (not changes)
2950 (not changes)
2941 and newdesc == old.description()
2951 and newdesc == old.description()
2942 and user == old.user()
2952 and user == old.user()
2943 and (date == old.date() or datemaydiffer)
2953 and (date == old.date() or datemaydiffer)
2944 and pureextra == old.extra()
2954 and pureextra == old.extra()
2945 ):
2955 ):
2946 # nothing changed. continuing here would create a new node
2956 # nothing changed. continuing here would create a new node
2947 # anyway because of the amend_source noise.
2957 # anyway because of the amend_source noise.
2948 #
2958 #
2949 # This not what we expect from amend.
2959 # This not what we expect from amend.
2950 return old.node()
2960 return old.node()
2951
2961
2952 commitphase = None
2962 commitphase = None
2953 if opts.get(b'secret'):
2963 if opts.get(b'secret'):
2954 commitphase = phases.secret
2964 commitphase = phases.secret
2955 newid = repo.commitctx(new)
2965 newid = repo.commitctx(new)
2956 ms.reset()
2966 ms.reset()
2957
2967
2958 # Reroute the working copy parent to the new changeset
2968 # Reroute the working copy parent to the new changeset
2959 repo.setparents(newid, nullid)
2969 repo.setparents(newid, nullid)
2960 mapping = {old.node(): (newid,)}
2970 mapping = {old.node(): (newid,)}
2961 obsmetadata = None
2971 obsmetadata = None
2962 if opts.get(b'note'):
2972 if opts.get(b'note'):
2963 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
2973 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
2964 backup = ui.configbool(b'rewrite', b'backup-bundle')
2974 backup = ui.configbool(b'rewrite', b'backup-bundle')
2965 scmutil.cleanupnodes(
2975 scmutil.cleanupnodes(
2966 repo,
2976 repo,
2967 mapping,
2977 mapping,
2968 b'amend',
2978 b'amend',
2969 metadata=obsmetadata,
2979 metadata=obsmetadata,
2970 fixphase=True,
2980 fixphase=True,
2971 targetphase=commitphase,
2981 targetphase=commitphase,
2972 backup=backup,
2982 backup=backup,
2973 )
2983 )
2974
2984
2975 # Fixing the dirstate because localrepo.commitctx does not update
2985 # Fixing the dirstate because localrepo.commitctx does not update
2976 # it. This is rather convenient because we did not need to update
2986 # it. This is rather convenient because we did not need to update
2977 # the dirstate for all the files in the new commit which commitctx
2987 # the dirstate for all the files in the new commit which commitctx
2978 # could have done if it updated the dirstate. Now, we can
2988 # could have done if it updated the dirstate. Now, we can
2979 # selectively update the dirstate only for the amended files.
2989 # selectively update the dirstate only for the amended files.
2980 dirstate = repo.dirstate
2990 dirstate = repo.dirstate
2981
2991
2982 # Update the state of the files which were added and modified in the
2992 # Update the state of the files which were added and modified in the
2983 # amend to "normal" in the dirstate. We need to use "normallookup" since
2993 # amend to "normal" in the dirstate. We need to use "normallookup" since
2984 # the files may have changed since the command started; using "normal"
2994 # the files may have changed since the command started; using "normal"
2985 # would mark them as clean but with uncommitted contents.
2995 # would mark them as clean but with uncommitted contents.
2986 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2996 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2987 for f in normalfiles:
2997 for f in normalfiles:
2988 dirstate.normallookup(f)
2998 dirstate.normallookup(f)
2989
2999
2990 # Update the state of files which were removed in the amend
3000 # Update the state of files which were removed in the amend
2991 # to "removed" in the dirstate.
3001 # to "removed" in the dirstate.
2992 removedfiles = set(wctx.removed()) & filestoamend
3002 removedfiles = set(wctx.removed()) & filestoamend
2993 for f in removedfiles:
3003 for f in removedfiles:
2994 dirstate.drop(f)
3004 dirstate.drop(f)
2995
3005
2996 return newid
3006 return newid
2997
3007
2998
3008
2999 def commiteditor(repo, ctx, subs, editform=b''):
3009 def commiteditor(repo, ctx, subs, editform=b''):
3000 if ctx.description():
3010 if ctx.description():
3001 return ctx.description()
3011 return ctx.description()
3002 return commitforceeditor(
3012 return commitforceeditor(
3003 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3013 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3004 )
3014 )
3005
3015
3006
3016
3007 def commitforceeditor(
3017 def commitforceeditor(
3008 repo,
3018 repo,
3009 ctx,
3019 ctx,
3010 subs,
3020 subs,
3011 finishdesc=None,
3021 finishdesc=None,
3012 extramsg=None,
3022 extramsg=None,
3013 editform=b'',
3023 editform=b'',
3014 unchangedmessagedetection=False,
3024 unchangedmessagedetection=False,
3015 ):
3025 ):
3016 if not extramsg:
3026 if not extramsg:
3017 extramsg = _(b"Leave message empty to abort commit.")
3027 extramsg = _(b"Leave message empty to abort commit.")
3018
3028
3019 forms = [e for e in editform.split(b'.') if e]
3029 forms = [e for e in editform.split(b'.') if e]
3020 forms.insert(0, b'changeset')
3030 forms.insert(0, b'changeset')
3021 templatetext = None
3031 templatetext = None
3022 while forms:
3032 while forms:
3023 ref = b'.'.join(forms)
3033 ref = b'.'.join(forms)
3024 if repo.ui.config(b'committemplate', ref):
3034 if repo.ui.config(b'committemplate', ref):
3025 templatetext = committext = buildcommittemplate(
3035 templatetext = committext = buildcommittemplate(
3026 repo, ctx, subs, extramsg, ref
3036 repo, ctx, subs, extramsg, ref
3027 )
3037 )
3028 break
3038 break
3029 forms.pop()
3039 forms.pop()
3030 else:
3040 else:
3031 committext = buildcommittext(repo, ctx, subs, extramsg)
3041 committext = buildcommittext(repo, ctx, subs, extramsg)
3032
3042
3033 # run editor in the repository root
3043 # run editor in the repository root
3034 olddir = encoding.getcwd()
3044 olddir = encoding.getcwd()
3035 os.chdir(repo.root)
3045 os.chdir(repo.root)
3036
3046
3037 # make in-memory changes visible to external process
3047 # make in-memory changes visible to external process
3038 tr = repo.currenttransaction()
3048 tr = repo.currenttransaction()
3039 repo.dirstate.write(tr)
3049 repo.dirstate.write(tr)
3040 pending = tr and tr.writepending() and repo.root
3050 pending = tr and tr.writepending() and repo.root
3041
3051
3042 editortext = repo.ui.edit(
3052 editortext = repo.ui.edit(
3043 committext,
3053 committext,
3044 ctx.user(),
3054 ctx.user(),
3045 ctx.extra(),
3055 ctx.extra(),
3046 editform=editform,
3056 editform=editform,
3047 pending=pending,
3057 pending=pending,
3048 repopath=repo.path,
3058 repopath=repo.path,
3049 action=b'commit',
3059 action=b'commit',
3050 )
3060 )
3051 text = editortext
3061 text = editortext
3052
3062
3053 # strip away anything below this special string (used for editors that want
3063 # strip away anything below this special string (used for editors that want
3054 # to display the diff)
3064 # to display the diff)
3055 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3065 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3056 if stripbelow:
3066 if stripbelow:
3057 text = text[: stripbelow.start()]
3067 text = text[: stripbelow.start()]
3058
3068
3059 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3069 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3060 os.chdir(olddir)
3070 os.chdir(olddir)
3061
3071
3062 if finishdesc:
3072 if finishdesc:
3063 text = finishdesc(text)
3073 text = finishdesc(text)
3064 if not text.strip():
3074 if not text.strip():
3065 raise error.InputError(_(b"empty commit message"))
3075 raise error.InputError(_(b"empty commit message"))
3066 if unchangedmessagedetection and editortext == templatetext:
3076 if unchangedmessagedetection and editortext == templatetext:
3067 raise error.InputError(_(b"commit message unchanged"))
3077 raise error.InputError(_(b"commit message unchanged"))
3068
3078
3069 return text
3079 return text
3070
3080
3071
3081
3072 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3082 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3073 ui = repo.ui
3083 ui = repo.ui
3074 spec = formatter.reference_templatespec(ref)
3084 spec = formatter.reference_templatespec(ref)
3075 t = logcmdutil.changesettemplater(ui, repo, spec)
3085 t = logcmdutil.changesettemplater(ui, repo, spec)
3076 t.t.cache.update(
3086 t.t.cache.update(
3077 (k, templater.unquotestring(v))
3087 (k, templater.unquotestring(v))
3078 for k, v in repo.ui.configitems(b'committemplate')
3088 for k, v in repo.ui.configitems(b'committemplate')
3079 )
3089 )
3080
3090
3081 if not extramsg:
3091 if not extramsg:
3082 extramsg = b'' # ensure that extramsg is string
3092 extramsg = b'' # ensure that extramsg is string
3083
3093
3084 ui.pushbuffer()
3094 ui.pushbuffer()
3085 t.show(ctx, extramsg=extramsg)
3095 t.show(ctx, extramsg=extramsg)
3086 return ui.popbuffer()
3096 return ui.popbuffer()
3087
3097
3088
3098
3089 def hgprefix(msg):
3099 def hgprefix(msg):
3090 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3100 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3091
3101
3092
3102
3093 def buildcommittext(repo, ctx, subs, extramsg):
3103 def buildcommittext(repo, ctx, subs, extramsg):
3094 edittext = []
3104 edittext = []
3095 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3105 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3096 if ctx.description():
3106 if ctx.description():
3097 edittext.append(ctx.description())
3107 edittext.append(ctx.description())
3098 edittext.append(b"")
3108 edittext.append(b"")
3099 edittext.append(b"") # Empty line between message and comments.
3109 edittext.append(b"") # Empty line between message and comments.
3100 edittext.append(
3110 edittext.append(
3101 hgprefix(
3111 hgprefix(
3102 _(
3112 _(
3103 b"Enter commit message."
3113 b"Enter commit message."
3104 b" Lines beginning with 'HG:' are removed."
3114 b" Lines beginning with 'HG:' are removed."
3105 )
3115 )
3106 )
3116 )
3107 )
3117 )
3108 edittext.append(hgprefix(extramsg))
3118 edittext.append(hgprefix(extramsg))
3109 edittext.append(b"HG: --")
3119 edittext.append(b"HG: --")
3110 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3120 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3111 if ctx.p2():
3121 if ctx.p2():
3112 edittext.append(hgprefix(_(b"branch merge")))
3122 edittext.append(hgprefix(_(b"branch merge")))
3113 if ctx.branch():
3123 if ctx.branch():
3114 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3124 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3115 if bookmarks.isactivewdirparent(repo):
3125 if bookmarks.isactivewdirparent(repo):
3116 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3126 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3117 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3127 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3118 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3128 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3119 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3129 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3120 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3130 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3121 if not added and not modified and not removed:
3131 if not added and not modified and not removed:
3122 edittext.append(hgprefix(_(b"no files changed")))
3132 edittext.append(hgprefix(_(b"no files changed")))
3123 edittext.append(b"")
3133 edittext.append(b"")
3124
3134
3125 return b"\n".join(edittext)
3135 return b"\n".join(edittext)
3126
3136
3127
3137
3128 def commitstatus(repo, node, branch, bheads=None, tip=None, opts=None):
3138 def commitstatus(repo, node, branch, bheads=None, tip=None, opts=None):
3129 if opts is None:
3139 if opts is None:
3130 opts = {}
3140 opts = {}
3131 ctx = repo[node]
3141 ctx = repo[node]
3132 parents = ctx.parents()
3142 parents = ctx.parents()
3133
3143
3134 if tip is not None and repo.changelog.tip() == tip:
3144 if tip is not None and repo.changelog.tip() == tip:
3135 # avoid reporting something like "committed new head" when
3145 # avoid reporting something like "committed new head" when
3136 # recommitting old changesets, and issue a helpful warning
3146 # recommitting old changesets, and issue a helpful warning
3137 # for most instances
3147 # for most instances
3138 repo.ui.warn(_("warning: commit already existed in the repository!\n"))
3148 repo.ui.warn(_("warning: commit already existed in the repository!\n"))
3139 elif (
3149 elif (
3140 not opts.get(b'amend')
3150 not opts.get(b'amend')
3141 and bheads
3151 and bheads
3142 and node not in bheads
3152 and node not in bheads
3143 and not any(
3153 and not any(
3144 p.node() in bheads and p.branch() == branch for p in parents
3154 p.node() in bheads and p.branch() == branch for p in parents
3145 )
3155 )
3146 ):
3156 ):
3147 repo.ui.status(_(b'created new head\n'))
3157 repo.ui.status(_(b'created new head\n'))
3148 # The message is not printed for initial roots. For the other
3158 # The message is not printed for initial roots. For the other
3149 # changesets, it is printed in the following situations:
3159 # changesets, it is printed in the following situations:
3150 #
3160 #
3151 # Par column: for the 2 parents with ...
3161 # Par column: for the 2 parents with ...
3152 # N: null or no parent
3162 # N: null or no parent
3153 # B: parent is on another named branch
3163 # B: parent is on another named branch
3154 # C: parent is a regular non head changeset
3164 # C: parent is a regular non head changeset
3155 # H: parent was a branch head of the current branch
3165 # H: parent was a branch head of the current branch
3156 # Msg column: whether we print "created new head" message
3166 # Msg column: whether we print "created new head" message
3157 # In the following, it is assumed that there already exists some
3167 # In the following, it is assumed that there already exists some
3158 # initial branch heads of the current branch, otherwise nothing is
3168 # initial branch heads of the current branch, otherwise nothing is
3159 # printed anyway.
3169 # printed anyway.
3160 #
3170 #
3161 # Par Msg Comment
3171 # Par Msg Comment
3162 # N N y additional topo root
3172 # N N y additional topo root
3163 #
3173 #
3164 # B N y additional branch root
3174 # B N y additional branch root
3165 # C N y additional topo head
3175 # C N y additional topo head
3166 # H N n usual case
3176 # H N n usual case
3167 #
3177 #
3168 # B B y weird additional branch root
3178 # B B y weird additional branch root
3169 # C B y branch merge
3179 # C B y branch merge
3170 # H B n merge with named branch
3180 # H B n merge with named branch
3171 #
3181 #
3172 # C C y additional head from merge
3182 # C C y additional head from merge
3173 # C H n merge with a head
3183 # C H n merge with a head
3174 #
3184 #
3175 # H H n head merge: head count decreases
3185 # H H n head merge: head count decreases
3176
3186
3177 if not opts.get(b'close_branch'):
3187 if not opts.get(b'close_branch'):
3178 for r in parents:
3188 for r in parents:
3179 if r.closesbranch() and r.branch() == branch:
3189 if r.closesbranch() and r.branch() == branch:
3180 repo.ui.status(
3190 repo.ui.status(
3181 _(b'reopening closed branch head %d\n') % r.rev()
3191 _(b'reopening closed branch head %d\n') % r.rev()
3182 )
3192 )
3183
3193
3184 if repo.ui.debugflag:
3194 if repo.ui.debugflag:
3185 repo.ui.write(
3195 repo.ui.write(
3186 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3196 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3187 )
3197 )
3188 elif repo.ui.verbose:
3198 elif repo.ui.verbose:
3189 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3199 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3190
3200
3191
3201
3192 def postcommitstatus(repo, pats, opts):
3202 def postcommitstatus(repo, pats, opts):
3193 return repo.status(match=scmutil.match(repo[None], pats, opts))
3203 return repo.status(match=scmutil.match(repo[None], pats, opts))
3194
3204
3195
3205
3196 def revert(ui, repo, ctx, *pats, **opts):
3206 def revert(ui, repo, ctx, *pats, **opts):
3197 opts = pycompat.byteskwargs(opts)
3207 opts = pycompat.byteskwargs(opts)
3198 parent, p2 = repo.dirstate.parents()
3208 parent, p2 = repo.dirstate.parents()
3199 node = ctx.node()
3209 node = ctx.node()
3200
3210
3201 mf = ctx.manifest()
3211 mf = ctx.manifest()
3202 if node == p2:
3212 if node == p2:
3203 parent = p2
3213 parent = p2
3204
3214
3205 # need all matching names in dirstate and manifest of target rev,
3215 # need all matching names in dirstate and manifest of target rev,
3206 # so have to walk both. do not print errors if files exist in one
3216 # so have to walk both. do not print errors if files exist in one
3207 # but not other. in both cases, filesets should be evaluated against
3217 # but not other. in both cases, filesets should be evaluated against
3208 # workingctx to get consistent result (issue4497). this means 'set:**'
3218 # workingctx to get consistent result (issue4497). this means 'set:**'
3209 # cannot be used to select missing files from target rev.
3219 # cannot be used to select missing files from target rev.
3210
3220
3211 # `names` is a mapping for all elements in working copy and target revision
3221 # `names` is a mapping for all elements in working copy and target revision
3212 # The mapping is in the form:
3222 # The mapping is in the form:
3213 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3223 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3214 names = {}
3224 names = {}
3215 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3225 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3216
3226
3217 with repo.wlock():
3227 with repo.wlock():
3218 ## filling of the `names` mapping
3228 ## filling of the `names` mapping
3219 # walk dirstate to fill `names`
3229 # walk dirstate to fill `names`
3220
3230
3221 interactive = opts.get(b'interactive', False)
3231 interactive = opts.get(b'interactive', False)
3222 wctx = repo[None]
3232 wctx = repo[None]
3223 m = scmutil.match(wctx, pats, opts)
3233 m = scmutil.match(wctx, pats, opts)
3224
3234
3225 # we'll need this later
3235 # we'll need this later
3226 targetsubs = sorted(s for s in wctx.substate if m(s))
3236 targetsubs = sorted(s for s in wctx.substate if m(s))
3227
3237
3228 if not m.always():
3238 if not m.always():
3229 matcher = matchmod.badmatch(m, lambda x, y: False)
3239 matcher = matchmod.badmatch(m, lambda x, y: False)
3230 for abs in wctx.walk(matcher):
3240 for abs in wctx.walk(matcher):
3231 names[abs] = m.exact(abs)
3241 names[abs] = m.exact(abs)
3232
3242
3233 # walk target manifest to fill `names`
3243 # walk target manifest to fill `names`
3234
3244
3235 def badfn(path, msg):
3245 def badfn(path, msg):
3236 if path in names:
3246 if path in names:
3237 return
3247 return
3238 if path in ctx.substate:
3248 if path in ctx.substate:
3239 return
3249 return
3240 path_ = path + b'/'
3250 path_ = path + b'/'
3241 for f in names:
3251 for f in names:
3242 if f.startswith(path_):
3252 if f.startswith(path_):
3243 return
3253 return
3244 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3254 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3245
3255
3246 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3256 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3247 if abs not in names:
3257 if abs not in names:
3248 names[abs] = m.exact(abs)
3258 names[abs] = m.exact(abs)
3249
3259
3250 # Find status of all file in `names`.
3260 # Find status of all file in `names`.
3251 m = scmutil.matchfiles(repo, names)
3261 m = scmutil.matchfiles(repo, names)
3252
3262
3253 changes = repo.status(
3263 changes = repo.status(
3254 node1=node, match=m, unknown=True, ignored=True, clean=True
3264 node1=node, match=m, unknown=True, ignored=True, clean=True
3255 )
3265 )
3256 else:
3266 else:
3257 changes = repo.status(node1=node, match=m)
3267 changes = repo.status(node1=node, match=m)
3258 for kind in changes:
3268 for kind in changes:
3259 for abs in kind:
3269 for abs in kind:
3260 names[abs] = m.exact(abs)
3270 names[abs] = m.exact(abs)
3261
3271
3262 m = scmutil.matchfiles(repo, names)
3272 m = scmutil.matchfiles(repo, names)
3263
3273
3264 modified = set(changes.modified)
3274 modified = set(changes.modified)
3265 added = set(changes.added)
3275 added = set(changes.added)
3266 removed = set(changes.removed)
3276 removed = set(changes.removed)
3267 _deleted = set(changes.deleted)
3277 _deleted = set(changes.deleted)
3268 unknown = set(changes.unknown)
3278 unknown = set(changes.unknown)
3269 unknown.update(changes.ignored)
3279 unknown.update(changes.ignored)
3270 clean = set(changes.clean)
3280 clean = set(changes.clean)
3271 modadded = set()
3281 modadded = set()
3272
3282
3273 # We need to account for the state of the file in the dirstate,
3283 # We need to account for the state of the file in the dirstate,
3274 # even when we revert against something else than parent. This will
3284 # even when we revert against something else than parent. This will
3275 # slightly alter the behavior of revert (doing back up or not, delete
3285 # slightly alter the behavior of revert (doing back up or not, delete
3276 # or just forget etc).
3286 # or just forget etc).
3277 if parent == node:
3287 if parent == node:
3278 dsmodified = modified
3288 dsmodified = modified
3279 dsadded = added
3289 dsadded = added
3280 dsremoved = removed
3290 dsremoved = removed
3281 # store all local modifications, useful later for rename detection
3291 # store all local modifications, useful later for rename detection
3282 localchanges = dsmodified | dsadded
3292 localchanges = dsmodified | dsadded
3283 modified, added, removed = set(), set(), set()
3293 modified, added, removed = set(), set(), set()
3284 else:
3294 else:
3285 changes = repo.status(node1=parent, match=m)
3295 changes = repo.status(node1=parent, match=m)
3286 dsmodified = set(changes.modified)
3296 dsmodified = set(changes.modified)
3287 dsadded = set(changes.added)
3297 dsadded = set(changes.added)
3288 dsremoved = set(changes.removed)
3298 dsremoved = set(changes.removed)
3289 # store all local modifications, useful later for rename detection
3299 # store all local modifications, useful later for rename detection
3290 localchanges = dsmodified | dsadded
3300 localchanges = dsmodified | dsadded
3291
3301
3292 # only take into account for removes between wc and target
3302 # only take into account for removes between wc and target
3293 clean |= dsremoved - removed
3303 clean |= dsremoved - removed
3294 dsremoved &= removed
3304 dsremoved &= removed
3295 # distinct between dirstate remove and other
3305 # distinct between dirstate remove and other
3296 removed -= dsremoved
3306 removed -= dsremoved
3297
3307
3298 modadded = added & dsmodified
3308 modadded = added & dsmodified
3299 added -= modadded
3309 added -= modadded
3300
3310
3301 # tell newly modified apart.
3311 # tell newly modified apart.
3302 dsmodified &= modified
3312 dsmodified &= modified
3303 dsmodified |= modified & dsadded # dirstate added may need backup
3313 dsmodified |= modified & dsadded # dirstate added may need backup
3304 modified -= dsmodified
3314 modified -= dsmodified
3305
3315
3306 # We need to wait for some post-processing to update this set
3316 # We need to wait for some post-processing to update this set
3307 # before making the distinction. The dirstate will be used for
3317 # before making the distinction. The dirstate will be used for
3308 # that purpose.
3318 # that purpose.
3309 dsadded = added
3319 dsadded = added
3310
3320
3311 # in case of merge, files that are actually added can be reported as
3321 # in case of merge, files that are actually added can be reported as
3312 # modified, we need to post process the result
3322 # modified, we need to post process the result
3313 if p2 != nullid:
3323 if p2 != nullid:
3314 mergeadd = set(dsmodified)
3324 mergeadd = set(dsmodified)
3315 for path in dsmodified:
3325 for path in dsmodified:
3316 if path in mf:
3326 if path in mf:
3317 mergeadd.remove(path)
3327 mergeadd.remove(path)
3318 dsadded |= mergeadd
3328 dsadded |= mergeadd
3319 dsmodified -= mergeadd
3329 dsmodified -= mergeadd
3320
3330
3321 # if f is a rename, update `names` to also revert the source
3331 # if f is a rename, update `names` to also revert the source
3322 for f in localchanges:
3332 for f in localchanges:
3323 src = repo.dirstate.copied(f)
3333 src = repo.dirstate.copied(f)
3324 # XXX should we check for rename down to target node?
3334 # XXX should we check for rename down to target node?
3325 if src and src not in names and repo.dirstate[src] == b'r':
3335 if src and src not in names and repo.dirstate[src] == b'r':
3326 dsremoved.add(src)
3336 dsremoved.add(src)
3327 names[src] = True
3337 names[src] = True
3328
3338
3329 # determine the exact nature of the deleted changesets
3339 # determine the exact nature of the deleted changesets
3330 deladded = set(_deleted)
3340 deladded = set(_deleted)
3331 for path in _deleted:
3341 for path in _deleted:
3332 if path in mf:
3342 if path in mf:
3333 deladded.remove(path)
3343 deladded.remove(path)
3334 deleted = _deleted - deladded
3344 deleted = _deleted - deladded
3335
3345
3336 # distinguish between file to forget and the other
3346 # distinguish between file to forget and the other
3337 added = set()
3347 added = set()
3338 for abs in dsadded:
3348 for abs in dsadded:
3339 if repo.dirstate[abs] != b'a':
3349 if repo.dirstate[abs] != b'a':
3340 added.add(abs)
3350 added.add(abs)
3341 dsadded -= added
3351 dsadded -= added
3342
3352
3343 for abs in deladded:
3353 for abs in deladded:
3344 if repo.dirstate[abs] == b'a':
3354 if repo.dirstate[abs] == b'a':
3345 dsadded.add(abs)
3355 dsadded.add(abs)
3346 deladded -= dsadded
3356 deladded -= dsadded
3347
3357
3348 # For files marked as removed, we check if an unknown file is present at
3358 # For files marked as removed, we check if an unknown file is present at
3349 # the same path. If a such file exists it may need to be backed up.
3359 # the same path. If a such file exists it may need to be backed up.
3350 # Making the distinction at this stage helps have simpler backup
3360 # Making the distinction at this stage helps have simpler backup
3351 # logic.
3361 # logic.
3352 removunk = set()
3362 removunk = set()
3353 for abs in removed:
3363 for abs in removed:
3354 target = repo.wjoin(abs)
3364 target = repo.wjoin(abs)
3355 if os.path.lexists(target):
3365 if os.path.lexists(target):
3356 removunk.add(abs)
3366 removunk.add(abs)
3357 removed -= removunk
3367 removed -= removunk
3358
3368
3359 dsremovunk = set()
3369 dsremovunk = set()
3360 for abs in dsremoved:
3370 for abs in dsremoved:
3361 target = repo.wjoin(abs)
3371 target = repo.wjoin(abs)
3362 if os.path.lexists(target):
3372 if os.path.lexists(target):
3363 dsremovunk.add(abs)
3373 dsremovunk.add(abs)
3364 dsremoved -= dsremovunk
3374 dsremoved -= dsremovunk
3365
3375
3366 # action to be actually performed by revert
3376 # action to be actually performed by revert
3367 # (<list of file>, message>) tuple
3377 # (<list of file>, message>) tuple
3368 actions = {
3378 actions = {
3369 b'revert': ([], _(b'reverting %s\n')),
3379 b'revert': ([], _(b'reverting %s\n')),
3370 b'add': ([], _(b'adding %s\n')),
3380 b'add': ([], _(b'adding %s\n')),
3371 b'remove': ([], _(b'removing %s\n')),
3381 b'remove': ([], _(b'removing %s\n')),
3372 b'drop': ([], _(b'removing %s\n')),
3382 b'drop': ([], _(b'removing %s\n')),
3373 b'forget': ([], _(b'forgetting %s\n')),
3383 b'forget': ([], _(b'forgetting %s\n')),
3374 b'undelete': ([], _(b'undeleting %s\n')),
3384 b'undelete': ([], _(b'undeleting %s\n')),
3375 b'noop': (None, _(b'no changes needed to %s\n')),
3385 b'noop': (None, _(b'no changes needed to %s\n')),
3376 b'unknown': (None, _(b'file not managed: %s\n')),
3386 b'unknown': (None, _(b'file not managed: %s\n')),
3377 }
3387 }
3378
3388
3379 # "constant" that convey the backup strategy.
3389 # "constant" that convey the backup strategy.
3380 # All set to `discard` if `no-backup` is set do avoid checking
3390 # All set to `discard` if `no-backup` is set do avoid checking
3381 # no_backup lower in the code.
3391 # no_backup lower in the code.
3382 # These values are ordered for comparison purposes
3392 # These values are ordered for comparison purposes
3383 backupinteractive = 3 # do backup if interactively modified
3393 backupinteractive = 3 # do backup if interactively modified
3384 backup = 2 # unconditionally do backup
3394 backup = 2 # unconditionally do backup
3385 check = 1 # check if the existing file differs from target
3395 check = 1 # check if the existing file differs from target
3386 discard = 0 # never do backup
3396 discard = 0 # never do backup
3387 if opts.get(b'no_backup'):
3397 if opts.get(b'no_backup'):
3388 backupinteractive = backup = check = discard
3398 backupinteractive = backup = check = discard
3389 if interactive:
3399 if interactive:
3390 dsmodifiedbackup = backupinteractive
3400 dsmodifiedbackup = backupinteractive
3391 else:
3401 else:
3392 dsmodifiedbackup = backup
3402 dsmodifiedbackup = backup
3393 tobackup = set()
3403 tobackup = set()
3394
3404
3395 backupanddel = actions[b'remove']
3405 backupanddel = actions[b'remove']
3396 if not opts.get(b'no_backup'):
3406 if not opts.get(b'no_backup'):
3397 backupanddel = actions[b'drop']
3407 backupanddel = actions[b'drop']
3398
3408
3399 disptable = (
3409 disptable = (
3400 # dispatch table:
3410 # dispatch table:
3401 # file state
3411 # file state
3402 # action
3412 # action
3403 # make backup
3413 # make backup
3404 ## Sets that results that will change file on disk
3414 ## Sets that results that will change file on disk
3405 # Modified compared to target, no local change
3415 # Modified compared to target, no local change
3406 (modified, actions[b'revert'], discard),
3416 (modified, actions[b'revert'], discard),
3407 # Modified compared to target, but local file is deleted
3417 # Modified compared to target, but local file is deleted
3408 (deleted, actions[b'revert'], discard),
3418 (deleted, actions[b'revert'], discard),
3409 # Modified compared to target, local change
3419 # Modified compared to target, local change
3410 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3420 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3411 # Added since target
3421 # Added since target
3412 (added, actions[b'remove'], discard),
3422 (added, actions[b'remove'], discard),
3413 # Added in working directory
3423 # Added in working directory
3414 (dsadded, actions[b'forget'], discard),
3424 (dsadded, actions[b'forget'], discard),
3415 # Added since target, have local modification
3425 # Added since target, have local modification
3416 (modadded, backupanddel, backup),
3426 (modadded, backupanddel, backup),
3417 # Added since target but file is missing in working directory
3427 # Added since target but file is missing in working directory
3418 (deladded, actions[b'drop'], discard),
3428 (deladded, actions[b'drop'], discard),
3419 # Removed since target, before working copy parent
3429 # Removed since target, before working copy parent
3420 (removed, actions[b'add'], discard),
3430 (removed, actions[b'add'], discard),
3421 # Same as `removed` but an unknown file exists at the same path
3431 # Same as `removed` but an unknown file exists at the same path
3422 (removunk, actions[b'add'], check),
3432 (removunk, actions[b'add'], check),
3423 # Removed since targe, marked as such in working copy parent
3433 # Removed since targe, marked as such in working copy parent
3424 (dsremoved, actions[b'undelete'], discard),
3434 (dsremoved, actions[b'undelete'], discard),
3425 # Same as `dsremoved` but an unknown file exists at the same path
3435 # Same as `dsremoved` but an unknown file exists at the same path
3426 (dsremovunk, actions[b'undelete'], check),
3436 (dsremovunk, actions[b'undelete'], check),
3427 ## the following sets does not result in any file changes
3437 ## the following sets does not result in any file changes
3428 # File with no modification
3438 # File with no modification
3429 (clean, actions[b'noop'], discard),
3439 (clean, actions[b'noop'], discard),
3430 # Existing file, not tracked anywhere
3440 # Existing file, not tracked anywhere
3431 (unknown, actions[b'unknown'], discard),
3441 (unknown, actions[b'unknown'], discard),
3432 )
3442 )
3433
3443
3434 for abs, exact in sorted(names.items()):
3444 for abs, exact in sorted(names.items()):
3435 # target file to be touch on disk (relative to cwd)
3445 # target file to be touch on disk (relative to cwd)
3436 target = repo.wjoin(abs)
3446 target = repo.wjoin(abs)
3437 # search the entry in the dispatch table.
3447 # search the entry in the dispatch table.
3438 # if the file is in any of these sets, it was touched in the working
3448 # if the file is in any of these sets, it was touched in the working
3439 # directory parent and we are sure it needs to be reverted.
3449 # directory parent and we are sure it needs to be reverted.
3440 for table, (xlist, msg), dobackup in disptable:
3450 for table, (xlist, msg), dobackup in disptable:
3441 if abs not in table:
3451 if abs not in table:
3442 continue
3452 continue
3443 if xlist is not None:
3453 if xlist is not None:
3444 xlist.append(abs)
3454 xlist.append(abs)
3445 if dobackup:
3455 if dobackup:
3446 # If in interactive mode, don't automatically create
3456 # If in interactive mode, don't automatically create
3447 # .orig files (issue4793)
3457 # .orig files (issue4793)
3448 if dobackup == backupinteractive:
3458 if dobackup == backupinteractive:
3449 tobackup.add(abs)
3459 tobackup.add(abs)
3450 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3460 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3451 absbakname = scmutil.backuppath(ui, repo, abs)
3461 absbakname = scmutil.backuppath(ui, repo, abs)
3452 bakname = os.path.relpath(
3462 bakname = os.path.relpath(
3453 absbakname, start=repo.root
3463 absbakname, start=repo.root
3454 )
3464 )
3455 ui.note(
3465 ui.note(
3456 _(b'saving current version of %s as %s\n')
3466 _(b'saving current version of %s as %s\n')
3457 % (uipathfn(abs), uipathfn(bakname))
3467 % (uipathfn(abs), uipathfn(bakname))
3458 )
3468 )
3459 if not opts.get(b'dry_run'):
3469 if not opts.get(b'dry_run'):
3460 if interactive:
3470 if interactive:
3461 util.copyfile(target, absbakname)
3471 util.copyfile(target, absbakname)
3462 else:
3472 else:
3463 util.rename(target, absbakname)
3473 util.rename(target, absbakname)
3464 if opts.get(b'dry_run'):
3474 if opts.get(b'dry_run'):
3465 if ui.verbose or not exact:
3475 if ui.verbose or not exact:
3466 ui.status(msg % uipathfn(abs))
3476 ui.status(msg % uipathfn(abs))
3467 elif exact:
3477 elif exact:
3468 ui.warn(msg % uipathfn(abs))
3478 ui.warn(msg % uipathfn(abs))
3469 break
3479 break
3470
3480
3471 if not opts.get(b'dry_run'):
3481 if not opts.get(b'dry_run'):
3472 needdata = (b'revert', b'add', b'undelete')
3482 needdata = (b'revert', b'add', b'undelete')
3473 oplist = [actions[name][0] for name in needdata]
3483 oplist = [actions[name][0] for name in needdata]
3474 prefetch = scmutil.prefetchfiles
3484 prefetch = scmutil.prefetchfiles
3475 matchfiles = scmutil.matchfiles(
3485 matchfiles = scmutil.matchfiles(
3476 repo, [f for sublist in oplist for f in sublist]
3486 repo, [f for sublist in oplist for f in sublist]
3477 )
3487 )
3478 prefetch(
3488 prefetch(
3479 repo,
3489 repo,
3480 [(ctx.rev(), matchfiles)],
3490 [(ctx.rev(), matchfiles)],
3481 )
3491 )
3482 match = scmutil.match(repo[None], pats)
3492 match = scmutil.match(repo[None], pats)
3483 _performrevert(
3493 _performrevert(
3484 repo,
3494 repo,
3485 ctx,
3495 ctx,
3486 names,
3496 names,
3487 uipathfn,
3497 uipathfn,
3488 actions,
3498 actions,
3489 match,
3499 match,
3490 interactive,
3500 interactive,
3491 tobackup,
3501 tobackup,
3492 )
3502 )
3493
3503
3494 if targetsubs:
3504 if targetsubs:
3495 # Revert the subrepos on the revert list
3505 # Revert the subrepos on the revert list
3496 for sub in targetsubs:
3506 for sub in targetsubs:
3497 try:
3507 try:
3498 wctx.sub(sub).revert(
3508 wctx.sub(sub).revert(
3499 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3509 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3500 )
3510 )
3501 except KeyError:
3511 except KeyError:
3502 raise error.Abort(
3512 raise error.Abort(
3503 b"subrepository '%s' does not exist in %s!"
3513 b"subrepository '%s' does not exist in %s!"
3504 % (sub, short(ctx.node()))
3514 % (sub, short(ctx.node()))
3505 )
3515 )
3506
3516
3507
3517
3508 def _performrevert(
3518 def _performrevert(
3509 repo,
3519 repo,
3510 ctx,
3520 ctx,
3511 names,
3521 names,
3512 uipathfn,
3522 uipathfn,
3513 actions,
3523 actions,
3514 match,
3524 match,
3515 interactive=False,
3525 interactive=False,
3516 tobackup=None,
3526 tobackup=None,
3517 ):
3527 ):
3518 """function that actually perform all the actions computed for revert
3528 """function that actually perform all the actions computed for revert
3519
3529
3520 This is an independent function to let extension to plug in and react to
3530 This is an independent function to let extension to plug in and react to
3521 the imminent revert.
3531 the imminent revert.
3522
3532
3523 Make sure you have the working directory locked when calling this function.
3533 Make sure you have the working directory locked when calling this function.
3524 """
3534 """
3525 parent, p2 = repo.dirstate.parents()
3535 parent, p2 = repo.dirstate.parents()
3526 node = ctx.node()
3536 node = ctx.node()
3527 excluded_files = []
3537 excluded_files = []
3528
3538
3529 def checkout(f):
3539 def checkout(f):
3530 fc = ctx[f]
3540 fc = ctx[f]
3531 repo.wwrite(f, fc.data(), fc.flags())
3541 repo.wwrite(f, fc.data(), fc.flags())
3532
3542
3533 def doremove(f):
3543 def doremove(f):
3534 try:
3544 try:
3535 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3545 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3536 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3546 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3537 except OSError:
3547 except OSError:
3538 pass
3548 pass
3539 repo.dirstate.remove(f)
3549 repo.dirstate.remove(f)
3540
3550
3541 def prntstatusmsg(action, f):
3551 def prntstatusmsg(action, f):
3542 exact = names[f]
3552 exact = names[f]
3543 if repo.ui.verbose or not exact:
3553 if repo.ui.verbose or not exact:
3544 repo.ui.status(actions[action][1] % uipathfn(f))
3554 repo.ui.status(actions[action][1] % uipathfn(f))
3545
3555
3546 audit_path = pathutil.pathauditor(repo.root, cached=True)
3556 audit_path = pathutil.pathauditor(repo.root, cached=True)
3547 for f in actions[b'forget'][0]:
3557 for f in actions[b'forget'][0]:
3548 if interactive:
3558 if interactive:
3549 choice = repo.ui.promptchoice(
3559 choice = repo.ui.promptchoice(
3550 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3560 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3551 )
3561 )
3552 if choice == 0:
3562 if choice == 0:
3553 prntstatusmsg(b'forget', f)
3563 prntstatusmsg(b'forget', f)
3554 repo.dirstate.drop(f)
3564 repo.dirstate.drop(f)
3555 else:
3565 else:
3556 excluded_files.append(f)
3566 excluded_files.append(f)
3557 else:
3567 else:
3558 prntstatusmsg(b'forget', f)
3568 prntstatusmsg(b'forget', f)
3559 repo.dirstate.drop(f)
3569 repo.dirstate.drop(f)
3560 for f in actions[b'remove'][0]:
3570 for f in actions[b'remove'][0]:
3561 audit_path(f)
3571 audit_path(f)
3562 if interactive:
3572 if interactive:
3563 choice = repo.ui.promptchoice(
3573 choice = repo.ui.promptchoice(
3564 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3574 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3565 )
3575 )
3566 if choice == 0:
3576 if choice == 0:
3567 prntstatusmsg(b'remove', f)
3577 prntstatusmsg(b'remove', f)
3568 doremove(f)
3578 doremove(f)
3569 else:
3579 else:
3570 excluded_files.append(f)
3580 excluded_files.append(f)
3571 else:
3581 else:
3572 prntstatusmsg(b'remove', f)
3582 prntstatusmsg(b'remove', f)
3573 doremove(f)
3583 doremove(f)
3574 for f in actions[b'drop'][0]:
3584 for f in actions[b'drop'][0]:
3575 audit_path(f)
3585 audit_path(f)
3576 prntstatusmsg(b'drop', f)
3586 prntstatusmsg(b'drop', f)
3577 repo.dirstate.remove(f)
3587 repo.dirstate.remove(f)
3578
3588
3579 normal = None
3589 normal = None
3580 if node == parent:
3590 if node == parent:
3581 # We're reverting to our parent. If possible, we'd like status
3591 # We're reverting to our parent. If possible, we'd like status
3582 # to report the file as clean. We have to use normallookup for
3592 # to report the file as clean. We have to use normallookup for
3583 # merges to avoid losing information about merged/dirty files.
3593 # merges to avoid losing information about merged/dirty files.
3584 if p2 != nullid:
3594 if p2 != nullid:
3585 normal = repo.dirstate.normallookup
3595 normal = repo.dirstate.normallookup
3586 else:
3596 else:
3587 normal = repo.dirstate.normal
3597 normal = repo.dirstate.normal
3588
3598
3589 newlyaddedandmodifiedfiles = set()
3599 newlyaddedandmodifiedfiles = set()
3590 if interactive:
3600 if interactive:
3591 # Prompt the user for changes to revert
3601 # Prompt the user for changes to revert
3592 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3602 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3593 m = scmutil.matchfiles(repo, torevert)
3603 m = scmutil.matchfiles(repo, torevert)
3594 diffopts = patch.difffeatureopts(
3604 diffopts = patch.difffeatureopts(
3595 repo.ui,
3605 repo.ui,
3596 whitespace=True,
3606 whitespace=True,
3597 section=b'commands',
3607 section=b'commands',
3598 configprefix=b'revert.interactive.',
3608 configprefix=b'revert.interactive.',
3599 )
3609 )
3600 diffopts.nodates = True
3610 diffopts.nodates = True
3601 diffopts.git = True
3611 diffopts.git = True
3602 operation = b'apply'
3612 operation = b'apply'
3603 if node == parent:
3613 if node == parent:
3604 if repo.ui.configbool(
3614 if repo.ui.configbool(
3605 b'experimental', b'revert.interactive.select-to-keep'
3615 b'experimental', b'revert.interactive.select-to-keep'
3606 ):
3616 ):
3607 operation = b'keep'
3617 operation = b'keep'
3608 else:
3618 else:
3609 operation = b'discard'
3619 operation = b'discard'
3610
3620
3611 if operation == b'apply':
3621 if operation == b'apply':
3612 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3622 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3613 else:
3623 else:
3614 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3624 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3615 originalchunks = patch.parsepatch(diff)
3625 originalchunks = patch.parsepatch(diff)
3616
3626
3617 try:
3627 try:
3618
3628
3619 chunks, opts = recordfilter(
3629 chunks, opts = recordfilter(
3620 repo.ui, originalchunks, match, operation=operation
3630 repo.ui, originalchunks, match, operation=operation
3621 )
3631 )
3622 if operation == b'discard':
3632 if operation == b'discard':
3623 chunks = patch.reversehunks(chunks)
3633 chunks = patch.reversehunks(chunks)
3624
3634
3625 except error.PatchError as err:
3635 except error.PatchError as err:
3626 raise error.Abort(_(b'error parsing patch: %s') % err)
3636 raise error.Abort(_(b'error parsing patch: %s') % err)
3627
3637
3628 # FIXME: when doing an interactive revert of a copy, there's no way of
3638 # FIXME: when doing an interactive revert of a copy, there's no way of
3629 # performing a partial revert of the added file, the only option is
3639 # performing a partial revert of the added file, the only option is
3630 # "remove added file <name> (Yn)?", so we don't need to worry about the
3640 # "remove added file <name> (Yn)?", so we don't need to worry about the
3631 # alsorestore value. Ideally we'd be able to partially revert
3641 # alsorestore value. Ideally we'd be able to partially revert
3632 # copied/renamed files.
3642 # copied/renamed files.
3633 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3643 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3634 chunks, originalchunks
3644 chunks, originalchunks
3635 )
3645 )
3636 if tobackup is None:
3646 if tobackup is None:
3637 tobackup = set()
3647 tobackup = set()
3638 # Apply changes
3648 # Apply changes
3639 fp = stringio()
3649 fp = stringio()
3640 # chunks are serialized per file, but files aren't sorted
3650 # chunks are serialized per file, but files aren't sorted
3641 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3651 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3642 prntstatusmsg(b'revert', f)
3652 prntstatusmsg(b'revert', f)
3643 files = set()
3653 files = set()
3644 for c in chunks:
3654 for c in chunks:
3645 if ishunk(c):
3655 if ishunk(c):
3646 abs = c.header.filename()
3656 abs = c.header.filename()
3647 # Create a backup file only if this hunk should be backed up
3657 # Create a backup file only if this hunk should be backed up
3648 if c.header.filename() in tobackup:
3658 if c.header.filename() in tobackup:
3649 target = repo.wjoin(abs)
3659 target = repo.wjoin(abs)
3650 bakname = scmutil.backuppath(repo.ui, repo, abs)
3660 bakname = scmutil.backuppath(repo.ui, repo, abs)
3651 util.copyfile(target, bakname)
3661 util.copyfile(target, bakname)
3652 tobackup.remove(abs)
3662 tobackup.remove(abs)
3653 if abs not in files:
3663 if abs not in files:
3654 files.add(abs)
3664 files.add(abs)
3655 if operation == b'keep':
3665 if operation == b'keep':
3656 checkout(abs)
3666 checkout(abs)
3657 c.write(fp)
3667 c.write(fp)
3658 dopatch = fp.tell()
3668 dopatch = fp.tell()
3659 fp.seek(0)
3669 fp.seek(0)
3660 if dopatch:
3670 if dopatch:
3661 try:
3671 try:
3662 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3672 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3663 except error.PatchError as err:
3673 except error.PatchError as err:
3664 raise error.Abort(pycompat.bytestr(err))
3674 raise error.Abort(pycompat.bytestr(err))
3665 del fp
3675 del fp
3666 else:
3676 else:
3667 for f in actions[b'revert'][0]:
3677 for f in actions[b'revert'][0]:
3668 prntstatusmsg(b'revert', f)
3678 prntstatusmsg(b'revert', f)
3669 checkout(f)
3679 checkout(f)
3670 if normal:
3680 if normal:
3671 normal(f)
3681 normal(f)
3672
3682
3673 for f in actions[b'add'][0]:
3683 for f in actions[b'add'][0]:
3674 # Don't checkout modified files, they are already created by the diff
3684 # Don't checkout modified files, they are already created by the diff
3675 if f not in newlyaddedandmodifiedfiles:
3685 if f not in newlyaddedandmodifiedfiles:
3676 prntstatusmsg(b'add', f)
3686 prntstatusmsg(b'add', f)
3677 checkout(f)
3687 checkout(f)
3678 repo.dirstate.add(f)
3688 repo.dirstate.add(f)
3679
3689
3680 normal = repo.dirstate.normallookup
3690 normal = repo.dirstate.normallookup
3681 if node == parent and p2 == nullid:
3691 if node == parent and p2 == nullid:
3682 normal = repo.dirstate.normal
3692 normal = repo.dirstate.normal
3683 for f in actions[b'undelete'][0]:
3693 for f in actions[b'undelete'][0]:
3684 if interactive:
3694 if interactive:
3685 choice = repo.ui.promptchoice(
3695 choice = repo.ui.promptchoice(
3686 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3696 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3687 )
3697 )
3688 if choice == 0:
3698 if choice == 0:
3689 prntstatusmsg(b'undelete', f)
3699 prntstatusmsg(b'undelete', f)
3690 checkout(f)
3700 checkout(f)
3691 normal(f)
3701 normal(f)
3692 else:
3702 else:
3693 excluded_files.append(f)
3703 excluded_files.append(f)
3694 else:
3704 else:
3695 prntstatusmsg(b'undelete', f)
3705 prntstatusmsg(b'undelete', f)
3696 checkout(f)
3706 checkout(f)
3697 normal(f)
3707 normal(f)
3698
3708
3699 copied = copies.pathcopies(repo[parent], ctx)
3709 copied = copies.pathcopies(repo[parent], ctx)
3700
3710
3701 for f in (
3711 for f in (
3702 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3712 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3703 ):
3713 ):
3704 if f in copied:
3714 if f in copied:
3705 repo.dirstate.copy(copied[f], f)
3715 repo.dirstate.copy(copied[f], f)
3706
3716
3707
3717
3708 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3718 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3709 # commands.outgoing. "missing" is "missing" of the result of
3719 # commands.outgoing. "missing" is "missing" of the result of
3710 # "findcommonoutgoing()"
3720 # "findcommonoutgoing()"
3711 outgoinghooks = util.hooks()
3721 outgoinghooks = util.hooks()
3712
3722
3713 # a list of (ui, repo) functions called by commands.summary
3723 # a list of (ui, repo) functions called by commands.summary
3714 summaryhooks = util.hooks()
3724 summaryhooks = util.hooks()
3715
3725
3716 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3726 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3717 #
3727 #
3718 # functions should return tuple of booleans below, if 'changes' is None:
3728 # functions should return tuple of booleans below, if 'changes' is None:
3719 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3729 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3720 #
3730 #
3721 # otherwise, 'changes' is a tuple of tuples below:
3731 # otherwise, 'changes' is a tuple of tuples below:
3722 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3732 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3723 # - (desturl, destbranch, destpeer, outgoing)
3733 # - (desturl, destbranch, destpeer, outgoing)
3724 summaryremotehooks = util.hooks()
3734 summaryremotehooks = util.hooks()
3725
3735
3726
3736
3727 def checkunfinished(repo, commit=False, skipmerge=False):
3737 def checkunfinished(repo, commit=False, skipmerge=False):
3728 """Look for an unfinished multistep operation, like graft, and abort
3738 """Look for an unfinished multistep operation, like graft, and abort
3729 if found. It's probably good to check this right before
3739 if found. It's probably good to check this right before
3730 bailifchanged().
3740 bailifchanged().
3731 """
3741 """
3732 # Check for non-clearable states first, so things like rebase will take
3742 # Check for non-clearable states first, so things like rebase will take
3733 # precedence over update.
3743 # precedence over update.
3734 for state in statemod._unfinishedstates:
3744 for state in statemod._unfinishedstates:
3735 if (
3745 if (
3736 state._clearable
3746 state._clearable
3737 or (commit and state._allowcommit)
3747 or (commit and state._allowcommit)
3738 or state._reportonly
3748 or state._reportonly
3739 ):
3749 ):
3740 continue
3750 continue
3741 if state.isunfinished(repo):
3751 if state.isunfinished(repo):
3742 raise error.StateError(state.msg(), hint=state.hint())
3752 raise error.StateError(state.msg(), hint=state.hint())
3743
3753
3744 for s in statemod._unfinishedstates:
3754 for s in statemod._unfinishedstates:
3745 if (
3755 if (
3746 not s._clearable
3756 not s._clearable
3747 or (commit and s._allowcommit)
3757 or (commit and s._allowcommit)
3748 or (s._opname == b'merge' and skipmerge)
3758 or (s._opname == b'merge' and skipmerge)
3749 or s._reportonly
3759 or s._reportonly
3750 ):
3760 ):
3751 continue
3761 continue
3752 if s.isunfinished(repo):
3762 if s.isunfinished(repo):
3753 raise error.StateError(s.msg(), hint=s.hint())
3763 raise error.StateError(s.msg(), hint=s.hint())
3754
3764
3755
3765
3756 def clearunfinished(repo):
3766 def clearunfinished(repo):
3757 """Check for unfinished operations (as above), and clear the ones
3767 """Check for unfinished operations (as above), and clear the ones
3758 that are clearable.
3768 that are clearable.
3759 """
3769 """
3760 for state in statemod._unfinishedstates:
3770 for state in statemod._unfinishedstates:
3761 if state._reportonly:
3771 if state._reportonly:
3762 continue
3772 continue
3763 if not state._clearable and state.isunfinished(repo):
3773 if not state._clearable and state.isunfinished(repo):
3764 raise error.StateError(state.msg(), hint=state.hint())
3774 raise error.StateError(state.msg(), hint=state.hint())
3765
3775
3766 for s in statemod._unfinishedstates:
3776 for s in statemod._unfinishedstates:
3767 if s._opname == b'merge' or state._reportonly:
3777 if s._opname == b'merge' or state._reportonly:
3768 continue
3778 continue
3769 if s._clearable and s.isunfinished(repo):
3779 if s._clearable and s.isunfinished(repo):
3770 util.unlink(repo.vfs.join(s._fname))
3780 util.unlink(repo.vfs.join(s._fname))
3771
3781
3772
3782
3773 def getunfinishedstate(repo):
3783 def getunfinishedstate(repo):
3774 """Checks for unfinished operations and returns statecheck object
3784 """Checks for unfinished operations and returns statecheck object
3775 for it"""
3785 for it"""
3776 for state in statemod._unfinishedstates:
3786 for state in statemod._unfinishedstates:
3777 if state.isunfinished(repo):
3787 if state.isunfinished(repo):
3778 return state
3788 return state
3779 return None
3789 return None
3780
3790
3781
3791
3782 def howtocontinue(repo):
3792 def howtocontinue(repo):
3783 """Check for an unfinished operation and return the command to finish
3793 """Check for an unfinished operation and return the command to finish
3784 it.
3794 it.
3785
3795
3786 statemod._unfinishedstates list is checked for an unfinished operation
3796 statemod._unfinishedstates list is checked for an unfinished operation
3787 and the corresponding message to finish it is generated if a method to
3797 and the corresponding message to finish it is generated if a method to
3788 continue is supported by the operation.
3798 continue is supported by the operation.
3789
3799
3790 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3800 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3791 a boolean.
3801 a boolean.
3792 """
3802 """
3793 contmsg = _(b"continue: %s")
3803 contmsg = _(b"continue: %s")
3794 for state in statemod._unfinishedstates:
3804 for state in statemod._unfinishedstates:
3795 if not state._continueflag:
3805 if not state._continueflag:
3796 continue
3806 continue
3797 if state.isunfinished(repo):
3807 if state.isunfinished(repo):
3798 return contmsg % state.continuemsg(), True
3808 return contmsg % state.continuemsg(), True
3799 if repo[None].dirty(missing=True, merge=False, branch=False):
3809 if repo[None].dirty(missing=True, merge=False, branch=False):
3800 return contmsg % _(b"hg commit"), False
3810 return contmsg % _(b"hg commit"), False
3801 return None, None
3811 return None, None
3802
3812
3803
3813
3804 def checkafterresolved(repo):
3814 def checkafterresolved(repo):
3805 """Inform the user about the next action after completing hg resolve
3815 """Inform the user about the next action after completing hg resolve
3806
3816
3807 If there's a an unfinished operation that supports continue flag,
3817 If there's a an unfinished operation that supports continue flag,
3808 howtocontinue will yield repo.ui.warn as the reporter.
3818 howtocontinue will yield repo.ui.warn as the reporter.
3809
3819
3810 Otherwise, it will yield repo.ui.note.
3820 Otherwise, it will yield repo.ui.note.
3811 """
3821 """
3812 msg, warning = howtocontinue(repo)
3822 msg, warning = howtocontinue(repo)
3813 if msg is not None:
3823 if msg is not None:
3814 if warning:
3824 if warning:
3815 repo.ui.warn(b"%s\n" % msg)
3825 repo.ui.warn(b"%s\n" % msg)
3816 else:
3826 else:
3817 repo.ui.note(b"%s\n" % msg)
3827 repo.ui.note(b"%s\n" % msg)
3818
3828
3819
3829
3820 def wrongtooltocontinue(repo, task):
3830 def wrongtooltocontinue(repo, task):
3821 """Raise an abort suggesting how to properly continue if there is an
3831 """Raise an abort suggesting how to properly continue if there is an
3822 active task.
3832 active task.
3823
3833
3824 Uses howtocontinue() to find the active task.
3834 Uses howtocontinue() to find the active task.
3825
3835
3826 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3836 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3827 a hint.
3837 a hint.
3828 """
3838 """
3829 after = howtocontinue(repo)
3839 after = howtocontinue(repo)
3830 hint = None
3840 hint = None
3831 if after[1]:
3841 if after[1]:
3832 hint = after[0]
3842 hint = after[0]
3833 raise error.StateError(_(b'no %s in progress') % task, hint=hint)
3843 raise error.StateError(_(b'no %s in progress') % task, hint=hint)
3834
3844
3835
3845
3836 def abortgraft(ui, repo, graftstate):
3846 def abortgraft(ui, repo, graftstate):
3837 """abort the interrupted graft and rollbacks to the state before interrupted
3847 """abort the interrupted graft and rollbacks to the state before interrupted
3838 graft"""
3848 graft"""
3839 if not graftstate.exists():
3849 if not graftstate.exists():
3840 raise error.StateError(_(b"no interrupted graft to abort"))
3850 raise error.StateError(_(b"no interrupted graft to abort"))
3841 statedata = readgraftstate(repo, graftstate)
3851 statedata = readgraftstate(repo, graftstate)
3842 newnodes = statedata.get(b'newnodes')
3852 newnodes = statedata.get(b'newnodes')
3843 if newnodes is None:
3853 if newnodes is None:
3844 # and old graft state which does not have all the data required to abort
3854 # and old graft state which does not have all the data required to abort
3845 # the graft
3855 # the graft
3846 raise error.Abort(_(b"cannot abort using an old graftstate"))
3856 raise error.Abort(_(b"cannot abort using an old graftstate"))
3847
3857
3848 # changeset from which graft operation was started
3858 # changeset from which graft operation was started
3849 if len(newnodes) > 0:
3859 if len(newnodes) > 0:
3850 startctx = repo[newnodes[0]].p1()
3860 startctx = repo[newnodes[0]].p1()
3851 else:
3861 else:
3852 startctx = repo[b'.']
3862 startctx = repo[b'.']
3853 # whether to strip or not
3863 # whether to strip or not
3854 cleanup = False
3864 cleanup = False
3855
3865
3856 if newnodes:
3866 if newnodes:
3857 newnodes = [repo[r].rev() for r in newnodes]
3867 newnodes = [repo[r].rev() for r in newnodes]
3858 cleanup = True
3868 cleanup = True
3859 # checking that none of the newnodes turned public or is public
3869 # checking that none of the newnodes turned public or is public
3860 immutable = [c for c in newnodes if not repo[c].mutable()]
3870 immutable = [c for c in newnodes if not repo[c].mutable()]
3861 if immutable:
3871 if immutable:
3862 repo.ui.warn(
3872 repo.ui.warn(
3863 _(b"cannot clean up public changesets %s\n")
3873 _(b"cannot clean up public changesets %s\n")
3864 % b', '.join(bytes(repo[r]) for r in immutable),
3874 % b', '.join(bytes(repo[r]) for r in immutable),
3865 hint=_(b"see 'hg help phases' for details"),
3875 hint=_(b"see 'hg help phases' for details"),
3866 )
3876 )
3867 cleanup = False
3877 cleanup = False
3868
3878
3869 # checking that no new nodes are created on top of grafted revs
3879 # checking that no new nodes are created on top of grafted revs
3870 desc = set(repo.changelog.descendants(newnodes))
3880 desc = set(repo.changelog.descendants(newnodes))
3871 if desc - set(newnodes):
3881 if desc - set(newnodes):
3872 repo.ui.warn(
3882 repo.ui.warn(
3873 _(
3883 _(
3874 b"new changesets detected on destination "
3884 b"new changesets detected on destination "
3875 b"branch, can't strip\n"
3885 b"branch, can't strip\n"
3876 )
3886 )
3877 )
3887 )
3878 cleanup = False
3888 cleanup = False
3879
3889
3880 if cleanup:
3890 if cleanup:
3881 with repo.wlock(), repo.lock():
3891 with repo.wlock(), repo.lock():
3882 mergemod.clean_update(startctx)
3892 mergemod.clean_update(startctx)
3883 # stripping the new nodes created
3893 # stripping the new nodes created
3884 strippoints = [
3894 strippoints = [
3885 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3895 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3886 ]
3896 ]
3887 repair.strip(repo.ui, repo, strippoints, backup=False)
3897 repair.strip(repo.ui, repo, strippoints, backup=False)
3888
3898
3889 if not cleanup:
3899 if not cleanup:
3890 # we don't update to the startnode if we can't strip
3900 # we don't update to the startnode if we can't strip
3891 startctx = repo[b'.']
3901 startctx = repo[b'.']
3892 mergemod.clean_update(startctx)
3902 mergemod.clean_update(startctx)
3893
3903
3894 ui.status(_(b"graft aborted\n"))
3904 ui.status(_(b"graft aborted\n"))
3895 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3905 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3896 graftstate.delete()
3906 graftstate.delete()
3897 return 0
3907 return 0
3898
3908
3899
3909
3900 def readgraftstate(repo, graftstate):
3910 def readgraftstate(repo, graftstate):
3901 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3911 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3902 """read the graft state file and return a dict of the data stored in it"""
3912 """read the graft state file and return a dict of the data stored in it"""
3903 try:
3913 try:
3904 return graftstate.read()
3914 return graftstate.read()
3905 except error.CorruptedState:
3915 except error.CorruptedState:
3906 nodes = repo.vfs.read(b'graftstate').splitlines()
3916 nodes = repo.vfs.read(b'graftstate').splitlines()
3907 return {b'nodes': nodes}
3917 return {b'nodes': nodes}
3908
3918
3909
3919
3910 def hgabortgraft(ui, repo):
3920 def hgabortgraft(ui, repo):
3911 """ abort logic for aborting graft using 'hg abort'"""
3921 """ abort logic for aborting graft using 'hg abort'"""
3912 with repo.wlock():
3922 with repo.wlock():
3913 graftstate = statemod.cmdstate(repo, b'graftstate')
3923 graftstate = statemod.cmdstate(repo, b'graftstate')
3914 return abortgraft(ui, repo, graftstate)
3924 return abortgraft(ui, repo, graftstate)
General Comments 0
You need to be logged in to leave comments. Login now