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