##// END OF EJS Templates
revert: use an exact matcher in interactive diff selection (issue5789)...
Denis Laxalde -
r36212:39b3aab6 default
parent child Browse files
Show More
@@ -1,3159 +1,3158
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 errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import tempfile
13 import tempfile
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
22
23 from . import (
23 from . import (
24 bookmarks,
24 bookmarks,
25 changelog,
25 changelog,
26 copies,
26 copies,
27 crecord as crecordmod,
27 crecord as crecordmod,
28 dirstateguard,
28 dirstateguard,
29 encoding,
29 encoding,
30 error,
30 error,
31 formatter,
31 formatter,
32 logcmdutil,
32 logcmdutil,
33 match as matchmod,
33 match as matchmod,
34 merge as mergemod,
34 merge as mergemod,
35 obsolete,
35 obsolete,
36 patch,
36 patch,
37 pathutil,
37 pathutil,
38 pycompat,
38 pycompat,
39 registrar,
39 registrar,
40 revlog,
40 revlog,
41 rewriteutil,
41 rewriteutil,
42 scmutil,
42 scmutil,
43 smartset,
43 smartset,
44 subrepoutil,
44 subrepoutil,
45 templater,
45 templater,
46 util,
46 util,
47 vfs as vfsmod,
47 vfs as vfsmod,
48 )
48 )
49 stringio = util.stringio
49 stringio = util.stringio
50
50
51 # templates of common command options
51 # templates of common command options
52
52
53 dryrunopts = [
53 dryrunopts = [
54 ('n', 'dry-run', None,
54 ('n', 'dry-run', None,
55 _('do not perform actions, just print output')),
55 _('do not perform actions, just print output')),
56 ]
56 ]
57
57
58 remoteopts = [
58 remoteopts = [
59 ('e', 'ssh', '',
59 ('e', 'ssh', '',
60 _('specify ssh command to use'), _('CMD')),
60 _('specify ssh command to use'), _('CMD')),
61 ('', 'remotecmd', '',
61 ('', 'remotecmd', '',
62 _('specify hg command to run on the remote side'), _('CMD')),
62 _('specify hg command to run on the remote side'), _('CMD')),
63 ('', 'insecure', None,
63 ('', 'insecure', None,
64 _('do not verify server certificate (ignoring web.cacerts config)')),
64 _('do not verify server certificate (ignoring web.cacerts config)')),
65 ]
65 ]
66
66
67 walkopts = [
67 walkopts = [
68 ('I', 'include', [],
68 ('I', 'include', [],
69 _('include names matching the given patterns'), _('PATTERN')),
69 _('include names matching the given patterns'), _('PATTERN')),
70 ('X', 'exclude', [],
70 ('X', 'exclude', [],
71 _('exclude names matching the given patterns'), _('PATTERN')),
71 _('exclude names matching the given patterns'), _('PATTERN')),
72 ]
72 ]
73
73
74 commitopts = [
74 commitopts = [
75 ('m', 'message', '',
75 ('m', 'message', '',
76 _('use text as commit message'), _('TEXT')),
76 _('use text as commit message'), _('TEXT')),
77 ('l', 'logfile', '',
77 ('l', 'logfile', '',
78 _('read commit message from file'), _('FILE')),
78 _('read commit message from file'), _('FILE')),
79 ]
79 ]
80
80
81 commitopts2 = [
81 commitopts2 = [
82 ('d', 'date', '',
82 ('d', 'date', '',
83 _('record the specified date as commit date'), _('DATE')),
83 _('record the specified date as commit date'), _('DATE')),
84 ('u', 'user', '',
84 ('u', 'user', '',
85 _('record the specified user as committer'), _('USER')),
85 _('record the specified user as committer'), _('USER')),
86 ]
86 ]
87
87
88 # hidden for now
88 # hidden for now
89 formatteropts = [
89 formatteropts = [
90 ('T', 'template', '',
90 ('T', 'template', '',
91 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
91 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
92 ]
92 ]
93
93
94 templateopts = [
94 templateopts = [
95 ('', 'style', '',
95 ('', 'style', '',
96 _('display using template map file (DEPRECATED)'), _('STYLE')),
96 _('display using template map file (DEPRECATED)'), _('STYLE')),
97 ('T', 'template', '',
97 ('T', 'template', '',
98 _('display with template'), _('TEMPLATE')),
98 _('display with template'), _('TEMPLATE')),
99 ]
99 ]
100
100
101 logopts = [
101 logopts = [
102 ('p', 'patch', None, _('show patch')),
102 ('p', 'patch', None, _('show patch')),
103 ('g', 'git', None, _('use git extended diff format')),
103 ('g', 'git', None, _('use git extended diff format')),
104 ('l', 'limit', '',
104 ('l', 'limit', '',
105 _('limit number of changes displayed'), _('NUM')),
105 _('limit number of changes displayed'), _('NUM')),
106 ('M', 'no-merges', None, _('do not show merges')),
106 ('M', 'no-merges', None, _('do not show merges')),
107 ('', 'stat', None, _('output diffstat-style summary of changes')),
107 ('', 'stat', None, _('output diffstat-style summary of changes')),
108 ('G', 'graph', None, _("show the revision DAG")),
108 ('G', 'graph', None, _("show the revision DAG")),
109 ] + templateopts
109 ] + templateopts
110
110
111 diffopts = [
111 diffopts = [
112 ('a', 'text', None, _('treat all files as text')),
112 ('a', 'text', None, _('treat all files as text')),
113 ('g', 'git', None, _('use git extended diff format')),
113 ('g', 'git', None, _('use git extended diff format')),
114 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
114 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
115 ('', 'nodates', None, _('omit dates from diff headers'))
115 ('', 'nodates', None, _('omit dates from diff headers'))
116 ]
116 ]
117
117
118 diffwsopts = [
118 diffwsopts = [
119 ('w', 'ignore-all-space', None,
119 ('w', 'ignore-all-space', None,
120 _('ignore white space when comparing lines')),
120 _('ignore white space when comparing lines')),
121 ('b', 'ignore-space-change', None,
121 ('b', 'ignore-space-change', None,
122 _('ignore changes in the amount of white space')),
122 _('ignore changes in the amount of white space')),
123 ('B', 'ignore-blank-lines', None,
123 ('B', 'ignore-blank-lines', None,
124 _('ignore changes whose lines are all blank')),
124 _('ignore changes whose lines are all blank')),
125 ('Z', 'ignore-space-at-eol', None,
125 ('Z', 'ignore-space-at-eol', None,
126 _('ignore changes in whitespace at EOL')),
126 _('ignore changes in whitespace at EOL')),
127 ]
127 ]
128
128
129 diffopts2 = [
129 diffopts2 = [
130 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
130 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
131 ('p', 'show-function', None, _('show which function each change is in')),
131 ('p', 'show-function', None, _('show which function each change is in')),
132 ('', 'reverse', None, _('produce a diff that undoes the changes')),
132 ('', 'reverse', None, _('produce a diff that undoes the changes')),
133 ] + diffwsopts + [
133 ] + diffwsopts + [
134 ('U', 'unified', '',
134 ('U', 'unified', '',
135 _('number of lines of context to show'), _('NUM')),
135 _('number of lines of context to show'), _('NUM')),
136 ('', 'stat', None, _('output diffstat-style summary of changes')),
136 ('', 'stat', None, _('output diffstat-style summary of changes')),
137 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
137 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
138 ]
138 ]
139
139
140 mergetoolopts = [
140 mergetoolopts = [
141 ('t', 'tool', '', _('specify merge tool')),
141 ('t', 'tool', '', _('specify merge tool')),
142 ]
142 ]
143
143
144 similarityopts = [
144 similarityopts = [
145 ('s', 'similarity', '',
145 ('s', 'similarity', '',
146 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
146 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
147 ]
147 ]
148
148
149 subrepoopts = [
149 subrepoopts = [
150 ('S', 'subrepos', None,
150 ('S', 'subrepos', None,
151 _('recurse into subrepositories'))
151 _('recurse into subrepositories'))
152 ]
152 ]
153
153
154 debugrevlogopts = [
154 debugrevlogopts = [
155 ('c', 'changelog', False, _('open changelog')),
155 ('c', 'changelog', False, _('open changelog')),
156 ('m', 'manifest', False, _('open manifest')),
156 ('m', 'manifest', False, _('open manifest')),
157 ('', 'dir', '', _('open directory manifest')),
157 ('', 'dir', '', _('open directory manifest')),
158 ]
158 ]
159
159
160 # special string such that everything below this line will be ingored in the
160 # special string such that everything below this line will be ingored in the
161 # editor text
161 # editor text
162 _linebelow = "^HG: ------------------------ >8 ------------------------$"
162 _linebelow = "^HG: ------------------------ >8 ------------------------$"
163
163
164 def ishunk(x):
164 def ishunk(x):
165 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
165 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
166 return isinstance(x, hunkclasses)
166 return isinstance(x, hunkclasses)
167
167
168 def newandmodified(chunks, originalchunks):
168 def newandmodified(chunks, originalchunks):
169 newlyaddedandmodifiedfiles = set()
169 newlyaddedandmodifiedfiles = set()
170 for chunk in chunks:
170 for chunk in chunks:
171 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
171 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
172 originalchunks:
172 originalchunks:
173 newlyaddedandmodifiedfiles.add(chunk.header.filename())
173 newlyaddedandmodifiedfiles.add(chunk.header.filename())
174 return newlyaddedandmodifiedfiles
174 return newlyaddedandmodifiedfiles
175
175
176 def parsealiases(cmd):
176 def parsealiases(cmd):
177 return cmd.lstrip("^").split("|")
177 return cmd.lstrip("^").split("|")
178
178
179 def setupwrapcolorwrite(ui):
179 def setupwrapcolorwrite(ui):
180 # wrap ui.write so diff output can be labeled/colorized
180 # wrap ui.write so diff output can be labeled/colorized
181 def wrapwrite(orig, *args, **kw):
181 def wrapwrite(orig, *args, **kw):
182 label = kw.pop(r'label', '')
182 label = kw.pop(r'label', '')
183 for chunk, l in patch.difflabel(lambda: args):
183 for chunk, l in patch.difflabel(lambda: args):
184 orig(chunk, label=label + l)
184 orig(chunk, label=label + l)
185
185
186 oldwrite = ui.write
186 oldwrite = ui.write
187 def wrap(*args, **kwargs):
187 def wrap(*args, **kwargs):
188 return wrapwrite(oldwrite, *args, **kwargs)
188 return wrapwrite(oldwrite, *args, **kwargs)
189 setattr(ui, 'write', wrap)
189 setattr(ui, 'write', wrap)
190 return oldwrite
190 return oldwrite
191
191
192 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
192 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
193 if usecurses:
193 if usecurses:
194 if testfile:
194 if testfile:
195 recordfn = crecordmod.testdecorator(testfile,
195 recordfn = crecordmod.testdecorator(testfile,
196 crecordmod.testchunkselector)
196 crecordmod.testchunkselector)
197 else:
197 else:
198 recordfn = crecordmod.chunkselector
198 recordfn = crecordmod.chunkselector
199
199
200 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
200 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
201
201
202 else:
202 else:
203 return patch.filterpatch(ui, originalhunks, operation)
203 return patch.filterpatch(ui, originalhunks, operation)
204
204
205 def recordfilter(ui, originalhunks, operation=None):
205 def recordfilter(ui, originalhunks, operation=None):
206 """ Prompts the user to filter the originalhunks and return a list of
206 """ Prompts the user to filter the originalhunks and return a list of
207 selected hunks.
207 selected hunks.
208 *operation* is used for to build ui messages to indicate the user what
208 *operation* is used for to build ui messages to indicate the user what
209 kind of filtering they are doing: reverting, committing, shelving, etc.
209 kind of filtering they are doing: reverting, committing, shelving, etc.
210 (see patch.filterpatch).
210 (see patch.filterpatch).
211 """
211 """
212 usecurses = crecordmod.checkcurses(ui)
212 usecurses = crecordmod.checkcurses(ui)
213 testfile = ui.config('experimental', 'crecordtest')
213 testfile = ui.config('experimental', 'crecordtest')
214 oldwrite = setupwrapcolorwrite(ui)
214 oldwrite = setupwrapcolorwrite(ui)
215 try:
215 try:
216 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
216 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
217 testfile, operation)
217 testfile, operation)
218 finally:
218 finally:
219 ui.write = oldwrite
219 ui.write = oldwrite
220 return newchunks, newopts
220 return newchunks, newopts
221
221
222 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
222 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
223 filterfn, *pats, **opts):
223 filterfn, *pats, **opts):
224 opts = pycompat.byteskwargs(opts)
224 opts = pycompat.byteskwargs(opts)
225 if not ui.interactive():
225 if not ui.interactive():
226 if cmdsuggest:
226 if cmdsuggest:
227 msg = _('running non-interactively, use %s instead') % cmdsuggest
227 msg = _('running non-interactively, use %s instead') % cmdsuggest
228 else:
228 else:
229 msg = _('running non-interactively')
229 msg = _('running non-interactively')
230 raise error.Abort(msg)
230 raise error.Abort(msg)
231
231
232 # make sure username is set before going interactive
232 # make sure username is set before going interactive
233 if not opts.get('user'):
233 if not opts.get('user'):
234 ui.username() # raise exception, username not provided
234 ui.username() # raise exception, username not provided
235
235
236 def recordfunc(ui, repo, message, match, opts):
236 def recordfunc(ui, repo, message, match, opts):
237 """This is generic record driver.
237 """This is generic record driver.
238
238
239 Its job is to interactively filter local changes, and
239 Its job is to interactively filter local changes, and
240 accordingly prepare working directory into a state in which the
240 accordingly prepare working directory into a state in which the
241 job can be delegated to a non-interactive commit command such as
241 job can be delegated to a non-interactive commit command such as
242 'commit' or 'qrefresh'.
242 'commit' or 'qrefresh'.
243
243
244 After the actual job is done by non-interactive command, the
244 After the actual job is done by non-interactive command, the
245 working directory is restored to its original state.
245 working directory is restored to its original state.
246
246
247 In the end we'll record interesting changes, and everything else
247 In the end we'll record interesting changes, and everything else
248 will be left in place, so the user can continue working.
248 will be left in place, so the user can continue working.
249 """
249 """
250
250
251 checkunfinished(repo, commit=True)
251 checkunfinished(repo, commit=True)
252 wctx = repo[None]
252 wctx = repo[None]
253 merge = len(wctx.parents()) > 1
253 merge = len(wctx.parents()) > 1
254 if merge:
254 if merge:
255 raise error.Abort(_('cannot partially commit a merge '
255 raise error.Abort(_('cannot partially commit a merge '
256 '(use "hg commit" instead)'))
256 '(use "hg commit" instead)'))
257
257
258 def fail(f, msg):
258 def fail(f, msg):
259 raise error.Abort('%s: %s' % (f, msg))
259 raise error.Abort('%s: %s' % (f, msg))
260
260
261 force = opts.get('force')
261 force = opts.get('force')
262 if not force:
262 if not force:
263 vdirs = []
263 vdirs = []
264 match.explicitdir = vdirs.append
264 match.explicitdir = vdirs.append
265 match.bad = fail
265 match.bad = fail
266
266
267 status = repo.status(match=match)
267 status = repo.status(match=match)
268 if not force:
268 if not force:
269 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
269 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
270 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
270 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
271 diffopts.nodates = True
271 diffopts.nodates = True
272 diffopts.git = True
272 diffopts.git = True
273 diffopts.showfunc = True
273 diffopts.showfunc = True
274 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
274 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
275 originalchunks = patch.parsepatch(originaldiff)
275 originalchunks = patch.parsepatch(originaldiff)
276
276
277 # 1. filter patch, since we are intending to apply subset of it
277 # 1. filter patch, since we are intending to apply subset of it
278 try:
278 try:
279 chunks, newopts = filterfn(ui, originalchunks)
279 chunks, newopts = filterfn(ui, originalchunks)
280 except error.PatchError as err:
280 except error.PatchError as err:
281 raise error.Abort(_('error parsing patch: %s') % err)
281 raise error.Abort(_('error parsing patch: %s') % err)
282 opts.update(newopts)
282 opts.update(newopts)
283
283
284 # We need to keep a backup of files that have been newly added and
284 # We need to keep a backup of files that have been newly added and
285 # modified during the recording process because there is a previous
285 # modified during the recording process because there is a previous
286 # version without the edit in the workdir
286 # version without the edit in the workdir
287 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
287 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
288 contenders = set()
288 contenders = set()
289 for h in chunks:
289 for h in chunks:
290 try:
290 try:
291 contenders.update(set(h.files()))
291 contenders.update(set(h.files()))
292 except AttributeError:
292 except AttributeError:
293 pass
293 pass
294
294
295 changed = status.modified + status.added + status.removed
295 changed = status.modified + status.added + status.removed
296 newfiles = [f for f in changed if f in contenders]
296 newfiles = [f for f in changed if f in contenders]
297 if not newfiles:
297 if not newfiles:
298 ui.status(_('no changes to record\n'))
298 ui.status(_('no changes to record\n'))
299 return 0
299 return 0
300
300
301 modified = set(status.modified)
301 modified = set(status.modified)
302
302
303 # 2. backup changed files, so we can restore them in the end
303 # 2. backup changed files, so we can restore them in the end
304
304
305 if backupall:
305 if backupall:
306 tobackup = changed
306 tobackup = changed
307 else:
307 else:
308 tobackup = [f for f in newfiles if f in modified or f in \
308 tobackup = [f for f in newfiles if f in modified or f in \
309 newlyaddedandmodifiedfiles]
309 newlyaddedandmodifiedfiles]
310 backups = {}
310 backups = {}
311 if tobackup:
311 if tobackup:
312 backupdir = repo.vfs.join('record-backups')
312 backupdir = repo.vfs.join('record-backups')
313 try:
313 try:
314 os.mkdir(backupdir)
314 os.mkdir(backupdir)
315 except OSError as err:
315 except OSError as err:
316 if err.errno != errno.EEXIST:
316 if err.errno != errno.EEXIST:
317 raise
317 raise
318 try:
318 try:
319 # backup continues
319 # backup continues
320 for f in tobackup:
320 for f in tobackup:
321 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
321 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
322 dir=backupdir)
322 dir=backupdir)
323 os.close(fd)
323 os.close(fd)
324 ui.debug('backup %r as %r\n' % (f, tmpname))
324 ui.debug('backup %r as %r\n' % (f, tmpname))
325 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
325 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
326 backups[f] = tmpname
326 backups[f] = tmpname
327
327
328 fp = stringio()
328 fp = stringio()
329 for c in chunks:
329 for c in chunks:
330 fname = c.filename()
330 fname = c.filename()
331 if fname in backups:
331 if fname in backups:
332 c.write(fp)
332 c.write(fp)
333 dopatch = fp.tell()
333 dopatch = fp.tell()
334 fp.seek(0)
334 fp.seek(0)
335
335
336 # 2.5 optionally review / modify patch in text editor
336 # 2.5 optionally review / modify patch in text editor
337 if opts.get('review', False):
337 if opts.get('review', False):
338 patchtext = (crecordmod.diffhelptext
338 patchtext = (crecordmod.diffhelptext
339 + crecordmod.patchhelptext
339 + crecordmod.patchhelptext
340 + fp.read())
340 + fp.read())
341 reviewedpatch = ui.edit(patchtext, "",
341 reviewedpatch = ui.edit(patchtext, "",
342 action="diff",
342 action="diff",
343 repopath=repo.path)
343 repopath=repo.path)
344 fp.truncate(0)
344 fp.truncate(0)
345 fp.write(reviewedpatch)
345 fp.write(reviewedpatch)
346 fp.seek(0)
346 fp.seek(0)
347
347
348 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
348 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
349 # 3a. apply filtered patch to clean repo (clean)
349 # 3a. apply filtered patch to clean repo (clean)
350 if backups:
350 if backups:
351 # Equivalent to hg.revert
351 # Equivalent to hg.revert
352 m = scmutil.matchfiles(repo, backups.keys())
352 m = scmutil.matchfiles(repo, backups.keys())
353 mergemod.update(repo, repo.dirstate.p1(),
353 mergemod.update(repo, repo.dirstate.p1(),
354 False, True, matcher=m)
354 False, True, matcher=m)
355
355
356 # 3b. (apply)
356 # 3b. (apply)
357 if dopatch:
357 if dopatch:
358 try:
358 try:
359 ui.debug('applying patch\n')
359 ui.debug('applying patch\n')
360 ui.debug(fp.getvalue())
360 ui.debug(fp.getvalue())
361 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
361 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
362 except error.PatchError as err:
362 except error.PatchError as err:
363 raise error.Abort(str(err))
363 raise error.Abort(str(err))
364 del fp
364 del fp
365
365
366 # 4. We prepared working directory according to filtered
366 # 4. We prepared working directory according to filtered
367 # patch. Now is the time to delegate the job to
367 # patch. Now is the time to delegate the job to
368 # commit/qrefresh or the like!
368 # commit/qrefresh or the like!
369
369
370 # Make all of the pathnames absolute.
370 # Make all of the pathnames absolute.
371 newfiles = [repo.wjoin(nf) for nf in newfiles]
371 newfiles = [repo.wjoin(nf) for nf in newfiles]
372 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
372 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
373 finally:
373 finally:
374 # 5. finally restore backed-up files
374 # 5. finally restore backed-up files
375 try:
375 try:
376 dirstate = repo.dirstate
376 dirstate = repo.dirstate
377 for realname, tmpname in backups.iteritems():
377 for realname, tmpname in backups.iteritems():
378 ui.debug('restoring %r to %r\n' % (tmpname, realname))
378 ui.debug('restoring %r to %r\n' % (tmpname, realname))
379
379
380 if dirstate[realname] == 'n':
380 if dirstate[realname] == 'n':
381 # without normallookup, restoring timestamp
381 # without normallookup, restoring timestamp
382 # may cause partially committed files
382 # may cause partially committed files
383 # to be treated as unmodified
383 # to be treated as unmodified
384 dirstate.normallookup(realname)
384 dirstate.normallookup(realname)
385
385
386 # copystat=True here and above are a hack to trick any
386 # copystat=True here and above are a hack to trick any
387 # editors that have f open that we haven't modified them.
387 # editors that have f open that we haven't modified them.
388 #
388 #
389 # Also note that this racy as an editor could notice the
389 # Also note that this racy as an editor could notice the
390 # file's mtime before we've finished writing it.
390 # file's mtime before we've finished writing it.
391 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
391 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
392 os.unlink(tmpname)
392 os.unlink(tmpname)
393 if tobackup:
393 if tobackup:
394 os.rmdir(backupdir)
394 os.rmdir(backupdir)
395 except OSError:
395 except OSError:
396 pass
396 pass
397
397
398 def recordinwlock(ui, repo, message, match, opts):
398 def recordinwlock(ui, repo, message, match, opts):
399 with repo.wlock():
399 with repo.wlock():
400 return recordfunc(ui, repo, message, match, opts)
400 return recordfunc(ui, repo, message, match, opts)
401
401
402 return commit(ui, repo, recordinwlock, pats, opts)
402 return commit(ui, repo, recordinwlock, pats, opts)
403
403
404 class dirnode(object):
404 class dirnode(object):
405 """
405 """
406 Represent a directory in user working copy with information required for
406 Represent a directory in user working copy with information required for
407 the purpose of tersing its status.
407 the purpose of tersing its status.
408
408
409 path is the path to the directory
409 path is the path to the directory
410
410
411 statuses is a set of statuses of all files in this directory (this includes
411 statuses is a set of statuses of all files in this directory (this includes
412 all the files in all the subdirectories too)
412 all the files in all the subdirectories too)
413
413
414 files is a list of files which are direct child of this directory
414 files is a list of files which are direct child of this directory
415
415
416 subdirs is a dictionary of sub-directory name as the key and it's own
416 subdirs is a dictionary of sub-directory name as the key and it's own
417 dirnode object as the value
417 dirnode object as the value
418 """
418 """
419
419
420 def __init__(self, dirpath):
420 def __init__(self, dirpath):
421 self.path = dirpath
421 self.path = dirpath
422 self.statuses = set([])
422 self.statuses = set([])
423 self.files = []
423 self.files = []
424 self.subdirs = {}
424 self.subdirs = {}
425
425
426 def _addfileindir(self, filename, status):
426 def _addfileindir(self, filename, status):
427 """Add a file in this directory as a direct child."""
427 """Add a file in this directory as a direct child."""
428 self.files.append((filename, status))
428 self.files.append((filename, status))
429
429
430 def addfile(self, filename, status):
430 def addfile(self, filename, status):
431 """
431 """
432 Add a file to this directory or to its direct parent directory.
432 Add a file to this directory or to its direct parent directory.
433
433
434 If the file is not direct child of this directory, we traverse to the
434 If the file is not direct child of this directory, we traverse to the
435 directory of which this file is a direct child of and add the file
435 directory of which this file is a direct child of and add the file
436 there.
436 there.
437 """
437 """
438
438
439 # the filename contains a path separator, it means it's not the direct
439 # the filename contains a path separator, it means it's not the direct
440 # child of this directory
440 # child of this directory
441 if '/' in filename:
441 if '/' in filename:
442 subdir, filep = filename.split('/', 1)
442 subdir, filep = filename.split('/', 1)
443
443
444 # does the dirnode object for subdir exists
444 # does the dirnode object for subdir exists
445 if subdir not in self.subdirs:
445 if subdir not in self.subdirs:
446 subdirpath = os.path.join(self.path, subdir)
446 subdirpath = os.path.join(self.path, subdir)
447 self.subdirs[subdir] = dirnode(subdirpath)
447 self.subdirs[subdir] = dirnode(subdirpath)
448
448
449 # try adding the file in subdir
449 # try adding the file in subdir
450 self.subdirs[subdir].addfile(filep, status)
450 self.subdirs[subdir].addfile(filep, status)
451
451
452 else:
452 else:
453 self._addfileindir(filename, status)
453 self._addfileindir(filename, status)
454
454
455 if status not in self.statuses:
455 if status not in self.statuses:
456 self.statuses.add(status)
456 self.statuses.add(status)
457
457
458 def iterfilepaths(self):
458 def iterfilepaths(self):
459 """Yield (status, path) for files directly under this directory."""
459 """Yield (status, path) for files directly under this directory."""
460 for f, st in self.files:
460 for f, st in self.files:
461 yield st, os.path.join(self.path, f)
461 yield st, os.path.join(self.path, f)
462
462
463 def tersewalk(self, terseargs):
463 def tersewalk(self, terseargs):
464 """
464 """
465 Yield (status, path) obtained by processing the status of this
465 Yield (status, path) obtained by processing the status of this
466 dirnode.
466 dirnode.
467
467
468 terseargs is the string of arguments passed by the user with `--terse`
468 terseargs is the string of arguments passed by the user with `--terse`
469 flag.
469 flag.
470
470
471 Following are the cases which can happen:
471 Following are the cases which can happen:
472
472
473 1) All the files in the directory (including all the files in its
473 1) All the files in the directory (including all the files in its
474 subdirectories) share the same status and the user has asked us to terse
474 subdirectories) share the same status and the user has asked us to terse
475 that status. -> yield (status, dirpath)
475 that status. -> yield (status, dirpath)
476
476
477 2) Otherwise, we do following:
477 2) Otherwise, we do following:
478
478
479 a) Yield (status, filepath) for all the files which are in this
479 a) Yield (status, filepath) for all the files which are in this
480 directory (only the ones in this directory, not the subdirs)
480 directory (only the ones in this directory, not the subdirs)
481
481
482 b) Recurse the function on all the subdirectories of this
482 b) Recurse the function on all the subdirectories of this
483 directory
483 directory
484 """
484 """
485
485
486 if len(self.statuses) == 1:
486 if len(self.statuses) == 1:
487 onlyst = self.statuses.pop()
487 onlyst = self.statuses.pop()
488
488
489 # Making sure we terse only when the status abbreviation is
489 # Making sure we terse only when the status abbreviation is
490 # passed as terse argument
490 # passed as terse argument
491 if onlyst in terseargs:
491 if onlyst in terseargs:
492 yield onlyst, self.path + pycompat.ossep
492 yield onlyst, self.path + pycompat.ossep
493 return
493 return
494
494
495 # add the files to status list
495 # add the files to status list
496 for st, fpath in self.iterfilepaths():
496 for st, fpath in self.iterfilepaths():
497 yield st, fpath
497 yield st, fpath
498
498
499 #recurse on the subdirs
499 #recurse on the subdirs
500 for dirobj in self.subdirs.values():
500 for dirobj in self.subdirs.values():
501 for st, fpath in dirobj.tersewalk(terseargs):
501 for st, fpath in dirobj.tersewalk(terseargs):
502 yield st, fpath
502 yield st, fpath
503
503
504 def tersedir(statuslist, terseargs):
504 def tersedir(statuslist, terseargs):
505 """
505 """
506 Terse the status if all the files in a directory shares the same status.
506 Terse the status if all the files in a directory shares the same status.
507
507
508 statuslist is scmutil.status() object which contains a list of files for
508 statuslist is scmutil.status() object which contains a list of files for
509 each status.
509 each status.
510 terseargs is string which is passed by the user as the argument to `--terse`
510 terseargs is string which is passed by the user as the argument to `--terse`
511 flag.
511 flag.
512
512
513 The function makes a tree of objects of dirnode class, and at each node it
513 The function makes a tree of objects of dirnode class, and at each node it
514 stores the information required to know whether we can terse a certain
514 stores the information required to know whether we can terse a certain
515 directory or not.
515 directory or not.
516 """
516 """
517 # the order matters here as that is used to produce final list
517 # the order matters here as that is used to produce final list
518 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
518 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
519
519
520 # checking the argument validity
520 # checking the argument validity
521 for s in pycompat.bytestr(terseargs):
521 for s in pycompat.bytestr(terseargs):
522 if s not in allst:
522 if s not in allst:
523 raise error.Abort(_("'%s' not recognized") % s)
523 raise error.Abort(_("'%s' not recognized") % s)
524
524
525 # creating a dirnode object for the root of the repo
525 # creating a dirnode object for the root of the repo
526 rootobj = dirnode('')
526 rootobj = dirnode('')
527 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
527 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
528 'ignored', 'removed')
528 'ignored', 'removed')
529
529
530 tersedict = {}
530 tersedict = {}
531 for attrname in pstatus:
531 for attrname in pstatus:
532 statuschar = attrname[0:1]
532 statuschar = attrname[0:1]
533 for f in getattr(statuslist, attrname):
533 for f in getattr(statuslist, attrname):
534 rootobj.addfile(f, statuschar)
534 rootobj.addfile(f, statuschar)
535 tersedict[statuschar] = []
535 tersedict[statuschar] = []
536
536
537 # we won't be tersing the root dir, so add files in it
537 # we won't be tersing the root dir, so add files in it
538 for st, fpath in rootobj.iterfilepaths():
538 for st, fpath in rootobj.iterfilepaths():
539 tersedict[st].append(fpath)
539 tersedict[st].append(fpath)
540
540
541 # process each sub-directory and build tersedict
541 # process each sub-directory and build tersedict
542 for subdir in rootobj.subdirs.values():
542 for subdir in rootobj.subdirs.values():
543 for st, f in subdir.tersewalk(terseargs):
543 for st, f in subdir.tersewalk(terseargs):
544 tersedict[st].append(f)
544 tersedict[st].append(f)
545
545
546 tersedlist = []
546 tersedlist = []
547 for st in allst:
547 for st in allst:
548 tersedict[st].sort()
548 tersedict[st].sort()
549 tersedlist.append(tersedict[st])
549 tersedlist.append(tersedict[st])
550
550
551 return tersedlist
551 return tersedlist
552
552
553 def _commentlines(raw):
553 def _commentlines(raw):
554 '''Surround lineswith a comment char and a new line'''
554 '''Surround lineswith a comment char and a new line'''
555 lines = raw.splitlines()
555 lines = raw.splitlines()
556 commentedlines = ['# %s' % line for line in lines]
556 commentedlines = ['# %s' % line for line in lines]
557 return '\n'.join(commentedlines) + '\n'
557 return '\n'.join(commentedlines) + '\n'
558
558
559 def _conflictsmsg(repo):
559 def _conflictsmsg(repo):
560 mergestate = mergemod.mergestate.read(repo)
560 mergestate = mergemod.mergestate.read(repo)
561 if not mergestate.active():
561 if not mergestate.active():
562 return
562 return
563
563
564 m = scmutil.match(repo[None])
564 m = scmutil.match(repo[None])
565 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
565 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
566 if unresolvedlist:
566 if unresolvedlist:
567 mergeliststr = '\n'.join(
567 mergeliststr = '\n'.join(
568 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
568 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
569 for path in unresolvedlist])
569 for path in unresolvedlist])
570 msg = _('''Unresolved merge conflicts:
570 msg = _('''Unresolved merge conflicts:
571
571
572 %s
572 %s
573
573
574 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
574 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
575 else:
575 else:
576 msg = _('No unresolved merge conflicts.')
576 msg = _('No unresolved merge conflicts.')
577
577
578 return _commentlines(msg)
578 return _commentlines(msg)
579
579
580 def _helpmessage(continuecmd, abortcmd):
580 def _helpmessage(continuecmd, abortcmd):
581 msg = _('To continue: %s\n'
581 msg = _('To continue: %s\n'
582 'To abort: %s') % (continuecmd, abortcmd)
582 'To abort: %s') % (continuecmd, abortcmd)
583 return _commentlines(msg)
583 return _commentlines(msg)
584
584
585 def _rebasemsg():
585 def _rebasemsg():
586 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
586 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
587
587
588 def _histeditmsg():
588 def _histeditmsg():
589 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
589 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
590
590
591 def _unshelvemsg():
591 def _unshelvemsg():
592 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
592 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
593
593
594 def _updatecleanmsg(dest=None):
594 def _updatecleanmsg(dest=None):
595 warning = _('warning: this will discard uncommitted changes')
595 warning = _('warning: this will discard uncommitted changes')
596 return 'hg update --clean %s (%s)' % (dest or '.', warning)
596 return 'hg update --clean %s (%s)' % (dest or '.', warning)
597
597
598 def _graftmsg():
598 def _graftmsg():
599 # tweakdefaults requires `update` to have a rev hence the `.`
599 # tweakdefaults requires `update` to have a rev hence the `.`
600 return _helpmessage('hg graft --continue', _updatecleanmsg())
600 return _helpmessage('hg graft --continue', _updatecleanmsg())
601
601
602 def _mergemsg():
602 def _mergemsg():
603 # tweakdefaults requires `update` to have a rev hence the `.`
603 # tweakdefaults requires `update` to have a rev hence the `.`
604 return _helpmessage('hg commit', _updatecleanmsg())
604 return _helpmessage('hg commit', _updatecleanmsg())
605
605
606 def _bisectmsg():
606 def _bisectmsg():
607 msg = _('To mark the changeset good: hg bisect --good\n'
607 msg = _('To mark the changeset good: hg bisect --good\n'
608 'To mark the changeset bad: hg bisect --bad\n'
608 'To mark the changeset bad: hg bisect --bad\n'
609 'To abort: hg bisect --reset\n')
609 'To abort: hg bisect --reset\n')
610 return _commentlines(msg)
610 return _commentlines(msg)
611
611
612 def fileexistspredicate(filename):
612 def fileexistspredicate(filename):
613 return lambda repo: repo.vfs.exists(filename)
613 return lambda repo: repo.vfs.exists(filename)
614
614
615 def _mergepredicate(repo):
615 def _mergepredicate(repo):
616 return len(repo[None].parents()) > 1
616 return len(repo[None].parents()) > 1
617
617
618 STATES = (
618 STATES = (
619 # (state, predicate to detect states, helpful message function)
619 # (state, predicate to detect states, helpful message function)
620 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
620 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
621 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
621 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
622 ('graft', fileexistspredicate('graftstate'), _graftmsg),
622 ('graft', fileexistspredicate('graftstate'), _graftmsg),
623 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
623 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
624 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
624 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
625 # The merge state is part of a list that will be iterated over.
625 # The merge state is part of a list that will be iterated over.
626 # They need to be last because some of the other unfinished states may also
626 # They need to be last because some of the other unfinished states may also
627 # be in a merge or update state (eg. rebase, histedit, graft, etc).
627 # be in a merge or update state (eg. rebase, histedit, graft, etc).
628 # We want those to have priority.
628 # We want those to have priority.
629 ('merge', _mergepredicate, _mergemsg),
629 ('merge', _mergepredicate, _mergemsg),
630 )
630 )
631
631
632 def _getrepostate(repo):
632 def _getrepostate(repo):
633 # experimental config: commands.status.skipstates
633 # experimental config: commands.status.skipstates
634 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
634 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
635 for state, statedetectionpredicate, msgfn in STATES:
635 for state, statedetectionpredicate, msgfn in STATES:
636 if state in skip:
636 if state in skip:
637 continue
637 continue
638 if statedetectionpredicate(repo):
638 if statedetectionpredicate(repo):
639 return (state, statedetectionpredicate, msgfn)
639 return (state, statedetectionpredicate, msgfn)
640
640
641 def morestatus(repo, fm):
641 def morestatus(repo, fm):
642 statetuple = _getrepostate(repo)
642 statetuple = _getrepostate(repo)
643 label = 'status.morestatus'
643 label = 'status.morestatus'
644 if statetuple:
644 if statetuple:
645 fm.startitem()
645 fm.startitem()
646 state, statedetectionpredicate, helpfulmsg = statetuple
646 state, statedetectionpredicate, helpfulmsg = statetuple
647 statemsg = _('The repository is in an unfinished *%s* state.') % state
647 statemsg = _('The repository is in an unfinished *%s* state.') % state
648 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
648 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
649 conmsg = _conflictsmsg(repo)
649 conmsg = _conflictsmsg(repo)
650 if conmsg:
650 if conmsg:
651 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
651 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
652 if helpfulmsg:
652 if helpfulmsg:
653 helpmsg = helpfulmsg()
653 helpmsg = helpfulmsg()
654 fm.write('helpmsg', '%s\n', helpmsg, label=label)
654 fm.write('helpmsg', '%s\n', helpmsg, label=label)
655
655
656 def findpossible(cmd, table, strict=False):
656 def findpossible(cmd, table, strict=False):
657 """
657 """
658 Return cmd -> (aliases, command table entry)
658 Return cmd -> (aliases, command table entry)
659 for each matching command.
659 for each matching command.
660 Return debug commands (or their aliases) only if no normal command matches.
660 Return debug commands (or their aliases) only if no normal command matches.
661 """
661 """
662 choice = {}
662 choice = {}
663 debugchoice = {}
663 debugchoice = {}
664
664
665 if cmd in table:
665 if cmd in table:
666 # short-circuit exact matches, "log" alias beats "^log|history"
666 # short-circuit exact matches, "log" alias beats "^log|history"
667 keys = [cmd]
667 keys = [cmd]
668 else:
668 else:
669 keys = table.keys()
669 keys = table.keys()
670
670
671 allcmds = []
671 allcmds = []
672 for e in keys:
672 for e in keys:
673 aliases = parsealiases(e)
673 aliases = parsealiases(e)
674 allcmds.extend(aliases)
674 allcmds.extend(aliases)
675 found = None
675 found = None
676 if cmd in aliases:
676 if cmd in aliases:
677 found = cmd
677 found = cmd
678 elif not strict:
678 elif not strict:
679 for a in aliases:
679 for a in aliases:
680 if a.startswith(cmd):
680 if a.startswith(cmd):
681 found = a
681 found = a
682 break
682 break
683 if found is not None:
683 if found is not None:
684 if aliases[0].startswith("debug") or found.startswith("debug"):
684 if aliases[0].startswith("debug") or found.startswith("debug"):
685 debugchoice[found] = (aliases, table[e])
685 debugchoice[found] = (aliases, table[e])
686 else:
686 else:
687 choice[found] = (aliases, table[e])
687 choice[found] = (aliases, table[e])
688
688
689 if not choice and debugchoice:
689 if not choice and debugchoice:
690 choice = debugchoice
690 choice = debugchoice
691
691
692 return choice, allcmds
692 return choice, allcmds
693
693
694 def findcmd(cmd, table, strict=True):
694 def findcmd(cmd, table, strict=True):
695 """Return (aliases, command table entry) for command string."""
695 """Return (aliases, command table entry) for command string."""
696 choice, allcmds = findpossible(cmd, table, strict)
696 choice, allcmds = findpossible(cmd, table, strict)
697
697
698 if cmd in choice:
698 if cmd in choice:
699 return choice[cmd]
699 return choice[cmd]
700
700
701 if len(choice) > 1:
701 if len(choice) > 1:
702 clist = sorted(choice)
702 clist = sorted(choice)
703 raise error.AmbiguousCommand(cmd, clist)
703 raise error.AmbiguousCommand(cmd, clist)
704
704
705 if choice:
705 if choice:
706 return list(choice.values())[0]
706 return list(choice.values())[0]
707
707
708 raise error.UnknownCommand(cmd, allcmds)
708 raise error.UnknownCommand(cmd, allcmds)
709
709
710 def changebranch(ui, repo, revs, label):
710 def changebranch(ui, repo, revs, label):
711 """ Change the branch name of given revs to label """
711 """ Change the branch name of given revs to label """
712
712
713 with repo.wlock(), repo.lock(), repo.transaction('branches'):
713 with repo.wlock(), repo.lock(), repo.transaction('branches'):
714 # abort in case of uncommitted merge or dirty wdir
714 # abort in case of uncommitted merge or dirty wdir
715 bailifchanged(repo)
715 bailifchanged(repo)
716 revs = scmutil.revrange(repo, revs)
716 revs = scmutil.revrange(repo, revs)
717 if not revs:
717 if not revs:
718 raise error.Abort("empty revision set")
718 raise error.Abort("empty revision set")
719 roots = repo.revs('roots(%ld)', revs)
719 roots = repo.revs('roots(%ld)', revs)
720 if len(roots) > 1:
720 if len(roots) > 1:
721 raise error.Abort(_("cannot change branch of non-linear revisions"))
721 raise error.Abort(_("cannot change branch of non-linear revisions"))
722 rewriteutil.precheck(repo, revs, 'change branch of')
722 rewriteutil.precheck(repo, revs, 'change branch of')
723
723
724 root = repo[roots.first()]
724 root = repo[roots.first()]
725 if not root.p1().branch() == label and label in repo.branchmap():
725 if not root.p1().branch() == label and label in repo.branchmap():
726 raise error.Abort(_("a branch of the same name already exists"))
726 raise error.Abort(_("a branch of the same name already exists"))
727
727
728 if repo.revs('merge() and %ld', revs):
728 if repo.revs('merge() and %ld', revs):
729 raise error.Abort(_("cannot change branch of a merge commit"))
729 raise error.Abort(_("cannot change branch of a merge commit"))
730 if repo.revs('obsolete() and %ld', revs):
730 if repo.revs('obsolete() and %ld', revs):
731 raise error.Abort(_("cannot change branch of a obsolete changeset"))
731 raise error.Abort(_("cannot change branch of a obsolete changeset"))
732
732
733 # make sure only topological heads
733 # make sure only topological heads
734 if repo.revs('heads(%ld) - head()', revs):
734 if repo.revs('heads(%ld) - head()', revs):
735 raise error.Abort(_("cannot change branch in middle of a stack"))
735 raise error.Abort(_("cannot change branch in middle of a stack"))
736
736
737 replacements = {}
737 replacements = {}
738 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
738 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
739 # mercurial.subrepo -> mercurial.cmdutil
739 # mercurial.subrepo -> mercurial.cmdutil
740 from . import context
740 from . import context
741 for rev in revs:
741 for rev in revs:
742 ctx = repo[rev]
742 ctx = repo[rev]
743 oldbranch = ctx.branch()
743 oldbranch = ctx.branch()
744 # check if ctx has same branch
744 # check if ctx has same branch
745 if oldbranch == label:
745 if oldbranch == label:
746 continue
746 continue
747
747
748 def filectxfn(repo, newctx, path):
748 def filectxfn(repo, newctx, path):
749 try:
749 try:
750 return ctx[path]
750 return ctx[path]
751 except error.ManifestLookupError:
751 except error.ManifestLookupError:
752 return None
752 return None
753
753
754 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
754 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
755 % (hex(ctx.node()), oldbranch, label))
755 % (hex(ctx.node()), oldbranch, label))
756 extra = ctx.extra()
756 extra = ctx.extra()
757 extra['branch_change'] = hex(ctx.node())
757 extra['branch_change'] = hex(ctx.node())
758 # While changing branch of set of linear commits, make sure that
758 # While changing branch of set of linear commits, make sure that
759 # we base our commits on new parent rather than old parent which
759 # we base our commits on new parent rather than old parent which
760 # was obsoleted while changing the branch
760 # was obsoleted while changing the branch
761 p1 = ctx.p1().node()
761 p1 = ctx.p1().node()
762 p2 = ctx.p2().node()
762 p2 = ctx.p2().node()
763 if p1 in replacements:
763 if p1 in replacements:
764 p1 = replacements[p1][0]
764 p1 = replacements[p1][0]
765 if p2 in replacements:
765 if p2 in replacements:
766 p2 = replacements[p2][0]
766 p2 = replacements[p2][0]
767
767
768 mc = context.memctx(repo, (p1, p2),
768 mc = context.memctx(repo, (p1, p2),
769 ctx.description(),
769 ctx.description(),
770 ctx.files(),
770 ctx.files(),
771 filectxfn,
771 filectxfn,
772 user=ctx.user(),
772 user=ctx.user(),
773 date=ctx.date(),
773 date=ctx.date(),
774 extra=extra,
774 extra=extra,
775 branch=label)
775 branch=label)
776
776
777 commitphase = ctx.phase()
777 commitphase = ctx.phase()
778 overrides = {('phases', 'new-commit'): commitphase}
778 overrides = {('phases', 'new-commit'): commitphase}
779 with repo.ui.configoverride(overrides, 'branch-change'):
779 with repo.ui.configoverride(overrides, 'branch-change'):
780 newnode = repo.commitctx(mc)
780 newnode = repo.commitctx(mc)
781
781
782 replacements[ctx.node()] = (newnode,)
782 replacements[ctx.node()] = (newnode,)
783 ui.debug('new node id is %s\n' % hex(newnode))
783 ui.debug('new node id is %s\n' % hex(newnode))
784
784
785 # create obsmarkers and move bookmarks
785 # create obsmarkers and move bookmarks
786 scmutil.cleanupnodes(repo, replacements, 'branch-change')
786 scmutil.cleanupnodes(repo, replacements, 'branch-change')
787
787
788 # move the working copy too
788 # move the working copy too
789 wctx = repo[None]
789 wctx = repo[None]
790 # in-progress merge is a bit too complex for now.
790 # in-progress merge is a bit too complex for now.
791 if len(wctx.parents()) == 1:
791 if len(wctx.parents()) == 1:
792 newid = replacements.get(wctx.p1().node())
792 newid = replacements.get(wctx.p1().node())
793 if newid is not None:
793 if newid is not None:
794 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
794 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
795 # mercurial.cmdutil
795 # mercurial.cmdutil
796 from . import hg
796 from . import hg
797 hg.update(repo, newid[0], quietempty=True)
797 hg.update(repo, newid[0], quietempty=True)
798
798
799 ui.status(_("changed branch on %d changesets\n") % len(replacements))
799 ui.status(_("changed branch on %d changesets\n") % len(replacements))
800
800
801 def findrepo(p):
801 def findrepo(p):
802 while not os.path.isdir(os.path.join(p, ".hg")):
802 while not os.path.isdir(os.path.join(p, ".hg")):
803 oldp, p = p, os.path.dirname(p)
803 oldp, p = p, os.path.dirname(p)
804 if p == oldp:
804 if p == oldp:
805 return None
805 return None
806
806
807 return p
807 return p
808
808
809 def bailifchanged(repo, merge=True, hint=None):
809 def bailifchanged(repo, merge=True, hint=None):
810 """ enforce the precondition that working directory must be clean.
810 """ enforce the precondition that working directory must be clean.
811
811
812 'merge' can be set to false if a pending uncommitted merge should be
812 'merge' can be set to false if a pending uncommitted merge should be
813 ignored (such as when 'update --check' runs).
813 ignored (such as when 'update --check' runs).
814
814
815 'hint' is the usual hint given to Abort exception.
815 'hint' is the usual hint given to Abort exception.
816 """
816 """
817
817
818 if merge and repo.dirstate.p2() != nullid:
818 if merge and repo.dirstate.p2() != nullid:
819 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
819 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
820 modified, added, removed, deleted = repo.status()[:4]
820 modified, added, removed, deleted = repo.status()[:4]
821 if modified or added or removed or deleted:
821 if modified or added or removed or deleted:
822 raise error.Abort(_('uncommitted changes'), hint=hint)
822 raise error.Abort(_('uncommitted changes'), hint=hint)
823 ctx = repo[None]
823 ctx = repo[None]
824 for s in sorted(ctx.substate):
824 for s in sorted(ctx.substate):
825 ctx.sub(s).bailifchanged(hint=hint)
825 ctx.sub(s).bailifchanged(hint=hint)
826
826
827 def logmessage(ui, opts):
827 def logmessage(ui, opts):
828 """ get the log message according to -m and -l option """
828 """ get the log message according to -m and -l option """
829 message = opts.get('message')
829 message = opts.get('message')
830 logfile = opts.get('logfile')
830 logfile = opts.get('logfile')
831
831
832 if message and logfile:
832 if message and logfile:
833 raise error.Abort(_('options --message and --logfile are mutually '
833 raise error.Abort(_('options --message and --logfile are mutually '
834 'exclusive'))
834 'exclusive'))
835 if not message and logfile:
835 if not message and logfile:
836 try:
836 try:
837 if isstdiofilename(logfile):
837 if isstdiofilename(logfile):
838 message = ui.fin.read()
838 message = ui.fin.read()
839 else:
839 else:
840 message = '\n'.join(util.readfile(logfile).splitlines())
840 message = '\n'.join(util.readfile(logfile).splitlines())
841 except IOError as inst:
841 except IOError as inst:
842 raise error.Abort(_("can't read commit message '%s': %s") %
842 raise error.Abort(_("can't read commit message '%s': %s") %
843 (logfile, encoding.strtolocal(inst.strerror)))
843 (logfile, encoding.strtolocal(inst.strerror)))
844 return message
844 return message
845
845
846 def mergeeditform(ctxorbool, baseformname):
846 def mergeeditform(ctxorbool, baseformname):
847 """return appropriate editform name (referencing a committemplate)
847 """return appropriate editform name (referencing a committemplate)
848
848
849 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
849 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
850 merging is committed.
850 merging is committed.
851
851
852 This returns baseformname with '.merge' appended if it is a merge,
852 This returns baseformname with '.merge' appended if it is a merge,
853 otherwise '.normal' is appended.
853 otherwise '.normal' is appended.
854 """
854 """
855 if isinstance(ctxorbool, bool):
855 if isinstance(ctxorbool, bool):
856 if ctxorbool:
856 if ctxorbool:
857 return baseformname + ".merge"
857 return baseformname + ".merge"
858 elif 1 < len(ctxorbool.parents()):
858 elif 1 < len(ctxorbool.parents()):
859 return baseformname + ".merge"
859 return baseformname + ".merge"
860
860
861 return baseformname + ".normal"
861 return baseformname + ".normal"
862
862
863 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
863 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
864 editform='', **opts):
864 editform='', **opts):
865 """get appropriate commit message editor according to '--edit' option
865 """get appropriate commit message editor according to '--edit' option
866
866
867 'finishdesc' is a function to be called with edited commit message
867 'finishdesc' is a function to be called with edited commit message
868 (= 'description' of the new changeset) just after editing, but
868 (= 'description' of the new changeset) just after editing, but
869 before checking empty-ness. It should return actual text to be
869 before checking empty-ness. It should return actual text to be
870 stored into history. This allows to change description before
870 stored into history. This allows to change description before
871 storing.
871 storing.
872
872
873 'extramsg' is a extra message to be shown in the editor instead of
873 'extramsg' is a extra message to be shown in the editor instead of
874 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
874 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
875 is automatically added.
875 is automatically added.
876
876
877 'editform' is a dot-separated list of names, to distinguish
877 'editform' is a dot-separated list of names, to distinguish
878 the purpose of commit text editing.
878 the purpose of commit text editing.
879
879
880 'getcommiteditor' returns 'commitforceeditor' regardless of
880 'getcommiteditor' returns 'commitforceeditor' regardless of
881 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
881 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
882 they are specific for usage in MQ.
882 they are specific for usage in MQ.
883 """
883 """
884 if edit or finishdesc or extramsg:
884 if edit or finishdesc or extramsg:
885 return lambda r, c, s: commitforceeditor(r, c, s,
885 return lambda r, c, s: commitforceeditor(r, c, s,
886 finishdesc=finishdesc,
886 finishdesc=finishdesc,
887 extramsg=extramsg,
887 extramsg=extramsg,
888 editform=editform)
888 editform=editform)
889 elif editform:
889 elif editform:
890 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
890 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
891 else:
891 else:
892 return commiteditor
892 return commiteditor
893
893
894 def makefilename(repo, pat, node, desc=None,
894 def makefilename(repo, pat, node, desc=None,
895 total=None, seqno=None, revwidth=None, pathname=None):
895 total=None, seqno=None, revwidth=None, pathname=None):
896 node_expander = {
896 node_expander = {
897 'H': lambda: hex(node),
897 'H': lambda: hex(node),
898 'R': lambda: '%d' % repo.changelog.rev(node),
898 'R': lambda: '%d' % repo.changelog.rev(node),
899 'h': lambda: short(node),
899 'h': lambda: short(node),
900 'm': lambda: re.sub('[^\w]', '_', desc or '')
900 'm': lambda: re.sub('[^\w]', '_', desc or '')
901 }
901 }
902 expander = {
902 expander = {
903 '%': lambda: '%',
903 '%': lambda: '%',
904 'b': lambda: os.path.basename(repo.root),
904 'b': lambda: os.path.basename(repo.root),
905 }
905 }
906
906
907 try:
907 try:
908 if node:
908 if node:
909 expander.update(node_expander)
909 expander.update(node_expander)
910 if node:
910 if node:
911 expander['r'] = (lambda:
911 expander['r'] = (lambda:
912 ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0))
912 ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0))
913 if total is not None:
913 if total is not None:
914 expander['N'] = lambda: '%d' % total
914 expander['N'] = lambda: '%d' % total
915 if seqno is not None:
915 if seqno is not None:
916 expander['n'] = lambda: '%d' % seqno
916 expander['n'] = lambda: '%d' % seqno
917 if total is not None and seqno is not None:
917 if total is not None and seqno is not None:
918 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
918 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
919 if pathname is not None:
919 if pathname is not None:
920 expander['s'] = lambda: os.path.basename(pathname)
920 expander['s'] = lambda: os.path.basename(pathname)
921 expander['d'] = lambda: os.path.dirname(pathname) or '.'
921 expander['d'] = lambda: os.path.dirname(pathname) or '.'
922 expander['p'] = lambda: pathname
922 expander['p'] = lambda: pathname
923
923
924 newname = []
924 newname = []
925 patlen = len(pat)
925 patlen = len(pat)
926 i = 0
926 i = 0
927 while i < patlen:
927 while i < patlen:
928 c = pat[i:i + 1]
928 c = pat[i:i + 1]
929 if c == '%':
929 if c == '%':
930 i += 1
930 i += 1
931 c = pat[i:i + 1]
931 c = pat[i:i + 1]
932 c = expander[c]()
932 c = expander[c]()
933 newname.append(c)
933 newname.append(c)
934 i += 1
934 i += 1
935 return ''.join(newname)
935 return ''.join(newname)
936 except KeyError as inst:
936 except KeyError as inst:
937 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
937 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
938 inst.args[0])
938 inst.args[0])
939
939
940 def isstdiofilename(pat):
940 def isstdiofilename(pat):
941 """True if the given pat looks like a filename denoting stdin/stdout"""
941 """True if the given pat looks like a filename denoting stdin/stdout"""
942 return not pat or pat == '-'
942 return not pat or pat == '-'
943
943
944 class _unclosablefile(object):
944 class _unclosablefile(object):
945 def __init__(self, fp):
945 def __init__(self, fp):
946 self._fp = fp
946 self._fp = fp
947
947
948 def close(self):
948 def close(self):
949 pass
949 pass
950
950
951 def __iter__(self):
951 def __iter__(self):
952 return iter(self._fp)
952 return iter(self._fp)
953
953
954 def __getattr__(self, attr):
954 def __getattr__(self, attr):
955 return getattr(self._fp, attr)
955 return getattr(self._fp, attr)
956
956
957 def __enter__(self):
957 def __enter__(self):
958 return self
958 return self
959
959
960 def __exit__(self, exc_type, exc_value, exc_tb):
960 def __exit__(self, exc_type, exc_value, exc_tb):
961 pass
961 pass
962
962
963 def makefileobj(repo, pat, node=None, desc=None, total=None,
963 def makefileobj(repo, pat, node=None, desc=None, total=None,
964 seqno=None, revwidth=None, mode='wb', modemap=None,
964 seqno=None, revwidth=None, mode='wb', modemap=None,
965 pathname=None):
965 pathname=None):
966
966
967 writable = mode not in ('r', 'rb')
967 writable = mode not in ('r', 'rb')
968
968
969 if isstdiofilename(pat):
969 if isstdiofilename(pat):
970 if writable:
970 if writable:
971 fp = repo.ui.fout
971 fp = repo.ui.fout
972 else:
972 else:
973 fp = repo.ui.fin
973 fp = repo.ui.fin
974 return _unclosablefile(fp)
974 return _unclosablefile(fp)
975 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
975 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
976 if modemap is not None:
976 if modemap is not None:
977 mode = modemap.get(fn, mode)
977 mode = modemap.get(fn, mode)
978 if mode == 'wb':
978 if mode == 'wb':
979 modemap[fn] = 'ab'
979 modemap[fn] = 'ab'
980 return open(fn, mode)
980 return open(fn, mode)
981
981
982 def openrevlog(repo, cmd, file_, opts):
982 def openrevlog(repo, cmd, file_, opts):
983 """opens the changelog, manifest, a filelog or a given revlog"""
983 """opens the changelog, manifest, a filelog or a given revlog"""
984 cl = opts['changelog']
984 cl = opts['changelog']
985 mf = opts['manifest']
985 mf = opts['manifest']
986 dir = opts['dir']
986 dir = opts['dir']
987 msg = None
987 msg = None
988 if cl and mf:
988 if cl and mf:
989 msg = _('cannot specify --changelog and --manifest at the same time')
989 msg = _('cannot specify --changelog and --manifest at the same time')
990 elif cl and dir:
990 elif cl and dir:
991 msg = _('cannot specify --changelog and --dir at the same time')
991 msg = _('cannot specify --changelog and --dir at the same time')
992 elif cl or mf or dir:
992 elif cl or mf or dir:
993 if file_:
993 if file_:
994 msg = _('cannot specify filename with --changelog or --manifest')
994 msg = _('cannot specify filename with --changelog or --manifest')
995 elif not repo:
995 elif not repo:
996 msg = _('cannot specify --changelog or --manifest or --dir '
996 msg = _('cannot specify --changelog or --manifest or --dir '
997 'without a repository')
997 'without a repository')
998 if msg:
998 if msg:
999 raise error.Abort(msg)
999 raise error.Abort(msg)
1000
1000
1001 r = None
1001 r = None
1002 if repo:
1002 if repo:
1003 if cl:
1003 if cl:
1004 r = repo.unfiltered().changelog
1004 r = repo.unfiltered().changelog
1005 elif dir:
1005 elif dir:
1006 if 'treemanifest' not in repo.requirements:
1006 if 'treemanifest' not in repo.requirements:
1007 raise error.Abort(_("--dir can only be used on repos with "
1007 raise error.Abort(_("--dir can only be used on repos with "
1008 "treemanifest enabled"))
1008 "treemanifest enabled"))
1009 dirlog = repo.manifestlog._revlog.dirlog(dir)
1009 dirlog = repo.manifestlog._revlog.dirlog(dir)
1010 if len(dirlog):
1010 if len(dirlog):
1011 r = dirlog
1011 r = dirlog
1012 elif mf:
1012 elif mf:
1013 r = repo.manifestlog._revlog
1013 r = repo.manifestlog._revlog
1014 elif file_:
1014 elif file_:
1015 filelog = repo.file(file_)
1015 filelog = repo.file(file_)
1016 if len(filelog):
1016 if len(filelog):
1017 r = filelog
1017 r = filelog
1018 if not r:
1018 if not r:
1019 if not file_:
1019 if not file_:
1020 raise error.CommandError(cmd, _('invalid arguments'))
1020 raise error.CommandError(cmd, _('invalid arguments'))
1021 if not os.path.isfile(file_):
1021 if not os.path.isfile(file_):
1022 raise error.Abort(_("revlog '%s' not found") % file_)
1022 raise error.Abort(_("revlog '%s' not found") % file_)
1023 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1023 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1024 file_[:-2] + ".i")
1024 file_[:-2] + ".i")
1025 return r
1025 return r
1026
1026
1027 def copy(ui, repo, pats, opts, rename=False):
1027 def copy(ui, repo, pats, opts, rename=False):
1028 # called with the repo lock held
1028 # called with the repo lock held
1029 #
1029 #
1030 # hgsep => pathname that uses "/" to separate directories
1030 # hgsep => pathname that uses "/" to separate directories
1031 # ossep => pathname that uses os.sep to separate directories
1031 # ossep => pathname that uses os.sep to separate directories
1032 cwd = repo.getcwd()
1032 cwd = repo.getcwd()
1033 targets = {}
1033 targets = {}
1034 after = opts.get("after")
1034 after = opts.get("after")
1035 dryrun = opts.get("dry_run")
1035 dryrun = opts.get("dry_run")
1036 wctx = repo[None]
1036 wctx = repo[None]
1037
1037
1038 def walkpat(pat):
1038 def walkpat(pat):
1039 srcs = []
1039 srcs = []
1040 if after:
1040 if after:
1041 badstates = '?'
1041 badstates = '?'
1042 else:
1042 else:
1043 badstates = '?r'
1043 badstates = '?r'
1044 m = scmutil.match(wctx, [pat], opts, globbed=True)
1044 m = scmutil.match(wctx, [pat], opts, globbed=True)
1045 for abs in wctx.walk(m):
1045 for abs in wctx.walk(m):
1046 state = repo.dirstate[abs]
1046 state = repo.dirstate[abs]
1047 rel = m.rel(abs)
1047 rel = m.rel(abs)
1048 exact = m.exact(abs)
1048 exact = m.exact(abs)
1049 if state in badstates:
1049 if state in badstates:
1050 if exact and state == '?':
1050 if exact and state == '?':
1051 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1051 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1052 if exact and state == 'r':
1052 if exact and state == 'r':
1053 ui.warn(_('%s: not copying - file has been marked for'
1053 ui.warn(_('%s: not copying - file has been marked for'
1054 ' remove\n') % rel)
1054 ' remove\n') % rel)
1055 continue
1055 continue
1056 # abs: hgsep
1056 # abs: hgsep
1057 # rel: ossep
1057 # rel: ossep
1058 srcs.append((abs, rel, exact))
1058 srcs.append((abs, rel, exact))
1059 return srcs
1059 return srcs
1060
1060
1061 # abssrc: hgsep
1061 # abssrc: hgsep
1062 # relsrc: ossep
1062 # relsrc: ossep
1063 # otarget: ossep
1063 # otarget: ossep
1064 def copyfile(abssrc, relsrc, otarget, exact):
1064 def copyfile(abssrc, relsrc, otarget, exact):
1065 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1065 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1066 if '/' in abstarget:
1066 if '/' in abstarget:
1067 # We cannot normalize abstarget itself, this would prevent
1067 # We cannot normalize abstarget itself, this would prevent
1068 # case only renames, like a => A.
1068 # case only renames, like a => A.
1069 abspath, absname = abstarget.rsplit('/', 1)
1069 abspath, absname = abstarget.rsplit('/', 1)
1070 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1070 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1071 reltarget = repo.pathto(abstarget, cwd)
1071 reltarget = repo.pathto(abstarget, cwd)
1072 target = repo.wjoin(abstarget)
1072 target = repo.wjoin(abstarget)
1073 src = repo.wjoin(abssrc)
1073 src = repo.wjoin(abssrc)
1074 state = repo.dirstate[abstarget]
1074 state = repo.dirstate[abstarget]
1075
1075
1076 scmutil.checkportable(ui, abstarget)
1076 scmutil.checkportable(ui, abstarget)
1077
1077
1078 # check for collisions
1078 # check for collisions
1079 prevsrc = targets.get(abstarget)
1079 prevsrc = targets.get(abstarget)
1080 if prevsrc is not None:
1080 if prevsrc is not None:
1081 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1081 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1082 (reltarget, repo.pathto(abssrc, cwd),
1082 (reltarget, repo.pathto(abssrc, cwd),
1083 repo.pathto(prevsrc, cwd)))
1083 repo.pathto(prevsrc, cwd)))
1084 return
1084 return
1085
1085
1086 # check for overwrites
1086 # check for overwrites
1087 exists = os.path.lexists(target)
1087 exists = os.path.lexists(target)
1088 samefile = False
1088 samefile = False
1089 if exists and abssrc != abstarget:
1089 if exists and abssrc != abstarget:
1090 if (repo.dirstate.normalize(abssrc) ==
1090 if (repo.dirstate.normalize(abssrc) ==
1091 repo.dirstate.normalize(abstarget)):
1091 repo.dirstate.normalize(abstarget)):
1092 if not rename:
1092 if not rename:
1093 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1093 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1094 return
1094 return
1095 exists = False
1095 exists = False
1096 samefile = True
1096 samefile = True
1097
1097
1098 if not after and exists or after and state in 'mn':
1098 if not after and exists or after and state in 'mn':
1099 if not opts['force']:
1099 if not opts['force']:
1100 if state in 'mn':
1100 if state in 'mn':
1101 msg = _('%s: not overwriting - file already committed\n')
1101 msg = _('%s: not overwriting - file already committed\n')
1102 if after:
1102 if after:
1103 flags = '--after --force'
1103 flags = '--after --force'
1104 else:
1104 else:
1105 flags = '--force'
1105 flags = '--force'
1106 if rename:
1106 if rename:
1107 hint = _('(hg rename %s to replace the file by '
1107 hint = _('(hg rename %s to replace the file by '
1108 'recording a rename)\n') % flags
1108 'recording a rename)\n') % flags
1109 else:
1109 else:
1110 hint = _('(hg copy %s to replace the file by '
1110 hint = _('(hg copy %s to replace the file by '
1111 'recording a copy)\n') % flags
1111 'recording a copy)\n') % flags
1112 else:
1112 else:
1113 msg = _('%s: not overwriting - file exists\n')
1113 msg = _('%s: not overwriting - file exists\n')
1114 if rename:
1114 if rename:
1115 hint = _('(hg rename --after to record the rename)\n')
1115 hint = _('(hg rename --after to record the rename)\n')
1116 else:
1116 else:
1117 hint = _('(hg copy --after to record the copy)\n')
1117 hint = _('(hg copy --after to record the copy)\n')
1118 ui.warn(msg % reltarget)
1118 ui.warn(msg % reltarget)
1119 ui.warn(hint)
1119 ui.warn(hint)
1120 return
1120 return
1121
1121
1122 if after:
1122 if after:
1123 if not exists:
1123 if not exists:
1124 if rename:
1124 if rename:
1125 ui.warn(_('%s: not recording move - %s does not exist\n') %
1125 ui.warn(_('%s: not recording move - %s does not exist\n') %
1126 (relsrc, reltarget))
1126 (relsrc, reltarget))
1127 else:
1127 else:
1128 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1128 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1129 (relsrc, reltarget))
1129 (relsrc, reltarget))
1130 return
1130 return
1131 elif not dryrun:
1131 elif not dryrun:
1132 try:
1132 try:
1133 if exists:
1133 if exists:
1134 os.unlink(target)
1134 os.unlink(target)
1135 targetdir = os.path.dirname(target) or '.'
1135 targetdir = os.path.dirname(target) or '.'
1136 if not os.path.isdir(targetdir):
1136 if not os.path.isdir(targetdir):
1137 os.makedirs(targetdir)
1137 os.makedirs(targetdir)
1138 if samefile:
1138 if samefile:
1139 tmp = target + "~hgrename"
1139 tmp = target + "~hgrename"
1140 os.rename(src, tmp)
1140 os.rename(src, tmp)
1141 os.rename(tmp, target)
1141 os.rename(tmp, target)
1142 else:
1142 else:
1143 util.copyfile(src, target)
1143 util.copyfile(src, target)
1144 srcexists = True
1144 srcexists = True
1145 except IOError as inst:
1145 except IOError as inst:
1146 if inst.errno == errno.ENOENT:
1146 if inst.errno == errno.ENOENT:
1147 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1147 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1148 srcexists = False
1148 srcexists = False
1149 else:
1149 else:
1150 ui.warn(_('%s: cannot copy - %s\n') %
1150 ui.warn(_('%s: cannot copy - %s\n') %
1151 (relsrc, encoding.strtolocal(inst.strerror)))
1151 (relsrc, encoding.strtolocal(inst.strerror)))
1152 return True # report a failure
1152 return True # report a failure
1153
1153
1154 if ui.verbose or not exact:
1154 if ui.verbose or not exact:
1155 if rename:
1155 if rename:
1156 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1156 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1157 else:
1157 else:
1158 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1158 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1159
1159
1160 targets[abstarget] = abssrc
1160 targets[abstarget] = abssrc
1161
1161
1162 # fix up dirstate
1162 # fix up dirstate
1163 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1163 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1164 dryrun=dryrun, cwd=cwd)
1164 dryrun=dryrun, cwd=cwd)
1165 if rename and not dryrun:
1165 if rename and not dryrun:
1166 if not after and srcexists and not samefile:
1166 if not after and srcexists and not samefile:
1167 repo.wvfs.unlinkpath(abssrc)
1167 repo.wvfs.unlinkpath(abssrc)
1168 wctx.forget([abssrc])
1168 wctx.forget([abssrc])
1169
1169
1170 # pat: ossep
1170 # pat: ossep
1171 # dest ossep
1171 # dest ossep
1172 # srcs: list of (hgsep, hgsep, ossep, bool)
1172 # srcs: list of (hgsep, hgsep, ossep, bool)
1173 # return: function that takes hgsep and returns ossep
1173 # return: function that takes hgsep and returns ossep
1174 def targetpathfn(pat, dest, srcs):
1174 def targetpathfn(pat, dest, srcs):
1175 if os.path.isdir(pat):
1175 if os.path.isdir(pat):
1176 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1176 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1177 abspfx = util.localpath(abspfx)
1177 abspfx = util.localpath(abspfx)
1178 if destdirexists:
1178 if destdirexists:
1179 striplen = len(os.path.split(abspfx)[0])
1179 striplen = len(os.path.split(abspfx)[0])
1180 else:
1180 else:
1181 striplen = len(abspfx)
1181 striplen = len(abspfx)
1182 if striplen:
1182 if striplen:
1183 striplen += len(pycompat.ossep)
1183 striplen += len(pycompat.ossep)
1184 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1184 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1185 elif destdirexists:
1185 elif destdirexists:
1186 res = lambda p: os.path.join(dest,
1186 res = lambda p: os.path.join(dest,
1187 os.path.basename(util.localpath(p)))
1187 os.path.basename(util.localpath(p)))
1188 else:
1188 else:
1189 res = lambda p: dest
1189 res = lambda p: dest
1190 return res
1190 return res
1191
1191
1192 # pat: ossep
1192 # pat: ossep
1193 # dest ossep
1193 # dest ossep
1194 # srcs: list of (hgsep, hgsep, ossep, bool)
1194 # srcs: list of (hgsep, hgsep, ossep, bool)
1195 # return: function that takes hgsep and returns ossep
1195 # return: function that takes hgsep and returns ossep
1196 def targetpathafterfn(pat, dest, srcs):
1196 def targetpathafterfn(pat, dest, srcs):
1197 if matchmod.patkind(pat):
1197 if matchmod.patkind(pat):
1198 # a mercurial pattern
1198 # a mercurial pattern
1199 res = lambda p: os.path.join(dest,
1199 res = lambda p: os.path.join(dest,
1200 os.path.basename(util.localpath(p)))
1200 os.path.basename(util.localpath(p)))
1201 else:
1201 else:
1202 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1202 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1203 if len(abspfx) < len(srcs[0][0]):
1203 if len(abspfx) < len(srcs[0][0]):
1204 # A directory. Either the target path contains the last
1204 # A directory. Either the target path contains the last
1205 # component of the source path or it does not.
1205 # component of the source path or it does not.
1206 def evalpath(striplen):
1206 def evalpath(striplen):
1207 score = 0
1207 score = 0
1208 for s in srcs:
1208 for s in srcs:
1209 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1209 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1210 if os.path.lexists(t):
1210 if os.path.lexists(t):
1211 score += 1
1211 score += 1
1212 return score
1212 return score
1213
1213
1214 abspfx = util.localpath(abspfx)
1214 abspfx = util.localpath(abspfx)
1215 striplen = len(abspfx)
1215 striplen = len(abspfx)
1216 if striplen:
1216 if striplen:
1217 striplen += len(pycompat.ossep)
1217 striplen += len(pycompat.ossep)
1218 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1218 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1219 score = evalpath(striplen)
1219 score = evalpath(striplen)
1220 striplen1 = len(os.path.split(abspfx)[0])
1220 striplen1 = len(os.path.split(abspfx)[0])
1221 if striplen1:
1221 if striplen1:
1222 striplen1 += len(pycompat.ossep)
1222 striplen1 += len(pycompat.ossep)
1223 if evalpath(striplen1) > score:
1223 if evalpath(striplen1) > score:
1224 striplen = striplen1
1224 striplen = striplen1
1225 res = lambda p: os.path.join(dest,
1225 res = lambda p: os.path.join(dest,
1226 util.localpath(p)[striplen:])
1226 util.localpath(p)[striplen:])
1227 else:
1227 else:
1228 # a file
1228 # a file
1229 if destdirexists:
1229 if destdirexists:
1230 res = lambda p: os.path.join(dest,
1230 res = lambda p: os.path.join(dest,
1231 os.path.basename(util.localpath(p)))
1231 os.path.basename(util.localpath(p)))
1232 else:
1232 else:
1233 res = lambda p: dest
1233 res = lambda p: dest
1234 return res
1234 return res
1235
1235
1236 pats = scmutil.expandpats(pats)
1236 pats = scmutil.expandpats(pats)
1237 if not pats:
1237 if not pats:
1238 raise error.Abort(_('no source or destination specified'))
1238 raise error.Abort(_('no source or destination specified'))
1239 if len(pats) == 1:
1239 if len(pats) == 1:
1240 raise error.Abort(_('no destination specified'))
1240 raise error.Abort(_('no destination specified'))
1241 dest = pats.pop()
1241 dest = pats.pop()
1242 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1242 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1243 if not destdirexists:
1243 if not destdirexists:
1244 if len(pats) > 1 or matchmod.patkind(pats[0]):
1244 if len(pats) > 1 or matchmod.patkind(pats[0]):
1245 raise error.Abort(_('with multiple sources, destination must be an '
1245 raise error.Abort(_('with multiple sources, destination must be an '
1246 'existing directory'))
1246 'existing directory'))
1247 if util.endswithsep(dest):
1247 if util.endswithsep(dest):
1248 raise error.Abort(_('destination %s is not a directory') % dest)
1248 raise error.Abort(_('destination %s is not a directory') % dest)
1249
1249
1250 tfn = targetpathfn
1250 tfn = targetpathfn
1251 if after:
1251 if after:
1252 tfn = targetpathafterfn
1252 tfn = targetpathafterfn
1253 copylist = []
1253 copylist = []
1254 for pat in pats:
1254 for pat in pats:
1255 srcs = walkpat(pat)
1255 srcs = walkpat(pat)
1256 if not srcs:
1256 if not srcs:
1257 continue
1257 continue
1258 copylist.append((tfn(pat, dest, srcs), srcs))
1258 copylist.append((tfn(pat, dest, srcs), srcs))
1259 if not copylist:
1259 if not copylist:
1260 raise error.Abort(_('no files to copy'))
1260 raise error.Abort(_('no files to copy'))
1261
1261
1262 errors = 0
1262 errors = 0
1263 for targetpath, srcs in copylist:
1263 for targetpath, srcs in copylist:
1264 for abssrc, relsrc, exact in srcs:
1264 for abssrc, relsrc, exact in srcs:
1265 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1265 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1266 errors += 1
1266 errors += 1
1267
1267
1268 if errors:
1268 if errors:
1269 ui.warn(_('(consider using --after)\n'))
1269 ui.warn(_('(consider using --after)\n'))
1270
1270
1271 return errors != 0
1271 return errors != 0
1272
1272
1273 ## facility to let extension process additional data into an import patch
1273 ## facility to let extension process additional data into an import patch
1274 # list of identifier to be executed in order
1274 # list of identifier to be executed in order
1275 extrapreimport = [] # run before commit
1275 extrapreimport = [] # run before commit
1276 extrapostimport = [] # run after commit
1276 extrapostimport = [] # run after commit
1277 # mapping from identifier to actual import function
1277 # mapping from identifier to actual import function
1278 #
1278 #
1279 # 'preimport' are run before the commit is made and are provided the following
1279 # 'preimport' are run before the commit is made and are provided the following
1280 # arguments:
1280 # arguments:
1281 # - repo: the localrepository instance,
1281 # - repo: the localrepository instance,
1282 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1282 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1283 # - extra: the future extra dictionary of the changeset, please mutate it,
1283 # - extra: the future extra dictionary of the changeset, please mutate it,
1284 # - opts: the import options.
1284 # - opts: the import options.
1285 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1285 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1286 # mutation of in memory commit and more. Feel free to rework the code to get
1286 # mutation of in memory commit and more. Feel free to rework the code to get
1287 # there.
1287 # there.
1288 extrapreimportmap = {}
1288 extrapreimportmap = {}
1289 # 'postimport' are run after the commit is made and are provided the following
1289 # 'postimport' are run after the commit is made and are provided the following
1290 # argument:
1290 # argument:
1291 # - ctx: the changectx created by import.
1291 # - ctx: the changectx created by import.
1292 extrapostimportmap = {}
1292 extrapostimportmap = {}
1293
1293
1294 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1294 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1295 """Utility function used by commands.import to import a single patch
1295 """Utility function used by commands.import to import a single patch
1296
1296
1297 This function is explicitly defined here to help the evolve extension to
1297 This function is explicitly defined here to help the evolve extension to
1298 wrap this part of the import logic.
1298 wrap this part of the import logic.
1299
1299
1300 The API is currently a bit ugly because it a simple code translation from
1300 The API is currently a bit ugly because it a simple code translation from
1301 the import command. Feel free to make it better.
1301 the import command. Feel free to make it better.
1302
1302
1303 :hunk: a patch (as a binary string)
1303 :hunk: a patch (as a binary string)
1304 :parents: nodes that will be parent of the created commit
1304 :parents: nodes that will be parent of the created commit
1305 :opts: the full dict of option passed to the import command
1305 :opts: the full dict of option passed to the import command
1306 :msgs: list to save commit message to.
1306 :msgs: list to save commit message to.
1307 (used in case we need to save it when failing)
1307 (used in case we need to save it when failing)
1308 :updatefunc: a function that update a repo to a given node
1308 :updatefunc: a function that update a repo to a given node
1309 updatefunc(<repo>, <node>)
1309 updatefunc(<repo>, <node>)
1310 """
1310 """
1311 # avoid cycle context -> subrepo -> cmdutil
1311 # avoid cycle context -> subrepo -> cmdutil
1312 from . import context
1312 from . import context
1313 extractdata = patch.extract(ui, hunk)
1313 extractdata = patch.extract(ui, hunk)
1314 tmpname = extractdata.get('filename')
1314 tmpname = extractdata.get('filename')
1315 message = extractdata.get('message')
1315 message = extractdata.get('message')
1316 user = opts.get('user') or extractdata.get('user')
1316 user = opts.get('user') or extractdata.get('user')
1317 date = opts.get('date') or extractdata.get('date')
1317 date = opts.get('date') or extractdata.get('date')
1318 branch = extractdata.get('branch')
1318 branch = extractdata.get('branch')
1319 nodeid = extractdata.get('nodeid')
1319 nodeid = extractdata.get('nodeid')
1320 p1 = extractdata.get('p1')
1320 p1 = extractdata.get('p1')
1321 p2 = extractdata.get('p2')
1321 p2 = extractdata.get('p2')
1322
1322
1323 nocommit = opts.get('no_commit')
1323 nocommit = opts.get('no_commit')
1324 importbranch = opts.get('import_branch')
1324 importbranch = opts.get('import_branch')
1325 update = not opts.get('bypass')
1325 update = not opts.get('bypass')
1326 strip = opts["strip"]
1326 strip = opts["strip"]
1327 prefix = opts["prefix"]
1327 prefix = opts["prefix"]
1328 sim = float(opts.get('similarity') or 0)
1328 sim = float(opts.get('similarity') or 0)
1329 if not tmpname:
1329 if not tmpname:
1330 return (None, None, False)
1330 return (None, None, False)
1331
1331
1332 rejects = False
1332 rejects = False
1333
1333
1334 try:
1334 try:
1335 cmdline_message = logmessage(ui, opts)
1335 cmdline_message = logmessage(ui, opts)
1336 if cmdline_message:
1336 if cmdline_message:
1337 # pickup the cmdline msg
1337 # pickup the cmdline msg
1338 message = cmdline_message
1338 message = cmdline_message
1339 elif message:
1339 elif message:
1340 # pickup the patch msg
1340 # pickup the patch msg
1341 message = message.strip()
1341 message = message.strip()
1342 else:
1342 else:
1343 # launch the editor
1343 # launch the editor
1344 message = None
1344 message = None
1345 ui.debug('message:\n%s\n' % message)
1345 ui.debug('message:\n%s\n' % message)
1346
1346
1347 if len(parents) == 1:
1347 if len(parents) == 1:
1348 parents.append(repo[nullid])
1348 parents.append(repo[nullid])
1349 if opts.get('exact'):
1349 if opts.get('exact'):
1350 if not nodeid or not p1:
1350 if not nodeid or not p1:
1351 raise error.Abort(_('not a Mercurial patch'))
1351 raise error.Abort(_('not a Mercurial patch'))
1352 p1 = repo[p1]
1352 p1 = repo[p1]
1353 p2 = repo[p2 or nullid]
1353 p2 = repo[p2 or nullid]
1354 elif p2:
1354 elif p2:
1355 try:
1355 try:
1356 p1 = repo[p1]
1356 p1 = repo[p1]
1357 p2 = repo[p2]
1357 p2 = repo[p2]
1358 # Without any options, consider p2 only if the
1358 # Without any options, consider p2 only if the
1359 # patch is being applied on top of the recorded
1359 # patch is being applied on top of the recorded
1360 # first parent.
1360 # first parent.
1361 if p1 != parents[0]:
1361 if p1 != parents[0]:
1362 p1 = parents[0]
1362 p1 = parents[0]
1363 p2 = repo[nullid]
1363 p2 = repo[nullid]
1364 except error.RepoError:
1364 except error.RepoError:
1365 p1, p2 = parents
1365 p1, p2 = parents
1366 if p2.node() == nullid:
1366 if p2.node() == nullid:
1367 ui.warn(_("warning: import the patch as a normal revision\n"
1367 ui.warn(_("warning: import the patch as a normal revision\n"
1368 "(use --exact to import the patch as a merge)\n"))
1368 "(use --exact to import the patch as a merge)\n"))
1369 else:
1369 else:
1370 p1, p2 = parents
1370 p1, p2 = parents
1371
1371
1372 n = None
1372 n = None
1373 if update:
1373 if update:
1374 if p1 != parents[0]:
1374 if p1 != parents[0]:
1375 updatefunc(repo, p1.node())
1375 updatefunc(repo, p1.node())
1376 if p2 != parents[1]:
1376 if p2 != parents[1]:
1377 repo.setparents(p1.node(), p2.node())
1377 repo.setparents(p1.node(), p2.node())
1378
1378
1379 if opts.get('exact') or importbranch:
1379 if opts.get('exact') or importbranch:
1380 repo.dirstate.setbranch(branch or 'default')
1380 repo.dirstate.setbranch(branch or 'default')
1381
1381
1382 partial = opts.get('partial', False)
1382 partial = opts.get('partial', False)
1383 files = set()
1383 files = set()
1384 try:
1384 try:
1385 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1385 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1386 files=files, eolmode=None, similarity=sim / 100.0)
1386 files=files, eolmode=None, similarity=sim / 100.0)
1387 except error.PatchError as e:
1387 except error.PatchError as e:
1388 if not partial:
1388 if not partial:
1389 raise error.Abort(str(e))
1389 raise error.Abort(str(e))
1390 if partial:
1390 if partial:
1391 rejects = True
1391 rejects = True
1392
1392
1393 files = list(files)
1393 files = list(files)
1394 if nocommit:
1394 if nocommit:
1395 if message:
1395 if message:
1396 msgs.append(message)
1396 msgs.append(message)
1397 else:
1397 else:
1398 if opts.get('exact') or p2:
1398 if opts.get('exact') or p2:
1399 # If you got here, you either use --force and know what
1399 # If you got here, you either use --force and know what
1400 # you are doing or used --exact or a merge patch while
1400 # you are doing or used --exact or a merge patch while
1401 # being updated to its first parent.
1401 # being updated to its first parent.
1402 m = None
1402 m = None
1403 else:
1403 else:
1404 m = scmutil.matchfiles(repo, files or [])
1404 m = scmutil.matchfiles(repo, files or [])
1405 editform = mergeeditform(repo[None], 'import.normal')
1405 editform = mergeeditform(repo[None], 'import.normal')
1406 if opts.get('exact'):
1406 if opts.get('exact'):
1407 editor = None
1407 editor = None
1408 else:
1408 else:
1409 editor = getcommiteditor(editform=editform,
1409 editor = getcommiteditor(editform=editform,
1410 **pycompat.strkwargs(opts))
1410 **pycompat.strkwargs(opts))
1411 extra = {}
1411 extra = {}
1412 for idfunc in extrapreimport:
1412 for idfunc in extrapreimport:
1413 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1413 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1414 overrides = {}
1414 overrides = {}
1415 if partial:
1415 if partial:
1416 overrides[('ui', 'allowemptycommit')] = True
1416 overrides[('ui', 'allowemptycommit')] = True
1417 with repo.ui.configoverride(overrides, 'import'):
1417 with repo.ui.configoverride(overrides, 'import'):
1418 n = repo.commit(message, user,
1418 n = repo.commit(message, user,
1419 date, match=m,
1419 date, match=m,
1420 editor=editor, extra=extra)
1420 editor=editor, extra=extra)
1421 for idfunc in extrapostimport:
1421 for idfunc in extrapostimport:
1422 extrapostimportmap[idfunc](repo[n])
1422 extrapostimportmap[idfunc](repo[n])
1423 else:
1423 else:
1424 if opts.get('exact') or importbranch:
1424 if opts.get('exact') or importbranch:
1425 branch = branch or 'default'
1425 branch = branch or 'default'
1426 else:
1426 else:
1427 branch = p1.branch()
1427 branch = p1.branch()
1428 store = patch.filestore()
1428 store = patch.filestore()
1429 try:
1429 try:
1430 files = set()
1430 files = set()
1431 try:
1431 try:
1432 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1432 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1433 files, eolmode=None)
1433 files, eolmode=None)
1434 except error.PatchError as e:
1434 except error.PatchError as e:
1435 raise error.Abort(str(e))
1435 raise error.Abort(str(e))
1436 if opts.get('exact'):
1436 if opts.get('exact'):
1437 editor = None
1437 editor = None
1438 else:
1438 else:
1439 editor = getcommiteditor(editform='import.bypass')
1439 editor = getcommiteditor(editform='import.bypass')
1440 memctx = context.memctx(repo, (p1.node(), p2.node()),
1440 memctx = context.memctx(repo, (p1.node(), p2.node()),
1441 message,
1441 message,
1442 files=files,
1442 files=files,
1443 filectxfn=store,
1443 filectxfn=store,
1444 user=user,
1444 user=user,
1445 date=date,
1445 date=date,
1446 branch=branch,
1446 branch=branch,
1447 editor=editor)
1447 editor=editor)
1448 n = memctx.commit()
1448 n = memctx.commit()
1449 finally:
1449 finally:
1450 store.close()
1450 store.close()
1451 if opts.get('exact') and nocommit:
1451 if opts.get('exact') and nocommit:
1452 # --exact with --no-commit is still useful in that it does merge
1452 # --exact with --no-commit is still useful in that it does merge
1453 # and branch bits
1453 # and branch bits
1454 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1454 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1455 elif opts.get('exact') and hex(n) != nodeid:
1455 elif opts.get('exact') and hex(n) != nodeid:
1456 raise error.Abort(_('patch is damaged or loses information'))
1456 raise error.Abort(_('patch is damaged or loses information'))
1457 msg = _('applied to working directory')
1457 msg = _('applied to working directory')
1458 if n:
1458 if n:
1459 # i18n: refers to a short changeset id
1459 # i18n: refers to a short changeset id
1460 msg = _('created %s') % short(n)
1460 msg = _('created %s') % short(n)
1461 return (msg, n, rejects)
1461 return (msg, n, rejects)
1462 finally:
1462 finally:
1463 os.unlink(tmpname)
1463 os.unlink(tmpname)
1464
1464
1465 # facility to let extensions include additional data in an exported patch
1465 # facility to let extensions include additional data in an exported patch
1466 # list of identifiers to be executed in order
1466 # list of identifiers to be executed in order
1467 extraexport = []
1467 extraexport = []
1468 # mapping from identifier to actual export function
1468 # mapping from identifier to actual export function
1469 # function as to return a string to be added to the header or None
1469 # function as to return a string to be added to the header or None
1470 # it is given two arguments (sequencenumber, changectx)
1470 # it is given two arguments (sequencenumber, changectx)
1471 extraexportmap = {}
1471 extraexportmap = {}
1472
1472
1473 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1473 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1474 node = scmutil.binnode(ctx)
1474 node = scmutil.binnode(ctx)
1475 parents = [p.node() for p in ctx.parents() if p]
1475 parents = [p.node() for p in ctx.parents() if p]
1476 branch = ctx.branch()
1476 branch = ctx.branch()
1477 if switch_parent:
1477 if switch_parent:
1478 parents.reverse()
1478 parents.reverse()
1479
1479
1480 if parents:
1480 if parents:
1481 prev = parents[0]
1481 prev = parents[0]
1482 else:
1482 else:
1483 prev = nullid
1483 prev = nullid
1484
1484
1485 write("# HG changeset patch\n")
1485 write("# HG changeset patch\n")
1486 write("# User %s\n" % ctx.user())
1486 write("# User %s\n" % ctx.user())
1487 write("# Date %d %d\n" % ctx.date())
1487 write("# Date %d %d\n" % ctx.date())
1488 write("# %s\n" % util.datestr(ctx.date()))
1488 write("# %s\n" % util.datestr(ctx.date()))
1489 if branch and branch != 'default':
1489 if branch and branch != 'default':
1490 write("# Branch %s\n" % branch)
1490 write("# Branch %s\n" % branch)
1491 write("# Node ID %s\n" % hex(node))
1491 write("# Node ID %s\n" % hex(node))
1492 write("# Parent %s\n" % hex(prev))
1492 write("# Parent %s\n" % hex(prev))
1493 if len(parents) > 1:
1493 if len(parents) > 1:
1494 write("# Parent %s\n" % hex(parents[1]))
1494 write("# Parent %s\n" % hex(parents[1]))
1495
1495
1496 for headerid in extraexport:
1496 for headerid in extraexport:
1497 header = extraexportmap[headerid](seqno, ctx)
1497 header = extraexportmap[headerid](seqno, ctx)
1498 if header is not None:
1498 if header is not None:
1499 write('# %s\n' % header)
1499 write('# %s\n' % header)
1500 write(ctx.description().rstrip())
1500 write(ctx.description().rstrip())
1501 write("\n\n")
1501 write("\n\n")
1502
1502
1503 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1503 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1504 write(chunk, label=label)
1504 write(chunk, label=label)
1505
1505
1506 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1506 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1507 opts=None, match=None):
1507 opts=None, match=None):
1508 '''export changesets as hg patches
1508 '''export changesets as hg patches
1509
1509
1510 Args:
1510 Args:
1511 repo: The repository from which we're exporting revisions.
1511 repo: The repository from which we're exporting revisions.
1512 revs: A list of revisions to export as revision numbers.
1512 revs: A list of revisions to export as revision numbers.
1513 fntemplate: An optional string to use for generating patch file names.
1513 fntemplate: An optional string to use for generating patch file names.
1514 fp: An optional file-like object to which patches should be written.
1514 fp: An optional file-like object to which patches should be written.
1515 switch_parent: If True, show diffs against second parent when not nullid.
1515 switch_parent: If True, show diffs against second parent when not nullid.
1516 Default is false, which always shows diff against p1.
1516 Default is false, which always shows diff against p1.
1517 opts: diff options to use for generating the patch.
1517 opts: diff options to use for generating the patch.
1518 match: If specified, only export changes to files matching this matcher.
1518 match: If specified, only export changes to files matching this matcher.
1519
1519
1520 Returns:
1520 Returns:
1521 Nothing.
1521 Nothing.
1522
1522
1523 Side Effect:
1523 Side Effect:
1524 "HG Changeset Patch" data is emitted to one of the following
1524 "HG Changeset Patch" data is emitted to one of the following
1525 destinations:
1525 destinations:
1526 fp is specified: All revs are written to the specified
1526 fp is specified: All revs are written to the specified
1527 file-like object.
1527 file-like object.
1528 fntemplate specified: Each rev is written to a unique file named using
1528 fntemplate specified: Each rev is written to a unique file named using
1529 the given template.
1529 the given template.
1530 Neither fp nor template specified: All revs written to repo.ui.write()
1530 Neither fp nor template specified: All revs written to repo.ui.write()
1531 '''
1531 '''
1532
1532
1533 total = len(revs)
1533 total = len(revs)
1534 revwidth = max(len(str(rev)) for rev in revs)
1534 revwidth = max(len(str(rev)) for rev in revs)
1535 filemode = {}
1535 filemode = {}
1536
1536
1537 write = None
1537 write = None
1538 dest = '<unnamed>'
1538 dest = '<unnamed>'
1539 if fp:
1539 if fp:
1540 dest = getattr(fp, 'name', dest)
1540 dest = getattr(fp, 'name', dest)
1541 def write(s, **kw):
1541 def write(s, **kw):
1542 fp.write(s)
1542 fp.write(s)
1543 elif not fntemplate:
1543 elif not fntemplate:
1544 write = repo.ui.write
1544 write = repo.ui.write
1545
1545
1546 for seqno, rev in enumerate(revs, 1):
1546 for seqno, rev in enumerate(revs, 1):
1547 ctx = repo[rev]
1547 ctx = repo[rev]
1548 fo = None
1548 fo = None
1549 if not fp and fntemplate:
1549 if not fp and fntemplate:
1550 desc_lines = ctx.description().rstrip().split('\n')
1550 desc_lines = ctx.description().rstrip().split('\n')
1551 desc = desc_lines[0] #Commit always has a first line.
1551 desc = desc_lines[0] #Commit always has a first line.
1552 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1552 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1553 total=total, seqno=seqno, revwidth=revwidth,
1553 total=total, seqno=seqno, revwidth=revwidth,
1554 mode='wb', modemap=filemode)
1554 mode='wb', modemap=filemode)
1555 dest = fo.name
1555 dest = fo.name
1556 def write(s, **kw):
1556 def write(s, **kw):
1557 fo.write(s)
1557 fo.write(s)
1558 if not dest.startswith('<'):
1558 if not dest.startswith('<'):
1559 repo.ui.note("%s\n" % dest)
1559 repo.ui.note("%s\n" % dest)
1560 _exportsingle(
1560 _exportsingle(
1561 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1561 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1562 if fo is not None:
1562 if fo is not None:
1563 fo.close()
1563 fo.close()
1564
1564
1565 def showmarker(fm, marker, index=None):
1565 def showmarker(fm, marker, index=None):
1566 """utility function to display obsolescence marker in a readable way
1566 """utility function to display obsolescence marker in a readable way
1567
1567
1568 To be used by debug function."""
1568 To be used by debug function."""
1569 if index is not None:
1569 if index is not None:
1570 fm.write('index', '%i ', index)
1570 fm.write('index', '%i ', index)
1571 fm.write('prednode', '%s ', hex(marker.prednode()))
1571 fm.write('prednode', '%s ', hex(marker.prednode()))
1572 succs = marker.succnodes()
1572 succs = marker.succnodes()
1573 fm.condwrite(succs, 'succnodes', '%s ',
1573 fm.condwrite(succs, 'succnodes', '%s ',
1574 fm.formatlist(map(hex, succs), name='node'))
1574 fm.formatlist(map(hex, succs), name='node'))
1575 fm.write('flag', '%X ', marker.flags())
1575 fm.write('flag', '%X ', marker.flags())
1576 parents = marker.parentnodes()
1576 parents = marker.parentnodes()
1577 if parents is not None:
1577 if parents is not None:
1578 fm.write('parentnodes', '{%s} ',
1578 fm.write('parentnodes', '{%s} ',
1579 fm.formatlist(map(hex, parents), name='node', sep=', '))
1579 fm.formatlist(map(hex, parents), name='node', sep=', '))
1580 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1580 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1581 meta = marker.metadata().copy()
1581 meta = marker.metadata().copy()
1582 meta.pop('date', None)
1582 meta.pop('date', None)
1583 smeta = util.rapply(pycompat.maybebytestr, meta)
1583 smeta = util.rapply(pycompat.maybebytestr, meta)
1584 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1584 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1585 fm.plain('\n')
1585 fm.plain('\n')
1586
1586
1587 def finddate(ui, repo, date):
1587 def finddate(ui, repo, date):
1588 """Find the tipmost changeset that matches the given date spec"""
1588 """Find the tipmost changeset that matches the given date spec"""
1589
1589
1590 df = util.matchdate(date)
1590 df = util.matchdate(date)
1591 m = scmutil.matchall(repo)
1591 m = scmutil.matchall(repo)
1592 results = {}
1592 results = {}
1593
1593
1594 def prep(ctx, fns):
1594 def prep(ctx, fns):
1595 d = ctx.date()
1595 d = ctx.date()
1596 if df(d[0]):
1596 if df(d[0]):
1597 results[ctx.rev()] = d
1597 results[ctx.rev()] = d
1598
1598
1599 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1599 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1600 rev = ctx.rev()
1600 rev = ctx.rev()
1601 if rev in results:
1601 if rev in results:
1602 ui.status(_("found revision %s from %s\n") %
1602 ui.status(_("found revision %s from %s\n") %
1603 (rev, util.datestr(results[rev])))
1603 (rev, util.datestr(results[rev])))
1604 return '%d' % rev
1604 return '%d' % rev
1605
1605
1606 raise error.Abort(_("revision matching date not found"))
1606 raise error.Abort(_("revision matching date not found"))
1607
1607
1608 def increasingwindows(windowsize=8, sizelimit=512):
1608 def increasingwindows(windowsize=8, sizelimit=512):
1609 while True:
1609 while True:
1610 yield windowsize
1610 yield windowsize
1611 if windowsize < sizelimit:
1611 if windowsize < sizelimit:
1612 windowsize *= 2
1612 windowsize *= 2
1613
1613
1614 def _walkrevs(repo, opts):
1614 def _walkrevs(repo, opts):
1615 # Default --rev value depends on --follow but --follow behavior
1615 # Default --rev value depends on --follow but --follow behavior
1616 # depends on revisions resolved from --rev...
1616 # depends on revisions resolved from --rev...
1617 follow = opts.get('follow') or opts.get('follow_first')
1617 follow = opts.get('follow') or opts.get('follow_first')
1618 if opts.get('rev'):
1618 if opts.get('rev'):
1619 revs = scmutil.revrange(repo, opts['rev'])
1619 revs = scmutil.revrange(repo, opts['rev'])
1620 elif follow and repo.dirstate.p1() == nullid:
1620 elif follow and repo.dirstate.p1() == nullid:
1621 revs = smartset.baseset()
1621 revs = smartset.baseset()
1622 elif follow:
1622 elif follow:
1623 revs = repo.revs('reverse(:.)')
1623 revs = repo.revs('reverse(:.)')
1624 else:
1624 else:
1625 revs = smartset.spanset(repo)
1625 revs = smartset.spanset(repo)
1626 revs.reverse()
1626 revs.reverse()
1627 return revs
1627 return revs
1628
1628
1629 class FileWalkError(Exception):
1629 class FileWalkError(Exception):
1630 pass
1630 pass
1631
1631
1632 def walkfilerevs(repo, match, follow, revs, fncache):
1632 def walkfilerevs(repo, match, follow, revs, fncache):
1633 '''Walks the file history for the matched files.
1633 '''Walks the file history for the matched files.
1634
1634
1635 Returns the changeset revs that are involved in the file history.
1635 Returns the changeset revs that are involved in the file history.
1636
1636
1637 Throws FileWalkError if the file history can't be walked using
1637 Throws FileWalkError if the file history can't be walked using
1638 filelogs alone.
1638 filelogs alone.
1639 '''
1639 '''
1640 wanted = set()
1640 wanted = set()
1641 copies = []
1641 copies = []
1642 minrev, maxrev = min(revs), max(revs)
1642 minrev, maxrev = min(revs), max(revs)
1643 def filerevgen(filelog, last):
1643 def filerevgen(filelog, last):
1644 """
1644 """
1645 Only files, no patterns. Check the history of each file.
1645 Only files, no patterns. Check the history of each file.
1646
1646
1647 Examines filelog entries within minrev, maxrev linkrev range
1647 Examines filelog entries within minrev, maxrev linkrev range
1648 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1648 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1649 tuples in backwards order
1649 tuples in backwards order
1650 """
1650 """
1651 cl_count = len(repo)
1651 cl_count = len(repo)
1652 revs = []
1652 revs = []
1653 for j in xrange(0, last + 1):
1653 for j in xrange(0, last + 1):
1654 linkrev = filelog.linkrev(j)
1654 linkrev = filelog.linkrev(j)
1655 if linkrev < minrev:
1655 if linkrev < minrev:
1656 continue
1656 continue
1657 # only yield rev for which we have the changelog, it can
1657 # only yield rev for which we have the changelog, it can
1658 # happen while doing "hg log" during a pull or commit
1658 # happen while doing "hg log" during a pull or commit
1659 if linkrev >= cl_count:
1659 if linkrev >= cl_count:
1660 break
1660 break
1661
1661
1662 parentlinkrevs = []
1662 parentlinkrevs = []
1663 for p in filelog.parentrevs(j):
1663 for p in filelog.parentrevs(j):
1664 if p != nullrev:
1664 if p != nullrev:
1665 parentlinkrevs.append(filelog.linkrev(p))
1665 parentlinkrevs.append(filelog.linkrev(p))
1666 n = filelog.node(j)
1666 n = filelog.node(j)
1667 revs.append((linkrev, parentlinkrevs,
1667 revs.append((linkrev, parentlinkrevs,
1668 follow and filelog.renamed(n)))
1668 follow and filelog.renamed(n)))
1669
1669
1670 return reversed(revs)
1670 return reversed(revs)
1671 def iterfiles():
1671 def iterfiles():
1672 pctx = repo['.']
1672 pctx = repo['.']
1673 for filename in match.files():
1673 for filename in match.files():
1674 if follow:
1674 if follow:
1675 if filename not in pctx:
1675 if filename not in pctx:
1676 raise error.Abort(_('cannot follow file not in parent '
1676 raise error.Abort(_('cannot follow file not in parent '
1677 'revision: "%s"') % filename)
1677 'revision: "%s"') % filename)
1678 yield filename, pctx[filename].filenode()
1678 yield filename, pctx[filename].filenode()
1679 else:
1679 else:
1680 yield filename, None
1680 yield filename, None
1681 for filename_node in copies:
1681 for filename_node in copies:
1682 yield filename_node
1682 yield filename_node
1683
1683
1684 for file_, node in iterfiles():
1684 for file_, node in iterfiles():
1685 filelog = repo.file(file_)
1685 filelog = repo.file(file_)
1686 if not len(filelog):
1686 if not len(filelog):
1687 if node is None:
1687 if node is None:
1688 # A zero count may be a directory or deleted file, so
1688 # A zero count may be a directory or deleted file, so
1689 # try to find matching entries on the slow path.
1689 # try to find matching entries on the slow path.
1690 if follow:
1690 if follow:
1691 raise error.Abort(
1691 raise error.Abort(
1692 _('cannot follow nonexistent file: "%s"') % file_)
1692 _('cannot follow nonexistent file: "%s"') % file_)
1693 raise FileWalkError("Cannot walk via filelog")
1693 raise FileWalkError("Cannot walk via filelog")
1694 else:
1694 else:
1695 continue
1695 continue
1696
1696
1697 if node is None:
1697 if node is None:
1698 last = len(filelog) - 1
1698 last = len(filelog) - 1
1699 else:
1699 else:
1700 last = filelog.rev(node)
1700 last = filelog.rev(node)
1701
1701
1702 # keep track of all ancestors of the file
1702 # keep track of all ancestors of the file
1703 ancestors = {filelog.linkrev(last)}
1703 ancestors = {filelog.linkrev(last)}
1704
1704
1705 # iterate from latest to oldest revision
1705 # iterate from latest to oldest revision
1706 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1706 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1707 if not follow:
1707 if not follow:
1708 if rev > maxrev:
1708 if rev > maxrev:
1709 continue
1709 continue
1710 else:
1710 else:
1711 # Note that last might not be the first interesting
1711 # Note that last might not be the first interesting
1712 # rev to us:
1712 # rev to us:
1713 # if the file has been changed after maxrev, we'll
1713 # if the file has been changed after maxrev, we'll
1714 # have linkrev(last) > maxrev, and we still need
1714 # have linkrev(last) > maxrev, and we still need
1715 # to explore the file graph
1715 # to explore the file graph
1716 if rev not in ancestors:
1716 if rev not in ancestors:
1717 continue
1717 continue
1718 # XXX insert 1327 fix here
1718 # XXX insert 1327 fix here
1719 if flparentlinkrevs:
1719 if flparentlinkrevs:
1720 ancestors.update(flparentlinkrevs)
1720 ancestors.update(flparentlinkrevs)
1721
1721
1722 fncache.setdefault(rev, []).append(file_)
1722 fncache.setdefault(rev, []).append(file_)
1723 wanted.add(rev)
1723 wanted.add(rev)
1724 if copied:
1724 if copied:
1725 copies.append(copied)
1725 copies.append(copied)
1726
1726
1727 return wanted
1727 return wanted
1728
1728
1729 class _followfilter(object):
1729 class _followfilter(object):
1730 def __init__(self, repo, onlyfirst=False):
1730 def __init__(self, repo, onlyfirst=False):
1731 self.repo = repo
1731 self.repo = repo
1732 self.startrev = nullrev
1732 self.startrev = nullrev
1733 self.roots = set()
1733 self.roots = set()
1734 self.onlyfirst = onlyfirst
1734 self.onlyfirst = onlyfirst
1735
1735
1736 def match(self, rev):
1736 def match(self, rev):
1737 def realparents(rev):
1737 def realparents(rev):
1738 if self.onlyfirst:
1738 if self.onlyfirst:
1739 return self.repo.changelog.parentrevs(rev)[0:1]
1739 return self.repo.changelog.parentrevs(rev)[0:1]
1740 else:
1740 else:
1741 return filter(lambda x: x != nullrev,
1741 return filter(lambda x: x != nullrev,
1742 self.repo.changelog.parentrevs(rev))
1742 self.repo.changelog.parentrevs(rev))
1743
1743
1744 if self.startrev == nullrev:
1744 if self.startrev == nullrev:
1745 self.startrev = rev
1745 self.startrev = rev
1746 return True
1746 return True
1747
1747
1748 if rev > self.startrev:
1748 if rev > self.startrev:
1749 # forward: all descendants
1749 # forward: all descendants
1750 if not self.roots:
1750 if not self.roots:
1751 self.roots.add(self.startrev)
1751 self.roots.add(self.startrev)
1752 for parent in realparents(rev):
1752 for parent in realparents(rev):
1753 if parent in self.roots:
1753 if parent in self.roots:
1754 self.roots.add(rev)
1754 self.roots.add(rev)
1755 return True
1755 return True
1756 else:
1756 else:
1757 # backwards: all parents
1757 # backwards: all parents
1758 if not self.roots:
1758 if not self.roots:
1759 self.roots.update(realparents(self.startrev))
1759 self.roots.update(realparents(self.startrev))
1760 if rev in self.roots:
1760 if rev in self.roots:
1761 self.roots.remove(rev)
1761 self.roots.remove(rev)
1762 self.roots.update(realparents(rev))
1762 self.roots.update(realparents(rev))
1763 return True
1763 return True
1764
1764
1765 return False
1765 return False
1766
1766
1767 def walkchangerevs(repo, match, opts, prepare):
1767 def walkchangerevs(repo, match, opts, prepare):
1768 '''Iterate over files and the revs in which they changed.
1768 '''Iterate over files and the revs in which they changed.
1769
1769
1770 Callers most commonly need to iterate backwards over the history
1770 Callers most commonly need to iterate backwards over the history
1771 in which they are interested. Doing so has awful (quadratic-looking)
1771 in which they are interested. Doing so has awful (quadratic-looking)
1772 performance, so we use iterators in a "windowed" way.
1772 performance, so we use iterators in a "windowed" way.
1773
1773
1774 We walk a window of revisions in the desired order. Within the
1774 We walk a window of revisions in the desired order. Within the
1775 window, we first walk forwards to gather data, then in the desired
1775 window, we first walk forwards to gather data, then in the desired
1776 order (usually backwards) to display it.
1776 order (usually backwards) to display it.
1777
1777
1778 This function returns an iterator yielding contexts. Before
1778 This function returns an iterator yielding contexts. Before
1779 yielding each context, the iterator will first call the prepare
1779 yielding each context, the iterator will first call the prepare
1780 function on each context in the window in forward order.'''
1780 function on each context in the window in forward order.'''
1781
1781
1782 follow = opts.get('follow') or opts.get('follow_first')
1782 follow = opts.get('follow') or opts.get('follow_first')
1783 revs = _walkrevs(repo, opts)
1783 revs = _walkrevs(repo, opts)
1784 if not revs:
1784 if not revs:
1785 return []
1785 return []
1786 wanted = set()
1786 wanted = set()
1787 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1787 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1788 fncache = {}
1788 fncache = {}
1789 change = repo.changectx
1789 change = repo.changectx
1790
1790
1791 # First step is to fill wanted, the set of revisions that we want to yield.
1791 # First step is to fill wanted, the set of revisions that we want to yield.
1792 # When it does not induce extra cost, we also fill fncache for revisions in
1792 # When it does not induce extra cost, we also fill fncache for revisions in
1793 # wanted: a cache of filenames that were changed (ctx.files()) and that
1793 # wanted: a cache of filenames that were changed (ctx.files()) and that
1794 # match the file filtering conditions.
1794 # match the file filtering conditions.
1795
1795
1796 if match.always():
1796 if match.always():
1797 # No files, no patterns. Display all revs.
1797 # No files, no patterns. Display all revs.
1798 wanted = revs
1798 wanted = revs
1799 elif not slowpath:
1799 elif not slowpath:
1800 # We only have to read through the filelog to find wanted revisions
1800 # We only have to read through the filelog to find wanted revisions
1801
1801
1802 try:
1802 try:
1803 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1803 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1804 except FileWalkError:
1804 except FileWalkError:
1805 slowpath = True
1805 slowpath = True
1806
1806
1807 # We decided to fall back to the slowpath because at least one
1807 # We decided to fall back to the slowpath because at least one
1808 # of the paths was not a file. Check to see if at least one of them
1808 # of the paths was not a file. Check to see if at least one of them
1809 # existed in history, otherwise simply return
1809 # existed in history, otherwise simply return
1810 for path in match.files():
1810 for path in match.files():
1811 if path == '.' or path in repo.store:
1811 if path == '.' or path in repo.store:
1812 break
1812 break
1813 else:
1813 else:
1814 return []
1814 return []
1815
1815
1816 if slowpath:
1816 if slowpath:
1817 # We have to read the changelog to match filenames against
1817 # We have to read the changelog to match filenames against
1818 # changed files
1818 # changed files
1819
1819
1820 if follow:
1820 if follow:
1821 raise error.Abort(_('can only follow copies/renames for explicit '
1821 raise error.Abort(_('can only follow copies/renames for explicit '
1822 'filenames'))
1822 'filenames'))
1823
1823
1824 # The slow path checks files modified in every changeset.
1824 # The slow path checks files modified in every changeset.
1825 # This is really slow on large repos, so compute the set lazily.
1825 # This is really slow on large repos, so compute the set lazily.
1826 class lazywantedset(object):
1826 class lazywantedset(object):
1827 def __init__(self):
1827 def __init__(self):
1828 self.set = set()
1828 self.set = set()
1829 self.revs = set(revs)
1829 self.revs = set(revs)
1830
1830
1831 # No need to worry about locality here because it will be accessed
1831 # No need to worry about locality here because it will be accessed
1832 # in the same order as the increasing window below.
1832 # in the same order as the increasing window below.
1833 def __contains__(self, value):
1833 def __contains__(self, value):
1834 if value in self.set:
1834 if value in self.set:
1835 return True
1835 return True
1836 elif not value in self.revs:
1836 elif not value in self.revs:
1837 return False
1837 return False
1838 else:
1838 else:
1839 self.revs.discard(value)
1839 self.revs.discard(value)
1840 ctx = change(value)
1840 ctx = change(value)
1841 matches = filter(match, ctx.files())
1841 matches = filter(match, ctx.files())
1842 if matches:
1842 if matches:
1843 fncache[value] = matches
1843 fncache[value] = matches
1844 self.set.add(value)
1844 self.set.add(value)
1845 return True
1845 return True
1846 return False
1846 return False
1847
1847
1848 def discard(self, value):
1848 def discard(self, value):
1849 self.revs.discard(value)
1849 self.revs.discard(value)
1850 self.set.discard(value)
1850 self.set.discard(value)
1851
1851
1852 wanted = lazywantedset()
1852 wanted = lazywantedset()
1853
1853
1854 # it might be worthwhile to do this in the iterator if the rev range
1854 # it might be worthwhile to do this in the iterator if the rev range
1855 # is descending and the prune args are all within that range
1855 # is descending and the prune args are all within that range
1856 for rev in opts.get('prune', ()):
1856 for rev in opts.get('prune', ()):
1857 rev = repo[rev].rev()
1857 rev = repo[rev].rev()
1858 ff = _followfilter(repo)
1858 ff = _followfilter(repo)
1859 stop = min(revs[0], revs[-1])
1859 stop = min(revs[0], revs[-1])
1860 for x in xrange(rev, stop - 1, -1):
1860 for x in xrange(rev, stop - 1, -1):
1861 if ff.match(x):
1861 if ff.match(x):
1862 wanted = wanted - [x]
1862 wanted = wanted - [x]
1863
1863
1864 # Now that wanted is correctly initialized, we can iterate over the
1864 # Now that wanted is correctly initialized, we can iterate over the
1865 # revision range, yielding only revisions in wanted.
1865 # revision range, yielding only revisions in wanted.
1866 def iterate():
1866 def iterate():
1867 if follow and match.always():
1867 if follow and match.always():
1868 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1868 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1869 def want(rev):
1869 def want(rev):
1870 return ff.match(rev) and rev in wanted
1870 return ff.match(rev) and rev in wanted
1871 else:
1871 else:
1872 def want(rev):
1872 def want(rev):
1873 return rev in wanted
1873 return rev in wanted
1874
1874
1875 it = iter(revs)
1875 it = iter(revs)
1876 stopiteration = False
1876 stopiteration = False
1877 for windowsize in increasingwindows():
1877 for windowsize in increasingwindows():
1878 nrevs = []
1878 nrevs = []
1879 for i in xrange(windowsize):
1879 for i in xrange(windowsize):
1880 rev = next(it, None)
1880 rev = next(it, None)
1881 if rev is None:
1881 if rev is None:
1882 stopiteration = True
1882 stopiteration = True
1883 break
1883 break
1884 elif want(rev):
1884 elif want(rev):
1885 nrevs.append(rev)
1885 nrevs.append(rev)
1886 for rev in sorted(nrevs):
1886 for rev in sorted(nrevs):
1887 fns = fncache.get(rev)
1887 fns = fncache.get(rev)
1888 ctx = change(rev)
1888 ctx = change(rev)
1889 if not fns:
1889 if not fns:
1890 def fns_generator():
1890 def fns_generator():
1891 for f in ctx.files():
1891 for f in ctx.files():
1892 if match(f):
1892 if match(f):
1893 yield f
1893 yield f
1894 fns = fns_generator()
1894 fns = fns_generator()
1895 prepare(ctx, fns)
1895 prepare(ctx, fns)
1896 for rev in nrevs:
1896 for rev in nrevs:
1897 yield change(rev)
1897 yield change(rev)
1898
1898
1899 if stopiteration:
1899 if stopiteration:
1900 break
1900 break
1901
1901
1902 return iterate()
1902 return iterate()
1903
1903
1904 def add(ui, repo, match, prefix, explicitonly, **opts):
1904 def add(ui, repo, match, prefix, explicitonly, **opts):
1905 join = lambda f: os.path.join(prefix, f)
1905 join = lambda f: os.path.join(prefix, f)
1906 bad = []
1906 bad = []
1907
1907
1908 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1908 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1909 names = []
1909 names = []
1910 wctx = repo[None]
1910 wctx = repo[None]
1911 cca = None
1911 cca = None
1912 abort, warn = scmutil.checkportabilityalert(ui)
1912 abort, warn = scmutil.checkportabilityalert(ui)
1913 if abort or warn:
1913 if abort or warn:
1914 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1914 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1915
1915
1916 badmatch = matchmod.badmatch(match, badfn)
1916 badmatch = matchmod.badmatch(match, badfn)
1917 dirstate = repo.dirstate
1917 dirstate = repo.dirstate
1918 # We don't want to just call wctx.walk here, since it would return a lot of
1918 # We don't want to just call wctx.walk here, since it would return a lot of
1919 # clean files, which we aren't interested in and takes time.
1919 # clean files, which we aren't interested in and takes time.
1920 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1920 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1921 unknown=True, ignored=False, full=False)):
1921 unknown=True, ignored=False, full=False)):
1922 exact = match.exact(f)
1922 exact = match.exact(f)
1923 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1923 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1924 if cca:
1924 if cca:
1925 cca(f)
1925 cca(f)
1926 names.append(f)
1926 names.append(f)
1927 if ui.verbose or not exact:
1927 if ui.verbose or not exact:
1928 ui.status(_('adding %s\n') % match.rel(f))
1928 ui.status(_('adding %s\n') % match.rel(f))
1929
1929
1930 for subpath in sorted(wctx.substate):
1930 for subpath in sorted(wctx.substate):
1931 sub = wctx.sub(subpath)
1931 sub = wctx.sub(subpath)
1932 try:
1932 try:
1933 submatch = matchmod.subdirmatcher(subpath, match)
1933 submatch = matchmod.subdirmatcher(subpath, match)
1934 if opts.get(r'subrepos'):
1934 if opts.get(r'subrepos'):
1935 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1935 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1936 else:
1936 else:
1937 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1937 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1938 except error.LookupError:
1938 except error.LookupError:
1939 ui.status(_("skipping missing subrepository: %s\n")
1939 ui.status(_("skipping missing subrepository: %s\n")
1940 % join(subpath))
1940 % join(subpath))
1941
1941
1942 if not opts.get(r'dry_run'):
1942 if not opts.get(r'dry_run'):
1943 rejected = wctx.add(names, prefix)
1943 rejected = wctx.add(names, prefix)
1944 bad.extend(f for f in rejected if f in match.files())
1944 bad.extend(f for f in rejected if f in match.files())
1945 return bad
1945 return bad
1946
1946
1947 def addwebdirpath(repo, serverpath, webconf):
1947 def addwebdirpath(repo, serverpath, webconf):
1948 webconf[serverpath] = repo.root
1948 webconf[serverpath] = repo.root
1949 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
1949 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
1950
1950
1951 for r in repo.revs('filelog("path:.hgsub")'):
1951 for r in repo.revs('filelog("path:.hgsub")'):
1952 ctx = repo[r]
1952 ctx = repo[r]
1953 for subpath in ctx.substate:
1953 for subpath in ctx.substate:
1954 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
1954 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
1955
1955
1956 def forget(ui, repo, match, prefix, explicitonly):
1956 def forget(ui, repo, match, prefix, explicitonly):
1957 join = lambda f: os.path.join(prefix, f)
1957 join = lambda f: os.path.join(prefix, f)
1958 bad = []
1958 bad = []
1959 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1959 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1960 wctx = repo[None]
1960 wctx = repo[None]
1961 forgot = []
1961 forgot = []
1962
1962
1963 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
1963 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
1964 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1964 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1965 if explicitonly:
1965 if explicitonly:
1966 forget = [f for f in forget if match.exact(f)]
1966 forget = [f for f in forget if match.exact(f)]
1967
1967
1968 for subpath in sorted(wctx.substate):
1968 for subpath in sorted(wctx.substate):
1969 sub = wctx.sub(subpath)
1969 sub = wctx.sub(subpath)
1970 try:
1970 try:
1971 submatch = matchmod.subdirmatcher(subpath, match)
1971 submatch = matchmod.subdirmatcher(subpath, match)
1972 subbad, subforgot = sub.forget(submatch, prefix)
1972 subbad, subforgot = sub.forget(submatch, prefix)
1973 bad.extend([subpath + '/' + f for f in subbad])
1973 bad.extend([subpath + '/' + f for f in subbad])
1974 forgot.extend([subpath + '/' + f for f in subforgot])
1974 forgot.extend([subpath + '/' + f for f in subforgot])
1975 except error.LookupError:
1975 except error.LookupError:
1976 ui.status(_("skipping missing subrepository: %s\n")
1976 ui.status(_("skipping missing subrepository: %s\n")
1977 % join(subpath))
1977 % join(subpath))
1978
1978
1979 if not explicitonly:
1979 if not explicitonly:
1980 for f in match.files():
1980 for f in match.files():
1981 if f not in repo.dirstate and not repo.wvfs.isdir(f):
1981 if f not in repo.dirstate and not repo.wvfs.isdir(f):
1982 if f not in forgot:
1982 if f not in forgot:
1983 if repo.wvfs.exists(f):
1983 if repo.wvfs.exists(f):
1984 # Don't complain if the exact case match wasn't given.
1984 # Don't complain if the exact case match wasn't given.
1985 # But don't do this until after checking 'forgot', so
1985 # But don't do this until after checking 'forgot', so
1986 # that subrepo files aren't normalized, and this op is
1986 # that subrepo files aren't normalized, and this op is
1987 # purely from data cached by the status walk above.
1987 # purely from data cached by the status walk above.
1988 if repo.dirstate.normalize(f) in repo.dirstate:
1988 if repo.dirstate.normalize(f) in repo.dirstate:
1989 continue
1989 continue
1990 ui.warn(_('not removing %s: '
1990 ui.warn(_('not removing %s: '
1991 'file is already untracked\n')
1991 'file is already untracked\n')
1992 % match.rel(f))
1992 % match.rel(f))
1993 bad.append(f)
1993 bad.append(f)
1994
1994
1995 for f in forget:
1995 for f in forget:
1996 if ui.verbose or not match.exact(f):
1996 if ui.verbose or not match.exact(f):
1997 ui.status(_('removing %s\n') % match.rel(f))
1997 ui.status(_('removing %s\n') % match.rel(f))
1998
1998
1999 rejected = wctx.forget(forget, prefix)
1999 rejected = wctx.forget(forget, prefix)
2000 bad.extend(f for f in rejected if f in match.files())
2000 bad.extend(f for f in rejected if f in match.files())
2001 forgot.extend(f for f in forget if f not in rejected)
2001 forgot.extend(f for f in forget if f not in rejected)
2002 return bad, forgot
2002 return bad, forgot
2003
2003
2004 def files(ui, ctx, m, fm, fmt, subrepos):
2004 def files(ui, ctx, m, fm, fmt, subrepos):
2005 rev = ctx.rev()
2005 rev = ctx.rev()
2006 ret = 1
2006 ret = 1
2007 ds = ctx.repo().dirstate
2007 ds = ctx.repo().dirstate
2008
2008
2009 for f in ctx.matches(m):
2009 for f in ctx.matches(m):
2010 if rev is None and ds[f] == 'r':
2010 if rev is None and ds[f] == 'r':
2011 continue
2011 continue
2012 fm.startitem()
2012 fm.startitem()
2013 if ui.verbose:
2013 if ui.verbose:
2014 fc = ctx[f]
2014 fc = ctx[f]
2015 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2015 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2016 fm.data(abspath=f)
2016 fm.data(abspath=f)
2017 fm.write('path', fmt, m.rel(f))
2017 fm.write('path', fmt, m.rel(f))
2018 ret = 0
2018 ret = 0
2019
2019
2020 for subpath in sorted(ctx.substate):
2020 for subpath in sorted(ctx.substate):
2021 submatch = matchmod.subdirmatcher(subpath, m)
2021 submatch = matchmod.subdirmatcher(subpath, m)
2022 if (subrepos or m.exact(subpath) or any(submatch.files())):
2022 if (subrepos or m.exact(subpath) or any(submatch.files())):
2023 sub = ctx.sub(subpath)
2023 sub = ctx.sub(subpath)
2024 try:
2024 try:
2025 recurse = m.exact(subpath) or subrepos
2025 recurse = m.exact(subpath) or subrepos
2026 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2026 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2027 ret = 0
2027 ret = 0
2028 except error.LookupError:
2028 except error.LookupError:
2029 ui.status(_("skipping missing subrepository: %s\n")
2029 ui.status(_("skipping missing subrepository: %s\n")
2030 % m.abs(subpath))
2030 % m.abs(subpath))
2031
2031
2032 return ret
2032 return ret
2033
2033
2034 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2034 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2035 join = lambda f: os.path.join(prefix, f)
2035 join = lambda f: os.path.join(prefix, f)
2036 ret = 0
2036 ret = 0
2037 s = repo.status(match=m, clean=True)
2037 s = repo.status(match=m, clean=True)
2038 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2038 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2039
2039
2040 wctx = repo[None]
2040 wctx = repo[None]
2041
2041
2042 if warnings is None:
2042 if warnings is None:
2043 warnings = []
2043 warnings = []
2044 warn = True
2044 warn = True
2045 else:
2045 else:
2046 warn = False
2046 warn = False
2047
2047
2048 subs = sorted(wctx.substate)
2048 subs = sorted(wctx.substate)
2049 total = len(subs)
2049 total = len(subs)
2050 count = 0
2050 count = 0
2051 for subpath in subs:
2051 for subpath in subs:
2052 count += 1
2052 count += 1
2053 submatch = matchmod.subdirmatcher(subpath, m)
2053 submatch = matchmod.subdirmatcher(subpath, m)
2054 if subrepos or m.exact(subpath) or any(submatch.files()):
2054 if subrepos or m.exact(subpath) or any(submatch.files()):
2055 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2055 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2056 sub = wctx.sub(subpath)
2056 sub = wctx.sub(subpath)
2057 try:
2057 try:
2058 if sub.removefiles(submatch, prefix, after, force, subrepos,
2058 if sub.removefiles(submatch, prefix, after, force, subrepos,
2059 warnings):
2059 warnings):
2060 ret = 1
2060 ret = 1
2061 except error.LookupError:
2061 except error.LookupError:
2062 warnings.append(_("skipping missing subrepository: %s\n")
2062 warnings.append(_("skipping missing subrepository: %s\n")
2063 % join(subpath))
2063 % join(subpath))
2064 ui.progress(_('searching'), None)
2064 ui.progress(_('searching'), None)
2065
2065
2066 # warn about failure to delete explicit files/dirs
2066 # warn about failure to delete explicit files/dirs
2067 deleteddirs = util.dirs(deleted)
2067 deleteddirs = util.dirs(deleted)
2068 files = m.files()
2068 files = m.files()
2069 total = len(files)
2069 total = len(files)
2070 count = 0
2070 count = 0
2071 for f in files:
2071 for f in files:
2072 def insubrepo():
2072 def insubrepo():
2073 for subpath in wctx.substate:
2073 for subpath in wctx.substate:
2074 if f.startswith(subpath + '/'):
2074 if f.startswith(subpath + '/'):
2075 return True
2075 return True
2076 return False
2076 return False
2077
2077
2078 count += 1
2078 count += 1
2079 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2079 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2080 isdir = f in deleteddirs or wctx.hasdir(f)
2080 isdir = f in deleteddirs or wctx.hasdir(f)
2081 if (f in repo.dirstate or isdir or f == '.'
2081 if (f in repo.dirstate or isdir or f == '.'
2082 or insubrepo() or f in subs):
2082 or insubrepo() or f in subs):
2083 continue
2083 continue
2084
2084
2085 if repo.wvfs.exists(f):
2085 if repo.wvfs.exists(f):
2086 if repo.wvfs.isdir(f):
2086 if repo.wvfs.isdir(f):
2087 warnings.append(_('not removing %s: no tracked files\n')
2087 warnings.append(_('not removing %s: no tracked files\n')
2088 % m.rel(f))
2088 % m.rel(f))
2089 else:
2089 else:
2090 warnings.append(_('not removing %s: file is untracked\n')
2090 warnings.append(_('not removing %s: file is untracked\n')
2091 % m.rel(f))
2091 % m.rel(f))
2092 # missing files will generate a warning elsewhere
2092 # missing files will generate a warning elsewhere
2093 ret = 1
2093 ret = 1
2094 ui.progress(_('deleting'), None)
2094 ui.progress(_('deleting'), None)
2095
2095
2096 if force:
2096 if force:
2097 list = modified + deleted + clean + added
2097 list = modified + deleted + clean + added
2098 elif after:
2098 elif after:
2099 list = deleted
2099 list = deleted
2100 remaining = modified + added + clean
2100 remaining = modified + added + clean
2101 total = len(remaining)
2101 total = len(remaining)
2102 count = 0
2102 count = 0
2103 for f in remaining:
2103 for f in remaining:
2104 count += 1
2104 count += 1
2105 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2105 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2106 if ui.verbose or (f in files):
2106 if ui.verbose or (f in files):
2107 warnings.append(_('not removing %s: file still exists\n')
2107 warnings.append(_('not removing %s: file still exists\n')
2108 % m.rel(f))
2108 % m.rel(f))
2109 ret = 1
2109 ret = 1
2110 ui.progress(_('skipping'), None)
2110 ui.progress(_('skipping'), None)
2111 else:
2111 else:
2112 list = deleted + clean
2112 list = deleted + clean
2113 total = len(modified) + len(added)
2113 total = len(modified) + len(added)
2114 count = 0
2114 count = 0
2115 for f in modified:
2115 for f in modified:
2116 count += 1
2116 count += 1
2117 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2117 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2118 warnings.append(_('not removing %s: file is modified (use -f'
2118 warnings.append(_('not removing %s: file is modified (use -f'
2119 ' to force removal)\n') % m.rel(f))
2119 ' to force removal)\n') % m.rel(f))
2120 ret = 1
2120 ret = 1
2121 for f in added:
2121 for f in added:
2122 count += 1
2122 count += 1
2123 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2123 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2124 warnings.append(_("not removing %s: file has been marked for add"
2124 warnings.append(_("not removing %s: file has been marked for add"
2125 " (use 'hg forget' to undo add)\n") % m.rel(f))
2125 " (use 'hg forget' to undo add)\n") % m.rel(f))
2126 ret = 1
2126 ret = 1
2127 ui.progress(_('skipping'), None)
2127 ui.progress(_('skipping'), None)
2128
2128
2129 list = sorted(list)
2129 list = sorted(list)
2130 total = len(list)
2130 total = len(list)
2131 count = 0
2131 count = 0
2132 for f in list:
2132 for f in list:
2133 count += 1
2133 count += 1
2134 if ui.verbose or not m.exact(f):
2134 if ui.verbose or not m.exact(f):
2135 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2135 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2136 ui.status(_('removing %s\n') % m.rel(f))
2136 ui.status(_('removing %s\n') % m.rel(f))
2137 ui.progress(_('deleting'), None)
2137 ui.progress(_('deleting'), None)
2138
2138
2139 with repo.wlock():
2139 with repo.wlock():
2140 if not after:
2140 if not after:
2141 for f in list:
2141 for f in list:
2142 if f in added:
2142 if f in added:
2143 continue # we never unlink added files on remove
2143 continue # we never unlink added files on remove
2144 repo.wvfs.unlinkpath(f, ignoremissing=True)
2144 repo.wvfs.unlinkpath(f, ignoremissing=True)
2145 repo[None].forget(list)
2145 repo[None].forget(list)
2146
2146
2147 if warn:
2147 if warn:
2148 for warning in warnings:
2148 for warning in warnings:
2149 ui.warn(warning)
2149 ui.warn(warning)
2150
2150
2151 return ret
2151 return ret
2152
2152
2153 def _updatecatformatter(fm, ctx, matcher, path, decode):
2153 def _updatecatformatter(fm, ctx, matcher, path, decode):
2154 """Hook for adding data to the formatter used by ``hg cat``.
2154 """Hook for adding data to the formatter used by ``hg cat``.
2155
2155
2156 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2156 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2157 this method first."""
2157 this method first."""
2158 data = ctx[path].data()
2158 data = ctx[path].data()
2159 if decode:
2159 if decode:
2160 data = ctx.repo().wwritedata(path, data)
2160 data = ctx.repo().wwritedata(path, data)
2161 fm.startitem()
2161 fm.startitem()
2162 fm.write('data', '%s', data)
2162 fm.write('data', '%s', data)
2163 fm.data(abspath=path, path=matcher.rel(path))
2163 fm.data(abspath=path, path=matcher.rel(path))
2164
2164
2165 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2165 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2166 err = 1
2166 err = 1
2167 opts = pycompat.byteskwargs(opts)
2167 opts = pycompat.byteskwargs(opts)
2168
2168
2169 def write(path):
2169 def write(path):
2170 filename = None
2170 filename = None
2171 if fntemplate:
2171 if fntemplate:
2172 filename = makefilename(repo, fntemplate, ctx.node(),
2172 filename = makefilename(repo, fntemplate, ctx.node(),
2173 pathname=os.path.join(prefix, path))
2173 pathname=os.path.join(prefix, path))
2174 # attempt to create the directory if it does not already exist
2174 # attempt to create the directory if it does not already exist
2175 try:
2175 try:
2176 os.makedirs(os.path.dirname(filename))
2176 os.makedirs(os.path.dirname(filename))
2177 except OSError:
2177 except OSError:
2178 pass
2178 pass
2179 with formatter.maybereopen(basefm, filename, opts) as fm:
2179 with formatter.maybereopen(basefm, filename, opts) as fm:
2180 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2180 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2181
2181
2182 # Automation often uses hg cat on single files, so special case it
2182 # Automation often uses hg cat on single files, so special case it
2183 # for performance to avoid the cost of parsing the manifest.
2183 # for performance to avoid the cost of parsing the manifest.
2184 if len(matcher.files()) == 1 and not matcher.anypats():
2184 if len(matcher.files()) == 1 and not matcher.anypats():
2185 file = matcher.files()[0]
2185 file = matcher.files()[0]
2186 mfl = repo.manifestlog
2186 mfl = repo.manifestlog
2187 mfnode = ctx.manifestnode()
2187 mfnode = ctx.manifestnode()
2188 try:
2188 try:
2189 if mfnode and mfl[mfnode].find(file)[0]:
2189 if mfnode and mfl[mfnode].find(file)[0]:
2190 scmutil.fileprefetchhooks(repo, ctx, [file])
2190 scmutil.fileprefetchhooks(repo, ctx, [file])
2191 write(file)
2191 write(file)
2192 return 0
2192 return 0
2193 except KeyError:
2193 except KeyError:
2194 pass
2194 pass
2195
2195
2196 files = [f for f in ctx.walk(matcher)]
2196 files = [f for f in ctx.walk(matcher)]
2197 scmutil.fileprefetchhooks(repo, ctx, files)
2197 scmutil.fileprefetchhooks(repo, ctx, files)
2198
2198
2199 for abs in files:
2199 for abs in files:
2200 write(abs)
2200 write(abs)
2201 err = 0
2201 err = 0
2202
2202
2203 for subpath in sorted(ctx.substate):
2203 for subpath in sorted(ctx.substate):
2204 sub = ctx.sub(subpath)
2204 sub = ctx.sub(subpath)
2205 try:
2205 try:
2206 submatch = matchmod.subdirmatcher(subpath, matcher)
2206 submatch = matchmod.subdirmatcher(subpath, matcher)
2207
2207
2208 if not sub.cat(submatch, basefm, fntemplate,
2208 if not sub.cat(submatch, basefm, fntemplate,
2209 os.path.join(prefix, sub._path),
2209 os.path.join(prefix, sub._path),
2210 **pycompat.strkwargs(opts)):
2210 **pycompat.strkwargs(opts)):
2211 err = 0
2211 err = 0
2212 except error.RepoLookupError:
2212 except error.RepoLookupError:
2213 ui.status(_("skipping missing subrepository: %s\n")
2213 ui.status(_("skipping missing subrepository: %s\n")
2214 % os.path.join(prefix, subpath))
2214 % os.path.join(prefix, subpath))
2215
2215
2216 return err
2216 return err
2217
2217
2218 def commit(ui, repo, commitfunc, pats, opts):
2218 def commit(ui, repo, commitfunc, pats, opts):
2219 '''commit the specified files or all outstanding changes'''
2219 '''commit the specified files or all outstanding changes'''
2220 date = opts.get('date')
2220 date = opts.get('date')
2221 if date:
2221 if date:
2222 opts['date'] = util.parsedate(date)
2222 opts['date'] = util.parsedate(date)
2223 message = logmessage(ui, opts)
2223 message = logmessage(ui, opts)
2224 matcher = scmutil.match(repo[None], pats, opts)
2224 matcher = scmutil.match(repo[None], pats, opts)
2225
2225
2226 dsguard = None
2226 dsguard = None
2227 # extract addremove carefully -- this function can be called from a command
2227 # extract addremove carefully -- this function can be called from a command
2228 # that doesn't support addremove
2228 # that doesn't support addremove
2229 if opts.get('addremove'):
2229 if opts.get('addremove'):
2230 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2230 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2231 with dsguard or util.nullcontextmanager():
2231 with dsguard or util.nullcontextmanager():
2232 if dsguard:
2232 if dsguard:
2233 if scmutil.addremove(repo, matcher, "", opts) != 0:
2233 if scmutil.addremove(repo, matcher, "", opts) != 0:
2234 raise error.Abort(
2234 raise error.Abort(
2235 _("failed to mark all new/missing files as added/removed"))
2235 _("failed to mark all new/missing files as added/removed"))
2236
2236
2237 return commitfunc(ui, repo, message, matcher, opts)
2237 return commitfunc(ui, repo, message, matcher, opts)
2238
2238
2239 def samefile(f, ctx1, ctx2):
2239 def samefile(f, ctx1, ctx2):
2240 if f in ctx1.manifest():
2240 if f in ctx1.manifest():
2241 a = ctx1.filectx(f)
2241 a = ctx1.filectx(f)
2242 if f in ctx2.manifest():
2242 if f in ctx2.manifest():
2243 b = ctx2.filectx(f)
2243 b = ctx2.filectx(f)
2244 return (not a.cmp(b)
2244 return (not a.cmp(b)
2245 and a.flags() == b.flags())
2245 and a.flags() == b.flags())
2246 else:
2246 else:
2247 return False
2247 return False
2248 else:
2248 else:
2249 return f not in ctx2.manifest()
2249 return f not in ctx2.manifest()
2250
2250
2251 def amend(ui, repo, old, extra, pats, opts):
2251 def amend(ui, repo, old, extra, pats, opts):
2252 # avoid cycle context -> subrepo -> cmdutil
2252 # avoid cycle context -> subrepo -> cmdutil
2253 from . import context
2253 from . import context
2254
2254
2255 # amend will reuse the existing user if not specified, but the obsolete
2255 # amend will reuse the existing user if not specified, but the obsolete
2256 # marker creation requires that the current user's name is specified.
2256 # marker creation requires that the current user's name is specified.
2257 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2257 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2258 ui.username() # raise exception if username not set
2258 ui.username() # raise exception if username not set
2259
2259
2260 ui.note(_('amending changeset %s\n') % old)
2260 ui.note(_('amending changeset %s\n') % old)
2261 base = old.p1()
2261 base = old.p1()
2262
2262
2263 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2263 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2264 # Participating changesets:
2264 # Participating changesets:
2265 #
2265 #
2266 # wctx o - workingctx that contains changes from working copy
2266 # wctx o - workingctx that contains changes from working copy
2267 # | to go into amending commit
2267 # | to go into amending commit
2268 # |
2268 # |
2269 # old o - changeset to amend
2269 # old o - changeset to amend
2270 # |
2270 # |
2271 # base o - first parent of the changeset to amend
2271 # base o - first parent of the changeset to amend
2272 wctx = repo[None]
2272 wctx = repo[None]
2273
2273
2274 # Copy to avoid mutating input
2274 # Copy to avoid mutating input
2275 extra = extra.copy()
2275 extra = extra.copy()
2276 # Update extra dict from amended commit (e.g. to preserve graft
2276 # Update extra dict from amended commit (e.g. to preserve graft
2277 # source)
2277 # source)
2278 extra.update(old.extra())
2278 extra.update(old.extra())
2279
2279
2280 # Also update it from the from the wctx
2280 # Also update it from the from the wctx
2281 extra.update(wctx.extra())
2281 extra.update(wctx.extra())
2282
2282
2283 user = opts.get('user') or old.user()
2283 user = opts.get('user') or old.user()
2284 date = opts.get('date') or old.date()
2284 date = opts.get('date') or old.date()
2285
2285
2286 # Parse the date to allow comparison between date and old.date()
2286 # Parse the date to allow comparison between date and old.date()
2287 date = util.parsedate(date)
2287 date = util.parsedate(date)
2288
2288
2289 if len(old.parents()) > 1:
2289 if len(old.parents()) > 1:
2290 # ctx.files() isn't reliable for merges, so fall back to the
2290 # ctx.files() isn't reliable for merges, so fall back to the
2291 # slower repo.status() method
2291 # slower repo.status() method
2292 files = set([fn for st in repo.status(base, old)[:3]
2292 files = set([fn for st in repo.status(base, old)[:3]
2293 for fn in st])
2293 for fn in st])
2294 else:
2294 else:
2295 files = set(old.files())
2295 files = set(old.files())
2296
2296
2297 # add/remove the files to the working copy if the "addremove" option
2297 # add/remove the files to the working copy if the "addremove" option
2298 # was specified.
2298 # was specified.
2299 matcher = scmutil.match(wctx, pats, opts)
2299 matcher = scmutil.match(wctx, pats, opts)
2300 if (opts.get('addremove')
2300 if (opts.get('addremove')
2301 and scmutil.addremove(repo, matcher, "", opts)):
2301 and scmutil.addremove(repo, matcher, "", opts)):
2302 raise error.Abort(
2302 raise error.Abort(
2303 _("failed to mark all new/missing files as added/removed"))
2303 _("failed to mark all new/missing files as added/removed"))
2304
2304
2305 # Check subrepos. This depends on in-place wctx._status update in
2305 # Check subrepos. This depends on in-place wctx._status update in
2306 # subrepo.precommit(). To minimize the risk of this hack, we do
2306 # subrepo.precommit(). To minimize the risk of this hack, we do
2307 # nothing if .hgsub does not exist.
2307 # nothing if .hgsub does not exist.
2308 if '.hgsub' in wctx or '.hgsub' in old:
2308 if '.hgsub' in wctx or '.hgsub' in old:
2309 subs, commitsubs, newsubstate = subrepoutil.precommit(
2309 subs, commitsubs, newsubstate = subrepoutil.precommit(
2310 ui, wctx, wctx._status, matcher)
2310 ui, wctx, wctx._status, matcher)
2311 # amend should abort if commitsubrepos is enabled
2311 # amend should abort if commitsubrepos is enabled
2312 assert not commitsubs
2312 assert not commitsubs
2313 if subs:
2313 if subs:
2314 subrepoutil.writestate(repo, newsubstate)
2314 subrepoutil.writestate(repo, newsubstate)
2315
2315
2316 filestoamend = set(f for f in wctx.files() if matcher(f))
2316 filestoamend = set(f for f in wctx.files() if matcher(f))
2317
2317
2318 changes = (len(filestoamend) > 0)
2318 changes = (len(filestoamend) > 0)
2319 if changes:
2319 if changes:
2320 # Recompute copies (avoid recording a -> b -> a)
2320 # Recompute copies (avoid recording a -> b -> a)
2321 copied = copies.pathcopies(base, wctx, matcher)
2321 copied = copies.pathcopies(base, wctx, matcher)
2322 if old.p2:
2322 if old.p2:
2323 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2323 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2324
2324
2325 # Prune files which were reverted by the updates: if old
2325 # Prune files which were reverted by the updates: if old
2326 # introduced file X and the file was renamed in the working
2326 # introduced file X and the file was renamed in the working
2327 # copy, then those two files are the same and
2327 # copy, then those two files are the same and
2328 # we can discard X from our list of files. Likewise if X
2328 # we can discard X from our list of files. Likewise if X
2329 # was removed, it's no longer relevant. If X is missing (aka
2329 # was removed, it's no longer relevant. If X is missing (aka
2330 # deleted), old X must be preserved.
2330 # deleted), old X must be preserved.
2331 files.update(filestoamend)
2331 files.update(filestoamend)
2332 files = [f for f in files if (not samefile(f, wctx, base)
2332 files = [f for f in files if (not samefile(f, wctx, base)
2333 or f in wctx.deleted())]
2333 or f in wctx.deleted())]
2334
2334
2335 def filectxfn(repo, ctx_, path):
2335 def filectxfn(repo, ctx_, path):
2336 try:
2336 try:
2337 # If the file being considered is not amongst the files
2337 # If the file being considered is not amongst the files
2338 # to be amended, we should return the file context from the
2338 # to be amended, we should return the file context from the
2339 # old changeset. This avoids issues when only some files in
2339 # old changeset. This avoids issues when only some files in
2340 # the working copy are being amended but there are also
2340 # the working copy are being amended but there are also
2341 # changes to other files from the old changeset.
2341 # changes to other files from the old changeset.
2342 if path not in filestoamend:
2342 if path not in filestoamend:
2343 return old.filectx(path)
2343 return old.filectx(path)
2344
2344
2345 # Return None for removed files.
2345 # Return None for removed files.
2346 if path in wctx.removed():
2346 if path in wctx.removed():
2347 return None
2347 return None
2348
2348
2349 fctx = wctx[path]
2349 fctx = wctx[path]
2350 flags = fctx.flags()
2350 flags = fctx.flags()
2351 mctx = context.memfilectx(repo, ctx_,
2351 mctx = context.memfilectx(repo, ctx_,
2352 fctx.path(), fctx.data(),
2352 fctx.path(), fctx.data(),
2353 islink='l' in flags,
2353 islink='l' in flags,
2354 isexec='x' in flags,
2354 isexec='x' in flags,
2355 copied=copied.get(path))
2355 copied=copied.get(path))
2356 return mctx
2356 return mctx
2357 except KeyError:
2357 except KeyError:
2358 return None
2358 return None
2359 else:
2359 else:
2360 ui.note(_('copying changeset %s to %s\n') % (old, base))
2360 ui.note(_('copying changeset %s to %s\n') % (old, base))
2361
2361
2362 # Use version of files as in the old cset
2362 # Use version of files as in the old cset
2363 def filectxfn(repo, ctx_, path):
2363 def filectxfn(repo, ctx_, path):
2364 try:
2364 try:
2365 return old.filectx(path)
2365 return old.filectx(path)
2366 except KeyError:
2366 except KeyError:
2367 return None
2367 return None
2368
2368
2369 # See if we got a message from -m or -l, if not, open the editor with
2369 # See if we got a message from -m or -l, if not, open the editor with
2370 # the message of the changeset to amend.
2370 # the message of the changeset to amend.
2371 message = logmessage(ui, opts)
2371 message = logmessage(ui, opts)
2372
2372
2373 editform = mergeeditform(old, 'commit.amend')
2373 editform = mergeeditform(old, 'commit.amend')
2374 editor = getcommiteditor(editform=editform,
2374 editor = getcommiteditor(editform=editform,
2375 **pycompat.strkwargs(opts))
2375 **pycompat.strkwargs(opts))
2376
2376
2377 if not message:
2377 if not message:
2378 editor = getcommiteditor(edit=True, editform=editform)
2378 editor = getcommiteditor(edit=True, editform=editform)
2379 message = old.description()
2379 message = old.description()
2380
2380
2381 pureextra = extra.copy()
2381 pureextra = extra.copy()
2382 extra['amend_source'] = old.hex()
2382 extra['amend_source'] = old.hex()
2383
2383
2384 new = context.memctx(repo,
2384 new = context.memctx(repo,
2385 parents=[base.node(), old.p2().node()],
2385 parents=[base.node(), old.p2().node()],
2386 text=message,
2386 text=message,
2387 files=files,
2387 files=files,
2388 filectxfn=filectxfn,
2388 filectxfn=filectxfn,
2389 user=user,
2389 user=user,
2390 date=date,
2390 date=date,
2391 extra=extra,
2391 extra=extra,
2392 editor=editor)
2392 editor=editor)
2393
2393
2394 newdesc = changelog.stripdesc(new.description())
2394 newdesc = changelog.stripdesc(new.description())
2395 if ((not changes)
2395 if ((not changes)
2396 and newdesc == old.description()
2396 and newdesc == old.description()
2397 and user == old.user()
2397 and user == old.user()
2398 and date == old.date()
2398 and date == old.date()
2399 and pureextra == old.extra()):
2399 and pureextra == old.extra()):
2400 # nothing changed. continuing here would create a new node
2400 # nothing changed. continuing here would create a new node
2401 # anyway because of the amend_source noise.
2401 # anyway because of the amend_source noise.
2402 #
2402 #
2403 # This not what we expect from amend.
2403 # This not what we expect from amend.
2404 return old.node()
2404 return old.node()
2405
2405
2406 if opts.get('secret'):
2406 if opts.get('secret'):
2407 commitphase = 'secret'
2407 commitphase = 'secret'
2408 else:
2408 else:
2409 commitphase = old.phase()
2409 commitphase = old.phase()
2410 overrides = {('phases', 'new-commit'): commitphase}
2410 overrides = {('phases', 'new-commit'): commitphase}
2411 with ui.configoverride(overrides, 'amend'):
2411 with ui.configoverride(overrides, 'amend'):
2412 newid = repo.commitctx(new)
2412 newid = repo.commitctx(new)
2413
2413
2414 # Reroute the working copy parent to the new changeset
2414 # Reroute the working copy parent to the new changeset
2415 repo.setparents(newid, nullid)
2415 repo.setparents(newid, nullid)
2416 mapping = {old.node(): (newid,)}
2416 mapping = {old.node(): (newid,)}
2417 obsmetadata = None
2417 obsmetadata = None
2418 if opts.get('note'):
2418 if opts.get('note'):
2419 obsmetadata = {'note': opts['note']}
2419 obsmetadata = {'note': opts['note']}
2420 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2420 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2421
2421
2422 # Fixing the dirstate because localrepo.commitctx does not update
2422 # Fixing the dirstate because localrepo.commitctx does not update
2423 # it. This is rather convenient because we did not need to update
2423 # it. This is rather convenient because we did not need to update
2424 # the dirstate for all the files in the new commit which commitctx
2424 # the dirstate for all the files in the new commit which commitctx
2425 # could have done if it updated the dirstate. Now, we can
2425 # could have done if it updated the dirstate. Now, we can
2426 # selectively update the dirstate only for the amended files.
2426 # selectively update the dirstate only for the amended files.
2427 dirstate = repo.dirstate
2427 dirstate = repo.dirstate
2428
2428
2429 # Update the state of the files which were added and
2429 # Update the state of the files which were added and
2430 # and modified in the amend to "normal" in the dirstate.
2430 # and modified in the amend to "normal" in the dirstate.
2431 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2431 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2432 for f in normalfiles:
2432 for f in normalfiles:
2433 dirstate.normal(f)
2433 dirstate.normal(f)
2434
2434
2435 # Update the state of files which were removed in the amend
2435 # Update the state of files which were removed in the amend
2436 # to "removed" in the dirstate.
2436 # to "removed" in the dirstate.
2437 removedfiles = set(wctx.removed()) & filestoamend
2437 removedfiles = set(wctx.removed()) & filestoamend
2438 for f in removedfiles:
2438 for f in removedfiles:
2439 dirstate.drop(f)
2439 dirstate.drop(f)
2440
2440
2441 return newid
2441 return newid
2442
2442
2443 def commiteditor(repo, ctx, subs, editform=''):
2443 def commiteditor(repo, ctx, subs, editform=''):
2444 if ctx.description():
2444 if ctx.description():
2445 return ctx.description()
2445 return ctx.description()
2446 return commitforceeditor(repo, ctx, subs, editform=editform,
2446 return commitforceeditor(repo, ctx, subs, editform=editform,
2447 unchangedmessagedetection=True)
2447 unchangedmessagedetection=True)
2448
2448
2449 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2449 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2450 editform='', unchangedmessagedetection=False):
2450 editform='', unchangedmessagedetection=False):
2451 if not extramsg:
2451 if not extramsg:
2452 extramsg = _("Leave message empty to abort commit.")
2452 extramsg = _("Leave message empty to abort commit.")
2453
2453
2454 forms = [e for e in editform.split('.') if e]
2454 forms = [e for e in editform.split('.') if e]
2455 forms.insert(0, 'changeset')
2455 forms.insert(0, 'changeset')
2456 templatetext = None
2456 templatetext = None
2457 while forms:
2457 while forms:
2458 ref = '.'.join(forms)
2458 ref = '.'.join(forms)
2459 if repo.ui.config('committemplate', ref):
2459 if repo.ui.config('committemplate', ref):
2460 templatetext = committext = buildcommittemplate(
2460 templatetext = committext = buildcommittemplate(
2461 repo, ctx, subs, extramsg, ref)
2461 repo, ctx, subs, extramsg, ref)
2462 break
2462 break
2463 forms.pop()
2463 forms.pop()
2464 else:
2464 else:
2465 committext = buildcommittext(repo, ctx, subs, extramsg)
2465 committext = buildcommittext(repo, ctx, subs, extramsg)
2466
2466
2467 # run editor in the repository root
2467 # run editor in the repository root
2468 olddir = pycompat.getcwd()
2468 olddir = pycompat.getcwd()
2469 os.chdir(repo.root)
2469 os.chdir(repo.root)
2470
2470
2471 # make in-memory changes visible to external process
2471 # make in-memory changes visible to external process
2472 tr = repo.currenttransaction()
2472 tr = repo.currenttransaction()
2473 repo.dirstate.write(tr)
2473 repo.dirstate.write(tr)
2474 pending = tr and tr.writepending() and repo.root
2474 pending = tr and tr.writepending() and repo.root
2475
2475
2476 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2476 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2477 editform=editform, pending=pending,
2477 editform=editform, pending=pending,
2478 repopath=repo.path, action='commit')
2478 repopath=repo.path, action='commit')
2479 text = editortext
2479 text = editortext
2480
2480
2481 # strip away anything below this special string (used for editors that want
2481 # strip away anything below this special string (used for editors that want
2482 # to display the diff)
2482 # to display the diff)
2483 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2483 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2484 if stripbelow:
2484 if stripbelow:
2485 text = text[:stripbelow.start()]
2485 text = text[:stripbelow.start()]
2486
2486
2487 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2487 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2488 os.chdir(olddir)
2488 os.chdir(olddir)
2489
2489
2490 if finishdesc:
2490 if finishdesc:
2491 text = finishdesc(text)
2491 text = finishdesc(text)
2492 if not text.strip():
2492 if not text.strip():
2493 raise error.Abort(_("empty commit message"))
2493 raise error.Abort(_("empty commit message"))
2494 if unchangedmessagedetection and editortext == templatetext:
2494 if unchangedmessagedetection and editortext == templatetext:
2495 raise error.Abort(_("commit message unchanged"))
2495 raise error.Abort(_("commit message unchanged"))
2496
2496
2497 return text
2497 return text
2498
2498
2499 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2499 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2500 ui = repo.ui
2500 ui = repo.ui
2501 spec = formatter.templatespec(ref, None, None)
2501 spec = formatter.templatespec(ref, None, None)
2502 t = logcmdutil.changesettemplater(ui, repo, spec)
2502 t = logcmdutil.changesettemplater(ui, repo, spec)
2503 t.t.cache.update((k, templater.unquotestring(v))
2503 t.t.cache.update((k, templater.unquotestring(v))
2504 for k, v in repo.ui.configitems('committemplate'))
2504 for k, v in repo.ui.configitems('committemplate'))
2505
2505
2506 if not extramsg:
2506 if not extramsg:
2507 extramsg = '' # ensure that extramsg is string
2507 extramsg = '' # ensure that extramsg is string
2508
2508
2509 ui.pushbuffer()
2509 ui.pushbuffer()
2510 t.show(ctx, extramsg=extramsg)
2510 t.show(ctx, extramsg=extramsg)
2511 return ui.popbuffer()
2511 return ui.popbuffer()
2512
2512
2513 def hgprefix(msg):
2513 def hgprefix(msg):
2514 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2514 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2515
2515
2516 def buildcommittext(repo, ctx, subs, extramsg):
2516 def buildcommittext(repo, ctx, subs, extramsg):
2517 edittext = []
2517 edittext = []
2518 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2518 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2519 if ctx.description():
2519 if ctx.description():
2520 edittext.append(ctx.description())
2520 edittext.append(ctx.description())
2521 edittext.append("")
2521 edittext.append("")
2522 edittext.append("") # Empty line between message and comments.
2522 edittext.append("") # Empty line between message and comments.
2523 edittext.append(hgprefix(_("Enter commit message."
2523 edittext.append(hgprefix(_("Enter commit message."
2524 " Lines beginning with 'HG:' are removed.")))
2524 " Lines beginning with 'HG:' are removed.")))
2525 edittext.append(hgprefix(extramsg))
2525 edittext.append(hgprefix(extramsg))
2526 edittext.append("HG: --")
2526 edittext.append("HG: --")
2527 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2527 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2528 if ctx.p2():
2528 if ctx.p2():
2529 edittext.append(hgprefix(_("branch merge")))
2529 edittext.append(hgprefix(_("branch merge")))
2530 if ctx.branch():
2530 if ctx.branch():
2531 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2531 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2532 if bookmarks.isactivewdirparent(repo):
2532 if bookmarks.isactivewdirparent(repo):
2533 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2533 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2534 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2534 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2535 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2535 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2536 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2536 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2537 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2537 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2538 if not added and not modified and not removed:
2538 if not added and not modified and not removed:
2539 edittext.append(hgprefix(_("no files changed")))
2539 edittext.append(hgprefix(_("no files changed")))
2540 edittext.append("")
2540 edittext.append("")
2541
2541
2542 return "\n".join(edittext)
2542 return "\n".join(edittext)
2543
2543
2544 def commitstatus(repo, node, branch, bheads=None, opts=None):
2544 def commitstatus(repo, node, branch, bheads=None, opts=None):
2545 if opts is None:
2545 if opts is None:
2546 opts = {}
2546 opts = {}
2547 ctx = repo[node]
2547 ctx = repo[node]
2548 parents = ctx.parents()
2548 parents = ctx.parents()
2549
2549
2550 if (not opts.get('amend') and bheads and node not in bheads and not
2550 if (not opts.get('amend') and bheads and node not in bheads and not
2551 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2551 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2552 repo.ui.status(_('created new head\n'))
2552 repo.ui.status(_('created new head\n'))
2553 # The message is not printed for initial roots. For the other
2553 # The message is not printed for initial roots. For the other
2554 # changesets, it is printed in the following situations:
2554 # changesets, it is printed in the following situations:
2555 #
2555 #
2556 # Par column: for the 2 parents with ...
2556 # Par column: for the 2 parents with ...
2557 # N: null or no parent
2557 # N: null or no parent
2558 # B: parent is on another named branch
2558 # B: parent is on another named branch
2559 # C: parent is a regular non head changeset
2559 # C: parent is a regular non head changeset
2560 # H: parent was a branch head of the current branch
2560 # H: parent was a branch head of the current branch
2561 # Msg column: whether we print "created new head" message
2561 # Msg column: whether we print "created new head" message
2562 # In the following, it is assumed that there already exists some
2562 # In the following, it is assumed that there already exists some
2563 # initial branch heads of the current branch, otherwise nothing is
2563 # initial branch heads of the current branch, otherwise nothing is
2564 # printed anyway.
2564 # printed anyway.
2565 #
2565 #
2566 # Par Msg Comment
2566 # Par Msg Comment
2567 # N N y additional topo root
2567 # N N y additional topo root
2568 #
2568 #
2569 # B N y additional branch root
2569 # B N y additional branch root
2570 # C N y additional topo head
2570 # C N y additional topo head
2571 # H N n usual case
2571 # H N n usual case
2572 #
2572 #
2573 # B B y weird additional branch root
2573 # B B y weird additional branch root
2574 # C B y branch merge
2574 # C B y branch merge
2575 # H B n merge with named branch
2575 # H B n merge with named branch
2576 #
2576 #
2577 # C C y additional head from merge
2577 # C C y additional head from merge
2578 # C H n merge with a head
2578 # C H n merge with a head
2579 #
2579 #
2580 # H H n head merge: head count decreases
2580 # H H n head merge: head count decreases
2581
2581
2582 if not opts.get('close_branch'):
2582 if not opts.get('close_branch'):
2583 for r in parents:
2583 for r in parents:
2584 if r.closesbranch() and r.branch() == branch:
2584 if r.closesbranch() and r.branch() == branch:
2585 repo.ui.status(_('reopening closed branch head %d\n') % r)
2585 repo.ui.status(_('reopening closed branch head %d\n') % r)
2586
2586
2587 if repo.ui.debugflag:
2587 if repo.ui.debugflag:
2588 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2588 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2589 elif repo.ui.verbose:
2589 elif repo.ui.verbose:
2590 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2590 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2591
2591
2592 def postcommitstatus(repo, pats, opts):
2592 def postcommitstatus(repo, pats, opts):
2593 return repo.status(match=scmutil.match(repo[None], pats, opts))
2593 return repo.status(match=scmutil.match(repo[None], pats, opts))
2594
2594
2595 def revert(ui, repo, ctx, parents, *pats, **opts):
2595 def revert(ui, repo, ctx, parents, *pats, **opts):
2596 opts = pycompat.byteskwargs(opts)
2596 opts = pycompat.byteskwargs(opts)
2597 parent, p2 = parents
2597 parent, p2 = parents
2598 node = ctx.node()
2598 node = ctx.node()
2599
2599
2600 mf = ctx.manifest()
2600 mf = ctx.manifest()
2601 if node == p2:
2601 if node == p2:
2602 parent = p2
2602 parent = p2
2603
2603
2604 # need all matching names in dirstate and manifest of target rev,
2604 # need all matching names in dirstate and manifest of target rev,
2605 # so have to walk both. do not print errors if files exist in one
2605 # so have to walk both. do not print errors if files exist in one
2606 # but not other. in both cases, filesets should be evaluated against
2606 # but not other. in both cases, filesets should be evaluated against
2607 # workingctx to get consistent result (issue4497). this means 'set:**'
2607 # workingctx to get consistent result (issue4497). this means 'set:**'
2608 # cannot be used to select missing files from target rev.
2608 # cannot be used to select missing files from target rev.
2609
2609
2610 # `names` is a mapping for all elements in working copy and target revision
2610 # `names` is a mapping for all elements in working copy and target revision
2611 # The mapping is in the form:
2611 # The mapping is in the form:
2612 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2612 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2613 names = {}
2613 names = {}
2614
2614
2615 with repo.wlock():
2615 with repo.wlock():
2616 ## filling of the `names` mapping
2616 ## filling of the `names` mapping
2617 # walk dirstate to fill `names`
2617 # walk dirstate to fill `names`
2618
2618
2619 interactive = opts.get('interactive', False)
2619 interactive = opts.get('interactive', False)
2620 wctx = repo[None]
2620 wctx = repo[None]
2621 m = scmutil.match(wctx, pats, opts)
2621 m = scmutil.match(wctx, pats, opts)
2622
2622
2623 # we'll need this later
2623 # we'll need this later
2624 targetsubs = sorted(s for s in wctx.substate if m(s))
2624 targetsubs = sorted(s for s in wctx.substate if m(s))
2625
2625
2626 if not m.always():
2626 if not m.always():
2627 matcher = matchmod.badmatch(m, lambda x, y: False)
2627 matcher = matchmod.badmatch(m, lambda x, y: False)
2628 for abs in wctx.walk(matcher):
2628 for abs in wctx.walk(matcher):
2629 names[abs] = m.rel(abs), m.exact(abs)
2629 names[abs] = m.rel(abs), m.exact(abs)
2630
2630
2631 # walk target manifest to fill `names`
2631 # walk target manifest to fill `names`
2632
2632
2633 def badfn(path, msg):
2633 def badfn(path, msg):
2634 if path in names:
2634 if path in names:
2635 return
2635 return
2636 if path in ctx.substate:
2636 if path in ctx.substate:
2637 return
2637 return
2638 path_ = path + '/'
2638 path_ = path + '/'
2639 for f in names:
2639 for f in names:
2640 if f.startswith(path_):
2640 if f.startswith(path_):
2641 return
2641 return
2642 ui.warn("%s: %s\n" % (m.rel(path), msg))
2642 ui.warn("%s: %s\n" % (m.rel(path), msg))
2643
2643
2644 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2644 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2645 if abs not in names:
2645 if abs not in names:
2646 names[abs] = m.rel(abs), m.exact(abs)
2646 names[abs] = m.rel(abs), m.exact(abs)
2647
2647
2648 # Find status of all file in `names`.
2648 # Find status of all file in `names`.
2649 m = scmutil.matchfiles(repo, names)
2649 m = scmutil.matchfiles(repo, names)
2650
2650
2651 changes = repo.status(node1=node, match=m,
2651 changes = repo.status(node1=node, match=m,
2652 unknown=True, ignored=True, clean=True)
2652 unknown=True, ignored=True, clean=True)
2653 else:
2653 else:
2654 changes = repo.status(node1=node, match=m)
2654 changes = repo.status(node1=node, match=m)
2655 for kind in changes:
2655 for kind in changes:
2656 for abs in kind:
2656 for abs in kind:
2657 names[abs] = m.rel(abs), m.exact(abs)
2657 names[abs] = m.rel(abs), m.exact(abs)
2658
2658
2659 m = scmutil.matchfiles(repo, names)
2659 m = scmutil.matchfiles(repo, names)
2660
2660
2661 modified = set(changes.modified)
2661 modified = set(changes.modified)
2662 added = set(changes.added)
2662 added = set(changes.added)
2663 removed = set(changes.removed)
2663 removed = set(changes.removed)
2664 _deleted = set(changes.deleted)
2664 _deleted = set(changes.deleted)
2665 unknown = set(changes.unknown)
2665 unknown = set(changes.unknown)
2666 unknown.update(changes.ignored)
2666 unknown.update(changes.ignored)
2667 clean = set(changes.clean)
2667 clean = set(changes.clean)
2668 modadded = set()
2668 modadded = set()
2669
2669
2670 # We need to account for the state of the file in the dirstate,
2670 # We need to account for the state of the file in the dirstate,
2671 # even when we revert against something else than parent. This will
2671 # even when we revert against something else than parent. This will
2672 # slightly alter the behavior of revert (doing back up or not, delete
2672 # slightly alter the behavior of revert (doing back up or not, delete
2673 # or just forget etc).
2673 # or just forget etc).
2674 if parent == node:
2674 if parent == node:
2675 dsmodified = modified
2675 dsmodified = modified
2676 dsadded = added
2676 dsadded = added
2677 dsremoved = removed
2677 dsremoved = removed
2678 # store all local modifications, useful later for rename detection
2678 # store all local modifications, useful later for rename detection
2679 localchanges = dsmodified | dsadded
2679 localchanges = dsmodified | dsadded
2680 modified, added, removed = set(), set(), set()
2680 modified, added, removed = set(), set(), set()
2681 else:
2681 else:
2682 changes = repo.status(node1=parent, match=m)
2682 changes = repo.status(node1=parent, match=m)
2683 dsmodified = set(changes.modified)
2683 dsmodified = set(changes.modified)
2684 dsadded = set(changes.added)
2684 dsadded = set(changes.added)
2685 dsremoved = set(changes.removed)
2685 dsremoved = set(changes.removed)
2686 # store all local modifications, useful later for rename detection
2686 # store all local modifications, useful later for rename detection
2687 localchanges = dsmodified | dsadded
2687 localchanges = dsmodified | dsadded
2688
2688
2689 # only take into account for removes between wc and target
2689 # only take into account for removes between wc and target
2690 clean |= dsremoved - removed
2690 clean |= dsremoved - removed
2691 dsremoved &= removed
2691 dsremoved &= removed
2692 # distinct between dirstate remove and other
2692 # distinct between dirstate remove and other
2693 removed -= dsremoved
2693 removed -= dsremoved
2694
2694
2695 modadded = added & dsmodified
2695 modadded = added & dsmodified
2696 added -= modadded
2696 added -= modadded
2697
2697
2698 # tell newly modified apart.
2698 # tell newly modified apart.
2699 dsmodified &= modified
2699 dsmodified &= modified
2700 dsmodified |= modified & dsadded # dirstate added may need backup
2700 dsmodified |= modified & dsadded # dirstate added may need backup
2701 modified -= dsmodified
2701 modified -= dsmodified
2702
2702
2703 # We need to wait for some post-processing to update this set
2703 # We need to wait for some post-processing to update this set
2704 # before making the distinction. The dirstate will be used for
2704 # before making the distinction. The dirstate will be used for
2705 # that purpose.
2705 # that purpose.
2706 dsadded = added
2706 dsadded = added
2707
2707
2708 # in case of merge, files that are actually added can be reported as
2708 # in case of merge, files that are actually added can be reported as
2709 # modified, we need to post process the result
2709 # modified, we need to post process the result
2710 if p2 != nullid:
2710 if p2 != nullid:
2711 mergeadd = set(dsmodified)
2711 mergeadd = set(dsmodified)
2712 for path in dsmodified:
2712 for path in dsmodified:
2713 if path in mf:
2713 if path in mf:
2714 mergeadd.remove(path)
2714 mergeadd.remove(path)
2715 dsadded |= mergeadd
2715 dsadded |= mergeadd
2716 dsmodified -= mergeadd
2716 dsmodified -= mergeadd
2717
2717
2718 # if f is a rename, update `names` to also revert the source
2718 # if f is a rename, update `names` to also revert the source
2719 cwd = repo.getcwd()
2719 cwd = repo.getcwd()
2720 for f in localchanges:
2720 for f in localchanges:
2721 src = repo.dirstate.copied(f)
2721 src = repo.dirstate.copied(f)
2722 # XXX should we check for rename down to target node?
2722 # XXX should we check for rename down to target node?
2723 if src and src not in names and repo.dirstate[src] == 'r':
2723 if src and src not in names and repo.dirstate[src] == 'r':
2724 dsremoved.add(src)
2724 dsremoved.add(src)
2725 names[src] = (repo.pathto(src, cwd), True)
2725 names[src] = (repo.pathto(src, cwd), True)
2726
2726
2727 # determine the exact nature of the deleted changesets
2727 # determine the exact nature of the deleted changesets
2728 deladded = set(_deleted)
2728 deladded = set(_deleted)
2729 for path in _deleted:
2729 for path in _deleted:
2730 if path in mf:
2730 if path in mf:
2731 deladded.remove(path)
2731 deladded.remove(path)
2732 deleted = _deleted - deladded
2732 deleted = _deleted - deladded
2733
2733
2734 # distinguish between file to forget and the other
2734 # distinguish between file to forget and the other
2735 added = set()
2735 added = set()
2736 for abs in dsadded:
2736 for abs in dsadded:
2737 if repo.dirstate[abs] != 'a':
2737 if repo.dirstate[abs] != 'a':
2738 added.add(abs)
2738 added.add(abs)
2739 dsadded -= added
2739 dsadded -= added
2740
2740
2741 for abs in deladded:
2741 for abs in deladded:
2742 if repo.dirstate[abs] == 'a':
2742 if repo.dirstate[abs] == 'a':
2743 dsadded.add(abs)
2743 dsadded.add(abs)
2744 deladded -= dsadded
2744 deladded -= dsadded
2745
2745
2746 # For files marked as removed, we check if an unknown file is present at
2746 # For files marked as removed, we check if an unknown file is present at
2747 # the same path. If a such file exists it may need to be backed up.
2747 # the same path. If a such file exists it may need to be backed up.
2748 # Making the distinction at this stage helps have simpler backup
2748 # Making the distinction at this stage helps have simpler backup
2749 # logic.
2749 # logic.
2750 removunk = set()
2750 removunk = set()
2751 for abs in removed:
2751 for abs in removed:
2752 target = repo.wjoin(abs)
2752 target = repo.wjoin(abs)
2753 if os.path.lexists(target):
2753 if os.path.lexists(target):
2754 removunk.add(abs)
2754 removunk.add(abs)
2755 removed -= removunk
2755 removed -= removunk
2756
2756
2757 dsremovunk = set()
2757 dsremovunk = set()
2758 for abs in dsremoved:
2758 for abs in dsremoved:
2759 target = repo.wjoin(abs)
2759 target = repo.wjoin(abs)
2760 if os.path.lexists(target):
2760 if os.path.lexists(target):
2761 dsremovunk.add(abs)
2761 dsremovunk.add(abs)
2762 dsremoved -= dsremovunk
2762 dsremoved -= dsremovunk
2763
2763
2764 # action to be actually performed by revert
2764 # action to be actually performed by revert
2765 # (<list of file>, message>) tuple
2765 # (<list of file>, message>) tuple
2766 actions = {'revert': ([], _('reverting %s\n')),
2766 actions = {'revert': ([], _('reverting %s\n')),
2767 'add': ([], _('adding %s\n')),
2767 'add': ([], _('adding %s\n')),
2768 'remove': ([], _('removing %s\n')),
2768 'remove': ([], _('removing %s\n')),
2769 'drop': ([], _('removing %s\n')),
2769 'drop': ([], _('removing %s\n')),
2770 'forget': ([], _('forgetting %s\n')),
2770 'forget': ([], _('forgetting %s\n')),
2771 'undelete': ([], _('undeleting %s\n')),
2771 'undelete': ([], _('undeleting %s\n')),
2772 'noop': (None, _('no changes needed to %s\n')),
2772 'noop': (None, _('no changes needed to %s\n')),
2773 'unknown': (None, _('file not managed: %s\n')),
2773 'unknown': (None, _('file not managed: %s\n')),
2774 }
2774 }
2775
2775
2776 # "constant" that convey the backup strategy.
2776 # "constant" that convey the backup strategy.
2777 # All set to `discard` if `no-backup` is set do avoid checking
2777 # All set to `discard` if `no-backup` is set do avoid checking
2778 # no_backup lower in the code.
2778 # no_backup lower in the code.
2779 # These values are ordered for comparison purposes
2779 # These values are ordered for comparison purposes
2780 backupinteractive = 3 # do backup if interactively modified
2780 backupinteractive = 3 # do backup if interactively modified
2781 backup = 2 # unconditionally do backup
2781 backup = 2 # unconditionally do backup
2782 check = 1 # check if the existing file differs from target
2782 check = 1 # check if the existing file differs from target
2783 discard = 0 # never do backup
2783 discard = 0 # never do backup
2784 if opts.get('no_backup'):
2784 if opts.get('no_backup'):
2785 backupinteractive = backup = check = discard
2785 backupinteractive = backup = check = discard
2786 if interactive:
2786 if interactive:
2787 dsmodifiedbackup = backupinteractive
2787 dsmodifiedbackup = backupinteractive
2788 else:
2788 else:
2789 dsmodifiedbackup = backup
2789 dsmodifiedbackup = backup
2790 tobackup = set()
2790 tobackup = set()
2791
2791
2792 backupanddel = actions['remove']
2792 backupanddel = actions['remove']
2793 if not opts.get('no_backup'):
2793 if not opts.get('no_backup'):
2794 backupanddel = actions['drop']
2794 backupanddel = actions['drop']
2795
2795
2796 disptable = (
2796 disptable = (
2797 # dispatch table:
2797 # dispatch table:
2798 # file state
2798 # file state
2799 # action
2799 # action
2800 # make backup
2800 # make backup
2801
2801
2802 ## Sets that results that will change file on disk
2802 ## Sets that results that will change file on disk
2803 # Modified compared to target, no local change
2803 # Modified compared to target, no local change
2804 (modified, actions['revert'], discard),
2804 (modified, actions['revert'], discard),
2805 # Modified compared to target, but local file is deleted
2805 # Modified compared to target, but local file is deleted
2806 (deleted, actions['revert'], discard),
2806 (deleted, actions['revert'], discard),
2807 # Modified compared to target, local change
2807 # Modified compared to target, local change
2808 (dsmodified, actions['revert'], dsmodifiedbackup),
2808 (dsmodified, actions['revert'], dsmodifiedbackup),
2809 # Added since target
2809 # Added since target
2810 (added, actions['remove'], discard),
2810 (added, actions['remove'], discard),
2811 # Added in working directory
2811 # Added in working directory
2812 (dsadded, actions['forget'], discard),
2812 (dsadded, actions['forget'], discard),
2813 # Added since target, have local modification
2813 # Added since target, have local modification
2814 (modadded, backupanddel, backup),
2814 (modadded, backupanddel, backup),
2815 # Added since target but file is missing in working directory
2815 # Added since target but file is missing in working directory
2816 (deladded, actions['drop'], discard),
2816 (deladded, actions['drop'], discard),
2817 # Removed since target, before working copy parent
2817 # Removed since target, before working copy parent
2818 (removed, actions['add'], discard),
2818 (removed, actions['add'], discard),
2819 # Same as `removed` but an unknown file exists at the same path
2819 # Same as `removed` but an unknown file exists at the same path
2820 (removunk, actions['add'], check),
2820 (removunk, actions['add'], check),
2821 # Removed since targe, marked as such in working copy parent
2821 # Removed since targe, marked as such in working copy parent
2822 (dsremoved, actions['undelete'], discard),
2822 (dsremoved, actions['undelete'], discard),
2823 # Same as `dsremoved` but an unknown file exists at the same path
2823 # Same as `dsremoved` but an unknown file exists at the same path
2824 (dsremovunk, actions['undelete'], check),
2824 (dsremovunk, actions['undelete'], check),
2825 ## the following sets does not result in any file changes
2825 ## the following sets does not result in any file changes
2826 # File with no modification
2826 # File with no modification
2827 (clean, actions['noop'], discard),
2827 (clean, actions['noop'], discard),
2828 # Existing file, not tracked anywhere
2828 # Existing file, not tracked anywhere
2829 (unknown, actions['unknown'], discard),
2829 (unknown, actions['unknown'], discard),
2830 )
2830 )
2831
2831
2832 for abs, (rel, exact) in sorted(names.items()):
2832 for abs, (rel, exact) in sorted(names.items()):
2833 # target file to be touch on disk (relative to cwd)
2833 # target file to be touch on disk (relative to cwd)
2834 target = repo.wjoin(abs)
2834 target = repo.wjoin(abs)
2835 # search the entry in the dispatch table.
2835 # search the entry in the dispatch table.
2836 # if the file is in any of these sets, it was touched in the working
2836 # if the file is in any of these sets, it was touched in the working
2837 # directory parent and we are sure it needs to be reverted.
2837 # directory parent and we are sure it needs to be reverted.
2838 for table, (xlist, msg), dobackup in disptable:
2838 for table, (xlist, msg), dobackup in disptable:
2839 if abs not in table:
2839 if abs not in table:
2840 continue
2840 continue
2841 if xlist is not None:
2841 if xlist is not None:
2842 xlist.append(abs)
2842 xlist.append(abs)
2843 if dobackup:
2843 if dobackup:
2844 # If in interactive mode, don't automatically create
2844 # If in interactive mode, don't automatically create
2845 # .orig files (issue4793)
2845 # .orig files (issue4793)
2846 if dobackup == backupinteractive:
2846 if dobackup == backupinteractive:
2847 tobackup.add(abs)
2847 tobackup.add(abs)
2848 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2848 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2849 bakname = scmutil.origpath(ui, repo, rel)
2849 bakname = scmutil.origpath(ui, repo, rel)
2850 ui.note(_('saving current version of %s as %s\n') %
2850 ui.note(_('saving current version of %s as %s\n') %
2851 (rel, bakname))
2851 (rel, bakname))
2852 if not opts.get('dry_run'):
2852 if not opts.get('dry_run'):
2853 if interactive:
2853 if interactive:
2854 util.copyfile(target, bakname)
2854 util.copyfile(target, bakname)
2855 else:
2855 else:
2856 util.rename(target, bakname)
2856 util.rename(target, bakname)
2857 if ui.verbose or not exact:
2857 if ui.verbose or not exact:
2858 if not isinstance(msg, bytes):
2858 if not isinstance(msg, bytes):
2859 msg = msg(abs)
2859 msg = msg(abs)
2860 ui.status(msg % rel)
2860 ui.status(msg % rel)
2861 elif exact:
2861 elif exact:
2862 ui.warn(msg % rel)
2862 ui.warn(msg % rel)
2863 break
2863 break
2864
2864
2865 if not opts.get('dry_run'):
2865 if not opts.get('dry_run'):
2866 needdata = ('revert', 'add', 'undelete')
2866 needdata = ('revert', 'add', 'undelete')
2867 if _revertprefetch is not _revertprefetchstub:
2867 if _revertprefetch is not _revertprefetchstub:
2868 ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
2868 ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
2869 "add a callback to 'scmutil.fileprefetchhooks'",
2869 "add a callback to 'scmutil.fileprefetchhooks'",
2870 '4.6', stacklevel=1)
2870 '4.6', stacklevel=1)
2871 _revertprefetch(repo, ctx,
2871 _revertprefetch(repo, ctx,
2872 *[actions[name][0] for name in needdata])
2872 *[actions[name][0] for name in needdata])
2873 oplist = [actions[name][0] for name in needdata]
2873 oplist = [actions[name][0] for name in needdata]
2874 prefetch = scmutil.fileprefetchhooks
2874 prefetch = scmutil.fileprefetchhooks
2875 prefetch(repo, ctx, [f for sublist in oplist for f in sublist])
2875 prefetch(repo, ctx, [f for sublist in oplist for f in sublist])
2876 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2876 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2877
2877
2878 if targetsubs:
2878 if targetsubs:
2879 # Revert the subrepos on the revert list
2879 # Revert the subrepos on the revert list
2880 for sub in targetsubs:
2880 for sub in targetsubs:
2881 try:
2881 try:
2882 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2882 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2883 **pycompat.strkwargs(opts))
2883 **pycompat.strkwargs(opts))
2884 except KeyError:
2884 except KeyError:
2885 raise error.Abort("subrepository '%s' does not exist in %s!"
2885 raise error.Abort("subrepository '%s' does not exist in %s!"
2886 % (sub, short(ctx.node())))
2886 % (sub, short(ctx.node())))
2887
2887
2888 def _revertprefetchstub(repo, ctx, *files):
2888 def _revertprefetchstub(repo, ctx, *files):
2889 """Stub method for detecting extension wrapping of _revertprefetch(), to
2889 """Stub method for detecting extension wrapping of _revertprefetch(), to
2890 issue a deprecation warning."""
2890 issue a deprecation warning."""
2891
2891
2892 _revertprefetch = _revertprefetchstub
2892 _revertprefetch = _revertprefetchstub
2893
2893
2894 def _performrevert(repo, parents, ctx, actions, interactive=False,
2894 def _performrevert(repo, parents, ctx, actions, interactive=False,
2895 tobackup=None):
2895 tobackup=None):
2896 """function that actually perform all the actions computed for revert
2896 """function that actually perform all the actions computed for revert
2897
2897
2898 This is an independent function to let extension to plug in and react to
2898 This is an independent function to let extension to plug in and react to
2899 the imminent revert.
2899 the imminent revert.
2900
2900
2901 Make sure you have the working directory locked when calling this function.
2901 Make sure you have the working directory locked when calling this function.
2902 """
2902 """
2903 parent, p2 = parents
2903 parent, p2 = parents
2904 node = ctx.node()
2904 node = ctx.node()
2905 excluded_files = []
2905 excluded_files = []
2906 matcher_opts = {"exclude": excluded_files}
2907
2906
2908 def checkout(f):
2907 def checkout(f):
2909 fc = ctx[f]
2908 fc = ctx[f]
2910 repo.wwrite(f, fc.data(), fc.flags())
2909 repo.wwrite(f, fc.data(), fc.flags())
2911
2910
2912 def doremove(f):
2911 def doremove(f):
2913 try:
2912 try:
2914 repo.wvfs.unlinkpath(f)
2913 repo.wvfs.unlinkpath(f)
2915 except OSError:
2914 except OSError:
2916 pass
2915 pass
2917 repo.dirstate.remove(f)
2916 repo.dirstate.remove(f)
2918
2917
2919 audit_path = pathutil.pathauditor(repo.root, cached=True)
2918 audit_path = pathutil.pathauditor(repo.root, cached=True)
2920 for f in actions['forget'][0]:
2919 for f in actions['forget'][0]:
2921 if interactive:
2920 if interactive:
2922 choice = repo.ui.promptchoice(
2921 choice = repo.ui.promptchoice(
2923 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2922 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2924 if choice == 0:
2923 if choice == 0:
2925 repo.dirstate.drop(f)
2924 repo.dirstate.drop(f)
2926 else:
2925 else:
2927 excluded_files.append(repo.wjoin(f))
2926 excluded_files.append(f)
2928 else:
2927 else:
2929 repo.dirstate.drop(f)
2928 repo.dirstate.drop(f)
2930 for f in actions['remove'][0]:
2929 for f in actions['remove'][0]:
2931 audit_path(f)
2930 audit_path(f)
2932 if interactive:
2931 if interactive:
2933 choice = repo.ui.promptchoice(
2932 choice = repo.ui.promptchoice(
2934 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
2933 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
2935 if choice == 0:
2934 if choice == 0:
2936 doremove(f)
2935 doremove(f)
2937 else:
2936 else:
2938 excluded_files.append(repo.wjoin(f))
2937 excluded_files.append(f)
2939 else:
2938 else:
2940 doremove(f)
2939 doremove(f)
2941 for f in actions['drop'][0]:
2940 for f in actions['drop'][0]:
2942 audit_path(f)
2941 audit_path(f)
2943 repo.dirstate.remove(f)
2942 repo.dirstate.remove(f)
2944
2943
2945 normal = None
2944 normal = None
2946 if node == parent:
2945 if node == parent:
2947 # We're reverting to our parent. If possible, we'd like status
2946 # We're reverting to our parent. If possible, we'd like status
2948 # to report the file as clean. We have to use normallookup for
2947 # to report the file as clean. We have to use normallookup for
2949 # merges to avoid losing information about merged/dirty files.
2948 # merges to avoid losing information about merged/dirty files.
2950 if p2 != nullid:
2949 if p2 != nullid:
2951 normal = repo.dirstate.normallookup
2950 normal = repo.dirstate.normallookup
2952 else:
2951 else:
2953 normal = repo.dirstate.normal
2952 normal = repo.dirstate.normal
2954
2953
2955 newlyaddedandmodifiedfiles = set()
2954 newlyaddedandmodifiedfiles = set()
2956 if interactive:
2955 if interactive:
2957 # Prompt the user for changes to revert
2956 # Prompt the user for changes to revert
2958 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
2957 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
2959 m = scmutil.match(ctx, torevert, matcher_opts)
2958 m = scmutil.matchfiles(repo, torevert)
2960 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
2959 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
2961 diffopts.nodates = True
2960 diffopts.nodates = True
2962 diffopts.git = True
2961 diffopts.git = True
2963 operation = 'discard'
2962 operation = 'discard'
2964 reversehunks = True
2963 reversehunks = True
2965 if node != parent:
2964 if node != parent:
2966 operation = 'apply'
2965 operation = 'apply'
2967 reversehunks = False
2966 reversehunks = False
2968 if reversehunks:
2967 if reversehunks:
2969 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
2968 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
2970 else:
2969 else:
2971 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
2970 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
2972 originalchunks = patch.parsepatch(diff)
2971 originalchunks = patch.parsepatch(diff)
2973
2972
2974 try:
2973 try:
2975
2974
2976 chunks, opts = recordfilter(repo.ui, originalchunks,
2975 chunks, opts = recordfilter(repo.ui, originalchunks,
2977 operation=operation)
2976 operation=operation)
2978 if reversehunks:
2977 if reversehunks:
2979 chunks = patch.reversehunks(chunks)
2978 chunks = patch.reversehunks(chunks)
2980
2979
2981 except error.PatchError as err:
2980 except error.PatchError as err:
2982 raise error.Abort(_('error parsing patch: %s') % err)
2981 raise error.Abort(_('error parsing patch: %s') % err)
2983
2982
2984 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
2983 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
2985 if tobackup is None:
2984 if tobackup is None:
2986 tobackup = set()
2985 tobackup = set()
2987 # Apply changes
2986 # Apply changes
2988 fp = stringio()
2987 fp = stringio()
2989 for c in chunks:
2988 for c in chunks:
2990 # Create a backup file only if this hunk should be backed up
2989 # Create a backup file only if this hunk should be backed up
2991 if ishunk(c) and c.header.filename() in tobackup:
2990 if ishunk(c) and c.header.filename() in tobackup:
2992 abs = c.header.filename()
2991 abs = c.header.filename()
2993 target = repo.wjoin(abs)
2992 target = repo.wjoin(abs)
2994 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
2993 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
2995 util.copyfile(target, bakname)
2994 util.copyfile(target, bakname)
2996 tobackup.remove(abs)
2995 tobackup.remove(abs)
2997 c.write(fp)
2996 c.write(fp)
2998 dopatch = fp.tell()
2997 dopatch = fp.tell()
2999 fp.seek(0)
2998 fp.seek(0)
3000 if dopatch:
2999 if dopatch:
3001 try:
3000 try:
3002 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3001 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3003 except error.PatchError as err:
3002 except error.PatchError as err:
3004 raise error.Abort(str(err))
3003 raise error.Abort(str(err))
3005 del fp
3004 del fp
3006 else:
3005 else:
3007 for f in actions['revert'][0]:
3006 for f in actions['revert'][0]:
3008 checkout(f)
3007 checkout(f)
3009 if normal:
3008 if normal:
3010 normal(f)
3009 normal(f)
3011
3010
3012 for f in actions['add'][0]:
3011 for f in actions['add'][0]:
3013 # Don't checkout modified files, they are already created by the diff
3012 # Don't checkout modified files, they are already created by the diff
3014 if f not in newlyaddedandmodifiedfiles:
3013 if f not in newlyaddedandmodifiedfiles:
3015 checkout(f)
3014 checkout(f)
3016 repo.dirstate.add(f)
3015 repo.dirstate.add(f)
3017
3016
3018 normal = repo.dirstate.normallookup
3017 normal = repo.dirstate.normallookup
3019 if node == parent and p2 == nullid:
3018 if node == parent and p2 == nullid:
3020 normal = repo.dirstate.normal
3019 normal = repo.dirstate.normal
3021 for f in actions['undelete'][0]:
3020 for f in actions['undelete'][0]:
3022 checkout(f)
3021 checkout(f)
3023 normal(f)
3022 normal(f)
3024
3023
3025 copied = copies.pathcopies(repo[parent], ctx)
3024 copied = copies.pathcopies(repo[parent], ctx)
3026
3025
3027 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3026 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3028 if f in copied:
3027 if f in copied:
3029 repo.dirstate.copy(copied[f], f)
3028 repo.dirstate.copy(copied[f], f)
3030
3029
3031 class command(registrar.command):
3030 class command(registrar.command):
3032 """deprecated: used registrar.command instead"""
3031 """deprecated: used registrar.command instead"""
3033 def _doregister(self, func, name, *args, **kwargs):
3032 def _doregister(self, func, name, *args, **kwargs):
3034 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3033 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3035 return super(command, self)._doregister(func, name, *args, **kwargs)
3034 return super(command, self)._doregister(func, name, *args, **kwargs)
3036
3035
3037 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3036 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3038 # commands.outgoing. "missing" is "missing" of the result of
3037 # commands.outgoing. "missing" is "missing" of the result of
3039 # "findcommonoutgoing()"
3038 # "findcommonoutgoing()"
3040 outgoinghooks = util.hooks()
3039 outgoinghooks = util.hooks()
3041
3040
3042 # a list of (ui, repo) functions called by commands.summary
3041 # a list of (ui, repo) functions called by commands.summary
3043 summaryhooks = util.hooks()
3042 summaryhooks = util.hooks()
3044
3043
3045 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3044 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3046 #
3045 #
3047 # functions should return tuple of booleans below, if 'changes' is None:
3046 # functions should return tuple of booleans below, if 'changes' is None:
3048 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3047 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3049 #
3048 #
3050 # otherwise, 'changes' is a tuple of tuples below:
3049 # otherwise, 'changes' is a tuple of tuples below:
3051 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3050 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3052 # - (desturl, destbranch, destpeer, outgoing)
3051 # - (desturl, destbranch, destpeer, outgoing)
3053 summaryremotehooks = util.hooks()
3052 summaryremotehooks = util.hooks()
3054
3053
3055 # A list of state files kept by multistep operations like graft.
3054 # A list of state files kept by multistep operations like graft.
3056 # Since graft cannot be aborted, it is considered 'clearable' by update.
3055 # Since graft cannot be aborted, it is considered 'clearable' by update.
3057 # note: bisect is intentionally excluded
3056 # note: bisect is intentionally excluded
3058 # (state file, clearable, allowcommit, error, hint)
3057 # (state file, clearable, allowcommit, error, hint)
3059 unfinishedstates = [
3058 unfinishedstates = [
3060 ('graftstate', True, False, _('graft in progress'),
3059 ('graftstate', True, False, _('graft in progress'),
3061 _("use 'hg graft --continue' or 'hg update' to abort")),
3060 _("use 'hg graft --continue' or 'hg update' to abort")),
3062 ('updatestate', True, False, _('last update was interrupted'),
3061 ('updatestate', True, False, _('last update was interrupted'),
3063 _("use 'hg update' to get a consistent checkout"))
3062 _("use 'hg update' to get a consistent checkout"))
3064 ]
3063 ]
3065
3064
3066 def checkunfinished(repo, commit=False):
3065 def checkunfinished(repo, commit=False):
3067 '''Look for an unfinished multistep operation, like graft, and abort
3066 '''Look for an unfinished multistep operation, like graft, and abort
3068 if found. It's probably good to check this right before
3067 if found. It's probably good to check this right before
3069 bailifchanged().
3068 bailifchanged().
3070 '''
3069 '''
3071 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3070 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3072 if commit and allowcommit:
3071 if commit and allowcommit:
3073 continue
3072 continue
3074 if repo.vfs.exists(f):
3073 if repo.vfs.exists(f):
3075 raise error.Abort(msg, hint=hint)
3074 raise error.Abort(msg, hint=hint)
3076
3075
3077 def clearunfinished(repo):
3076 def clearunfinished(repo):
3078 '''Check for unfinished operations (as above), and clear the ones
3077 '''Check for unfinished operations (as above), and clear the ones
3079 that are clearable.
3078 that are clearable.
3080 '''
3079 '''
3081 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3080 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3082 if not clearable and repo.vfs.exists(f):
3081 if not clearable and repo.vfs.exists(f):
3083 raise error.Abort(msg, hint=hint)
3082 raise error.Abort(msg, hint=hint)
3084 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3083 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3085 if clearable and repo.vfs.exists(f):
3084 if clearable and repo.vfs.exists(f):
3086 util.unlink(repo.vfs.join(f))
3085 util.unlink(repo.vfs.join(f))
3087
3086
3088 afterresolvedstates = [
3087 afterresolvedstates = [
3089 ('graftstate',
3088 ('graftstate',
3090 _('hg graft --continue')),
3089 _('hg graft --continue')),
3091 ]
3090 ]
3092
3091
3093 def howtocontinue(repo):
3092 def howtocontinue(repo):
3094 '''Check for an unfinished operation and return the command to finish
3093 '''Check for an unfinished operation and return the command to finish
3095 it.
3094 it.
3096
3095
3097 afterresolvedstates tuples define a .hg/{file} and the corresponding
3096 afterresolvedstates tuples define a .hg/{file} and the corresponding
3098 command needed to finish it.
3097 command needed to finish it.
3099
3098
3100 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3099 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3101 a boolean.
3100 a boolean.
3102 '''
3101 '''
3103 contmsg = _("continue: %s")
3102 contmsg = _("continue: %s")
3104 for f, msg in afterresolvedstates:
3103 for f, msg in afterresolvedstates:
3105 if repo.vfs.exists(f):
3104 if repo.vfs.exists(f):
3106 return contmsg % msg, True
3105 return contmsg % msg, True
3107 if repo[None].dirty(missing=True, merge=False, branch=False):
3106 if repo[None].dirty(missing=True, merge=False, branch=False):
3108 return contmsg % _("hg commit"), False
3107 return contmsg % _("hg commit"), False
3109 return None, None
3108 return None, None
3110
3109
3111 def checkafterresolved(repo):
3110 def checkafterresolved(repo):
3112 '''Inform the user about the next action after completing hg resolve
3111 '''Inform the user about the next action after completing hg resolve
3113
3112
3114 If there's a matching afterresolvedstates, howtocontinue will yield
3113 If there's a matching afterresolvedstates, howtocontinue will yield
3115 repo.ui.warn as the reporter.
3114 repo.ui.warn as the reporter.
3116
3115
3117 Otherwise, it will yield repo.ui.note.
3116 Otherwise, it will yield repo.ui.note.
3118 '''
3117 '''
3119 msg, warning = howtocontinue(repo)
3118 msg, warning = howtocontinue(repo)
3120 if msg is not None:
3119 if msg is not None:
3121 if warning:
3120 if warning:
3122 repo.ui.warn("%s\n" % msg)
3121 repo.ui.warn("%s\n" % msg)
3123 else:
3122 else:
3124 repo.ui.note("%s\n" % msg)
3123 repo.ui.note("%s\n" % msg)
3125
3124
3126 def wrongtooltocontinue(repo, task):
3125 def wrongtooltocontinue(repo, task):
3127 '''Raise an abort suggesting how to properly continue if there is an
3126 '''Raise an abort suggesting how to properly continue if there is an
3128 active task.
3127 active task.
3129
3128
3130 Uses howtocontinue() to find the active task.
3129 Uses howtocontinue() to find the active task.
3131
3130
3132 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3131 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3133 a hint.
3132 a hint.
3134 '''
3133 '''
3135 after = howtocontinue(repo)
3134 after = howtocontinue(repo)
3136 hint = None
3135 hint = None
3137 if after[1]:
3136 if after[1]:
3138 hint = after[0]
3137 hint = after[0]
3139 raise error.Abort(_('no %s in progress') % task, hint=hint)
3138 raise error.Abort(_('no %s in progress') % task, hint=hint)
3140
3139
3141 class changeset_printer(logcmdutil.changesetprinter):
3140 class changeset_printer(logcmdutil.changesetprinter):
3142
3141
3143 def __init__(self, ui, *args, **kwargs):
3142 def __init__(self, ui, *args, **kwargs):
3144 msg = ("'cmdutil.changeset_printer' is deprecated, "
3143 msg = ("'cmdutil.changeset_printer' is deprecated, "
3145 "use 'logcmdutil.logcmdutil'")
3144 "use 'logcmdutil.logcmdutil'")
3146 ui.deprecwarn(msg, "4.6")
3145 ui.deprecwarn(msg, "4.6")
3147 super(changeset_printer, self).__init__(ui, *args, **kwargs)
3146 super(changeset_printer, self).__init__(ui, *args, **kwargs)
3148
3147
3149 def displaygraph(ui, *args, **kwargs):
3148 def displaygraph(ui, *args, **kwargs):
3150 msg = ("'cmdutil.displaygraph' is deprecated, "
3149 msg = ("'cmdutil.displaygraph' is deprecated, "
3151 "use 'logcmdutil.displaygraph'")
3150 "use 'logcmdutil.displaygraph'")
3152 ui.deprecwarn(msg, "4.6")
3151 ui.deprecwarn(msg, "4.6")
3153 return logcmdutil.displaygraph(ui, *args, **kwargs)
3152 return logcmdutil.displaygraph(ui, *args, **kwargs)
3154
3153
3155 def show_changeset(ui, *args, **kwargs):
3154 def show_changeset(ui, *args, **kwargs):
3156 msg = ("'cmdutil.show_changeset' is deprecated, "
3155 msg = ("'cmdutil.show_changeset' is deprecated, "
3157 "use 'logcmdutil.changesetdisplayer'")
3156 "use 'logcmdutil.changesetdisplayer'")
3158 ui.deprecwarn(msg, "4.6")
3157 ui.deprecwarn(msg, "4.6")
3159 return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
3158 return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
@@ -1,436 +1,432
1 Revert interactive tests
1 Revert interactive tests
2 1 add and commit file f
2 1 add and commit file f
3 2 add commit file folder1/g
3 2 add commit file folder1/g
4 3 add and commit file folder2/h
4 3 add and commit file folder2/h
5 4 add and commit file folder1/i
5 4 add and commit file folder1/i
6 5 commit change to file f
6 5 commit change to file f
7 6 commit changes to files folder1/g folder2/h
7 6 commit changes to files folder1/g folder2/h
8 7 commit changes to files folder1/g folder2/h
8 7 commit changes to files folder1/g folder2/h
9 8 revert interactive to commit id 2 (line 3 above), check that folder1/i is removed and
9 8 revert interactive to commit id 2 (line 3 above), check that folder1/i is removed and
10 9 make workdir match 7
10 9 make workdir match 7
11 10 run the same test than 8 from within folder1 and check same expectations
11 10 run the same test than 8 from within folder1 and check same expectations
12
12
13 $ cat <<EOF >> $HGRCPATH
13 $ cat <<EOF >> $HGRCPATH
14 > [ui]
14 > [ui]
15 > interactive = true
15 > interactive = true
16 > [extensions]
16 > [extensions]
17 > record =
17 > record =
18 > purge =
18 > purge =
19 > EOF
19 > EOF
20
20
21
21
22 $ mkdir -p a/folder1 a/folder2
22 $ mkdir -p a/folder1 a/folder2
23 $ cd a
23 $ cd a
24 $ hg init
24 $ hg init
25 >>> open('f', 'wb').write("1\n2\n3\n4\n5\n")
25 >>> open('f', 'wb').write("1\n2\n3\n4\n5\n")
26 $ hg add f ; hg commit -m "adding f"
26 $ hg add f ; hg commit -m "adding f"
27 $ cat f > folder1/g ; hg add folder1/g ; hg commit -m "adding folder1/g"
27 $ cat f > folder1/g ; hg add folder1/g ; hg commit -m "adding folder1/g"
28 $ cat f > folder2/h ; hg add folder2/h ; hg commit -m "adding folder2/h"
28 $ cat f > folder2/h ; hg add folder2/h ; hg commit -m "adding folder2/h"
29 $ cat f > folder1/i ; hg add folder1/i ; hg commit -m "adding folder1/i"
29 $ cat f > folder1/i ; hg add folder1/i ; hg commit -m "adding folder1/i"
30 >>> open('f', 'wb').write("a\n1\n2\n3\n4\n5\nb\n")
30 >>> open('f', 'wb').write("a\n1\n2\n3\n4\n5\nb\n")
31 $ hg commit -m "modifying f"
31 $ hg commit -m "modifying f"
32 >>> open('folder1/g', 'wb').write("c\n1\n2\n3\n4\n5\nd\n")
32 >>> open('folder1/g', 'wb').write("c\n1\n2\n3\n4\n5\nd\n")
33 $ hg commit -m "modifying folder1/g"
33 $ hg commit -m "modifying folder1/g"
34 >>> open('folder2/h', 'wb').write("e\n1\n2\n3\n4\n5\nf\n")
34 >>> open('folder2/h', 'wb').write("e\n1\n2\n3\n4\n5\nf\n")
35 $ hg commit -m "modifying folder2/h"
35 $ hg commit -m "modifying folder2/h"
36 $ hg tip
36 $ hg tip
37 changeset: 6:59dd6e4ab63a
37 changeset: 6:59dd6e4ab63a
38 tag: tip
38 tag: tip
39 user: test
39 user: test
40 date: Thu Jan 01 00:00:00 1970 +0000
40 date: Thu Jan 01 00:00:00 1970 +0000
41 summary: modifying folder2/h
41 summary: modifying folder2/h
42
42
43 $ hg revert -i -r 2 --all -- << EOF
43 $ hg revert -i -r 2 --all -- << EOF
44 > y
44 > y
45 > y
45 > y
46 > y
46 > y
47 > y
47 > y
48 > y
48 > y
49 > ?
49 > ?
50 > y
50 > y
51 > n
51 > n
52 > n
52 > n
53 > EOF
53 > EOF
54 reverting f
54 reverting f
55 reverting folder1/g
55 reverting folder1/g
56 removing folder1/i
56 removing folder1/i
57 reverting folder2/h
57 reverting folder2/h
58 remove added file folder1/i (Yn)? y
58 remove added file folder1/i (Yn)? y
59 diff --git a/f b/f
59 diff --git a/f b/f
60 2 hunks, 2 lines changed
60 2 hunks, 2 lines changed
61 examine changes to 'f'? [Ynesfdaq?] y
61 examine changes to 'f'? [Ynesfdaq?] y
62
62
63 @@ -1,6 +1,5 @@
63 @@ -1,6 +1,5 @@
64 -a
64 -a
65 1
65 1
66 2
66 2
67 3
67 3
68 4
68 4
69 5
69 5
70 apply change 1/6 to 'f'? [Ynesfdaq?] y
70 apply change 1/6 to 'f'? [Ynesfdaq?] y
71
71
72 @@ -2,6 +1,5 @@
72 @@ -2,6 +1,5 @@
73 1
73 1
74 2
74 2
75 3
75 3
76 4
76 4
77 5
77 5
78 -b
78 -b
79 apply change 2/6 to 'f'? [Ynesfdaq?] y
79 apply change 2/6 to 'f'? [Ynesfdaq?] y
80
80
81 diff --git a/folder1/g b/folder1/g
81 diff --git a/folder1/g b/folder1/g
82 2 hunks, 2 lines changed
82 2 hunks, 2 lines changed
83 examine changes to 'folder1/g'? [Ynesfdaq?] y
83 examine changes to 'folder1/g'? [Ynesfdaq?] y
84
84
85 @@ -1,6 +1,5 @@
85 @@ -1,6 +1,5 @@
86 -c
86 -c
87 1
87 1
88 2
88 2
89 3
89 3
90 4
90 4
91 5
91 5
92 apply change 3/6 to 'folder1/g'? [Ynesfdaq?] ?
92 apply change 3/6 to 'folder1/g'? [Ynesfdaq?] ?
93
93
94 y - yes, apply this change
94 y - yes, apply this change
95 n - no, skip this change
95 n - no, skip this change
96 e - edit this change manually
96 e - edit this change manually
97 s - skip remaining changes to this file
97 s - skip remaining changes to this file
98 f - apply remaining changes to this file
98 f - apply remaining changes to this file
99 d - done, skip remaining changes and files
99 d - done, skip remaining changes and files
100 a - apply all changes to all remaining files
100 a - apply all changes to all remaining files
101 q - quit, applying no changes
101 q - quit, applying no changes
102 ? - ? (display help)
102 ? - ? (display help)
103 apply change 3/6 to 'folder1/g'? [Ynesfdaq?] y
103 apply change 3/6 to 'folder1/g'? [Ynesfdaq?] y
104
104
105 @@ -2,6 +1,5 @@
105 @@ -2,6 +1,5 @@
106 1
106 1
107 2
107 2
108 3
108 3
109 4
109 4
110 5
110 5
111 -d
111 -d
112 apply change 4/6 to 'folder1/g'? [Ynesfdaq?] n
112 apply change 4/6 to 'folder1/g'? [Ynesfdaq?] n
113
113
114 diff --git a/folder2/h b/folder2/h
114 diff --git a/folder2/h b/folder2/h
115 2 hunks, 2 lines changed
115 2 hunks, 2 lines changed
116 examine changes to 'folder2/h'? [Ynesfdaq?] n
116 examine changes to 'folder2/h'? [Ynesfdaq?] n
117
117
118 $ cat f
118 $ cat f
119 1
119 1
120 2
120 2
121 3
121 3
122 4
122 4
123 5
123 5
124 $ cat folder1/g
124 $ cat folder1/g
125 1
125 1
126 2
126 2
127 3
127 3
128 4
128 4
129 5
129 5
130 d
130 d
131 $ cat folder2/h
131 $ cat folder2/h
132 e
132 e
133 1
133 1
134 2
134 2
135 3
135 3
136 4
136 4
137 5
137 5
138 f
138 f
139
139
140 Test that --interactive lift the need for --all
140 Test that --interactive lift the need for --all
141
141
142 $ echo q | hg revert -i -r 2
142 $ echo q | hg revert -i -r 2
143 reverting folder1/g
143 reverting folder1/g
144 reverting folder2/h
144 reverting folder2/h
145 diff --git a/folder1/g b/folder1/g
145 diff --git a/folder1/g b/folder1/g
146 1 hunks, 1 lines changed
146 1 hunks, 1 lines changed
147 examine changes to 'folder1/g'? [Ynesfdaq?] q
147 examine changes to 'folder1/g'? [Ynesfdaq?] q
148
148
149 abort: user quit
149 abort: user quit
150 [255]
150 [255]
151 $ ls folder1/
151 $ ls folder1/
152 g
152 g
153
153
154 Test that a noop revert doesn't do an unnecessary backup
154 Test that a noop revert doesn't do an unnecessary backup
155 $ (echo y; echo n) | hg revert -i -r 2 folder1/g
155 $ (echo y; echo n) | hg revert -i -r 2 folder1/g
156 diff --git a/folder1/g b/folder1/g
156 diff --git a/folder1/g b/folder1/g
157 1 hunks, 1 lines changed
157 1 hunks, 1 lines changed
158 examine changes to 'folder1/g'? [Ynesfdaq?] y
158 examine changes to 'folder1/g'? [Ynesfdaq?] y
159
159
160 @@ -3,4 +3,3 @@
160 @@ -3,4 +3,3 @@
161 3
161 3
162 4
162 4
163 5
163 5
164 -d
164 -d
165 apply this change to 'folder1/g'? [Ynesfdaq?] n
165 apply this change to 'folder1/g'? [Ynesfdaq?] n
166
166
167 $ ls folder1/
167 $ ls folder1/
168 g
168 g
169
169
170 Test --no-backup
170 Test --no-backup
171 $ (echo y; echo y) | hg revert -i -C -r 2 folder1/g
171 $ (echo y; echo y) | hg revert -i -C -r 2 folder1/g
172 diff --git a/folder1/g b/folder1/g
172 diff --git a/folder1/g b/folder1/g
173 1 hunks, 1 lines changed
173 1 hunks, 1 lines changed
174 examine changes to 'folder1/g'? [Ynesfdaq?] y
174 examine changes to 'folder1/g'? [Ynesfdaq?] y
175
175
176 @@ -3,4 +3,3 @@
176 @@ -3,4 +3,3 @@
177 3
177 3
178 4
178 4
179 5
179 5
180 -d
180 -d
181 apply this change to 'folder1/g'? [Ynesfdaq?] y
181 apply this change to 'folder1/g'? [Ynesfdaq?] y
182
182
183 $ ls folder1/
183 $ ls folder1/
184 g
184 g
185 >>> open('folder1/g', 'wb').write("1\n2\n3\n4\n5\nd\n")
185 >>> open('folder1/g', 'wb').write("1\n2\n3\n4\n5\nd\n")
186
186
187
187
188 $ hg update -C 6
188 $ hg update -C 6
189 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 $ hg revert -i -r 2 --all -- << EOF
190 $ hg revert -i -r 2 --all -- << EOF
191 > n
191 > n
192 > y
192 > y
193 > y
193 > y
194 > y
194 > y
195 > y
195 > y
196 > y
196 > y
197 > n
197 > n
198 > n
198 > n
199 > EOF
199 > EOF
200 reverting f
200 reverting f
201 reverting folder1/g
201 reverting folder1/g
202 removing folder1/i
202 removing folder1/i
203 reverting folder2/h
203 reverting folder2/h
204 remove added file folder1/i (Yn)? n
204 remove added file folder1/i (Yn)? n
205 diff --git a/f b/f
205 diff --git a/f b/f
206 2 hunks, 2 lines changed
206 2 hunks, 2 lines changed
207 examine changes to 'f'? [Ynesfdaq?] y
207 examine changes to 'f'? [Ynesfdaq?] y
208
208
209 @@ -1,6 +1,5 @@
209 @@ -1,6 +1,5 @@
210 -a
210 -a
211 1
211 1
212 2
212 2
213 3
213 3
214 4
214 4
215 5
215 5
216 apply change 1/6 to 'f'? [Ynesfdaq?] y
216 apply change 1/6 to 'f'? [Ynesfdaq?] y
217
217
218 @@ -2,6 +1,5 @@
218 @@ -2,6 +1,5 @@
219 1
219 1
220 2
220 2
221 3
221 3
222 4
222 4
223 5
223 5
224 -b
224 -b
225 apply change 2/6 to 'f'? [Ynesfdaq?] y
225 apply change 2/6 to 'f'? [Ynesfdaq?] y
226
226
227 diff --git a/folder1/g b/folder1/g
227 diff --git a/folder1/g b/folder1/g
228 2 hunks, 2 lines changed
228 2 hunks, 2 lines changed
229 examine changes to 'folder1/g'? [Ynesfdaq?] y
229 examine changes to 'folder1/g'? [Ynesfdaq?] y
230
230
231 @@ -1,6 +1,5 @@
231 @@ -1,6 +1,5 @@
232 -c
232 -c
233 1
233 1
234 2
234 2
235 3
235 3
236 4
236 4
237 5
237 5
238 apply change 3/6 to 'folder1/g'? [Ynesfdaq?] y
238 apply change 3/6 to 'folder1/g'? [Ynesfdaq?] y
239
239
240 @@ -2,6 +1,5 @@
240 @@ -2,6 +1,5 @@
241 1
241 1
242 2
242 2
243 3
243 3
244 4
244 4
245 5
245 5
246 -d
246 -d
247 apply change 4/6 to 'folder1/g'? [Ynesfdaq?] n
247 apply change 4/6 to 'folder1/g'? [Ynesfdaq?] n
248
248
249 diff --git a/folder2/h b/folder2/h
249 diff --git a/folder2/h b/folder2/h
250 2 hunks, 2 lines changed
250 2 hunks, 2 lines changed
251 examine changes to 'folder2/h'? [Ynesfdaq?] n
251 examine changes to 'folder2/h'? [Ynesfdaq?] n
252
252
253 $ cat f
253 $ cat f
254 1
254 1
255 2
255 2
256 3
256 3
257 4
257 4
258 5
258 5
259 $ cat folder1/g
259 $ cat folder1/g
260 1
260 1
261 2
261 2
262 3
262 3
263 4
263 4
264 5
264 5
265 d
265 d
266 $ cat folder2/h
266 $ cat folder2/h
267 e
267 e
268 1
268 1
269 2
269 2
270 3
270 3
271 4
271 4
272 5
272 5
273 f
273 f
274 $ hg st
274 $ hg st
275 M f
275 M f
276 M folder1/g
276 M folder1/g
277 $ hg revert --interactive f << EOF
277 $ hg revert --interactive f << EOF
278 > y
278 > y
279 > ?
279 > ?
280 > y
280 > y
281 > n
281 > n
282 > n
282 > n
283 > EOF
283 > EOF
284 diff --git a/f b/f
284 diff --git a/f b/f
285 2 hunks, 2 lines changed
285 2 hunks, 2 lines changed
286 examine changes to 'f'? [Ynesfdaq?] y
286 examine changes to 'f'? [Ynesfdaq?] y
287
287
288 @@ -1,6 +1,5 @@
288 @@ -1,6 +1,5 @@
289 -a
289 -a
290 1
290 1
291 2
291 2
292 3
292 3
293 4
293 4
294 5
294 5
295 discard change 1/2 to 'f'? [Ynesfdaq?] ?
295 discard change 1/2 to 'f'? [Ynesfdaq?] ?
296
296
297 y - yes, discard this change
297 y - yes, discard this change
298 n - no, skip this change
298 n - no, skip this change
299 e - edit this change manually
299 e - edit this change manually
300 s - skip remaining changes to this file
300 s - skip remaining changes to this file
301 f - discard remaining changes to this file
301 f - discard remaining changes to this file
302 d - done, skip remaining changes and files
302 d - done, skip remaining changes and files
303 a - discard all changes to all remaining files
303 a - discard all changes to all remaining files
304 q - quit, discarding no changes
304 q - quit, discarding no changes
305 ? - ? (display help)
305 ? - ? (display help)
306 discard change 1/2 to 'f'? [Ynesfdaq?] y
306 discard change 1/2 to 'f'? [Ynesfdaq?] y
307
307
308 @@ -2,6 +1,5 @@
308 @@ -2,6 +1,5 @@
309 1
309 1
310 2
310 2
311 3
311 3
312 4
312 4
313 5
313 5
314 -b
314 -b
315 discard change 2/2 to 'f'? [Ynesfdaq?] n
315 discard change 2/2 to 'f'? [Ynesfdaq?] n
316
316
317 $ hg st
317 $ hg st
318 M f
318 M f
319 M folder1/g
319 M folder1/g
320 ? f.orig
320 ? f.orig
321 $ cat f
321 $ cat f
322 a
322 a
323 1
323 1
324 2
324 2
325 3
325 3
326 4
326 4
327 5
327 5
328 $ cat f.orig
328 $ cat f.orig
329 1
329 1
330 2
330 2
331 3
331 3
332 4
332 4
333 5
333 5
334 $ rm f.orig
334 $ rm f.orig
335 $ hg update -C .
335 $ hg update -C .
336 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
337
337
338 Check editing files newly added by a revert
338 Check editing files newly added by a revert
339
339
340 1) Create a dummy editor changing 1 to 42
340 1) Create a dummy editor changing 1 to 42
341 $ cat > $TESTTMP/editor.sh << '__EOF__'
341 $ cat > $TESTTMP/editor.sh << '__EOF__'
342 > cat "$1" | sed "s/1/42/g" > tt
342 > cat "$1" | sed "s/1/42/g" > tt
343 > mv tt "$1"
343 > mv tt "$1"
344 > __EOF__
344 > __EOF__
345
345
346 2) Add k
346 2) Add k
347 $ printf "1\n" > k
347 $ printf "1\n" > k
348 $ hg add k
348 $ hg add k
349 $ hg commit -m "add k"
349 $ hg commit -m "add k"
350
350
351 3) Use interactive revert with editing (replacing +1 with +42):
351 3) Use interactive revert with editing (replacing +1 with +42):
352 $ printf "0\n2\n" > k
352 $ printf "0\n2\n" > k
353 $ HGEDITOR="\"sh\" \"${TESTTMP}/editor.sh\"" hg revert -i <<EOF
353 $ HGEDITOR="\"sh\" \"${TESTTMP}/editor.sh\"" hg revert -i <<EOF
354 > y
354 > y
355 > e
355 > e
356 > EOF
356 > EOF
357 reverting k
357 reverting k
358 diff --git a/k b/k
358 diff --git a/k b/k
359 1 hunks, 2 lines changed
359 1 hunks, 2 lines changed
360 examine changes to 'k'? [Ynesfdaq?] y
360 examine changes to 'k'? [Ynesfdaq?] y
361
361
362 @@ -1,1 +1,2 @@
362 @@ -1,1 +1,2 @@
363 -1
363 -1
364 +0
364 +0
365 +2
365 +2
366 discard this change to 'k'? [Ynesfdaq?] e
366 discard this change to 'k'? [Ynesfdaq?] e
367
367
368 $ cat k
368 $ cat k
369 42
369 42
370
370
371 $ hg update -C .
371 $ hg update -C .
372 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
372 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
373 $ hg purge
373 $ hg purge
374 $ touch newfile
374 $ touch newfile
375 $ hg add newfile
375 $ hg add newfile
376 $ hg status
376 $ hg status
377 A newfile
377 A newfile
378 $ hg revert -i <<EOF
378 $ hg revert -i <<EOF
379 > n
379 > n
380 > EOF
380 > EOF
381 forgetting newfile
381 forgetting newfile
382 forget added file newfile (Yn)? n
382 forget added file newfile (Yn)? n
383 $ hg status
383 $ hg status
384 A newfile
384 A newfile
385 $ hg revert -i <<EOF
385 $ hg revert -i <<EOF
386 > y
386 > y
387 > EOF
387 > EOF
388 forgetting newfile
388 forgetting newfile
389 forget added file newfile (Yn)? y
389 forget added file newfile (Yn)? y
390 $ hg status
390 $ hg status
391 ? newfile
391 ? newfile
392
392
393 When a line without EOL is selected during "revert -i" (issue5651)
393 When a line without EOL is selected during "revert -i" (issue5651)
394
394
395 $ hg init $TESTTMP/revert-i-eol
395 $ hg init $TESTTMP/revert-i-eol
396 $ cd $TESTTMP/revert-i-eol
396 $ cd $TESTTMP/revert-i-eol
397 $ echo 0 > a
397 $ echo 0 > a
398 $ hg ci -qAm 0
398 $ hg ci -qAm 0
399 $ printf 1 >> a
399 $ printf 1 >> a
400 $ hg ci -qAm 1
400 $ hg ci -qAm 1
401 $ cat a
401 $ cat a
402 0
402 0
403 1 (no-eol)
403 1 (no-eol)
404
404
405 $ hg revert -ir'.^' <<EOF
405 $ hg revert -ir'.^' <<EOF
406 > y
406 > y
407 > y
407 > y
408 > EOF
408 > EOF
409 reverting a
409 reverting a
410 diff --git a/a b/a
410 diff --git a/a b/a
411 1 hunks, 1 lines changed
411 1 hunks, 1 lines changed
412 examine changes to 'a'? [Ynesfdaq?] y
412 examine changes to 'a'? [Ynesfdaq?] y
413
413
414 @@ -1,2 +1,1 @@
414 @@ -1,2 +1,1 @@
415 0
415 0
416 -1
416 -1
417 \ No newline at end of file
417 \ No newline at end of file
418 apply this change to 'a'? [Ynesfdaq?] y
418 apply this change to 'a'? [Ynesfdaq?] y
419
419
420 $ cat a
420 $ cat a
421 0
421 0
422
422
423 When specified pattern does not exist, we should exit early (issue5789).
423 When specified pattern does not exist, we should exit early (issue5789).
424
424
425 $ hg files
425 $ hg files
426 a
426 a
427 $ hg rev b
427 $ hg rev b
428 b: no such file in rev b40d1912accf
428 b: no such file in rev b40d1912accf
429 $ hg rev -i b
429 $ hg rev -i b
430 b: no such file in rev b40d1912accf
430 b: no such file in rev b40d1912accf
431 diff --git a/a b/a
432 1 hunks, 1 lines changed
433 examine changes to 'a'? [Ynesfdaq?] abort: response expected
434 [255]
435
431
436 $ cd ..
432 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now