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