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