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