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