##// END OF EJS Templates
cmdutil: build "%m" (desc|firstline) in makefilename()
Yuya Nishihara -
r36225:cc46e68f default
parent child Browse files
Show More
@@ -1,3153 +1,3152 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import 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(ctx, pat, desc=None,
894 def makefilename(ctx, pat,
895 total=None, seqno=None, revwidth=None, pathname=None):
895 total=None, seqno=None, revwidth=None, pathname=None):
896 expander = {
896 expander = {
897 'H': lambda: ctx.hex(),
897 'H': lambda: ctx.hex(),
898 'R': lambda: '%d' % ctx.rev(),
898 'R': lambda: '%d' % ctx.rev(),
899 'h': lambda: short(ctx.node()),
899 'h': lambda: short(ctx.node()),
900 'm': lambda: re.sub('[^\w]', '_', desc or ''),
900 'm': lambda: re.sub('[^\w]', '_',
901 ctx.description().rstrip().splitlines()[0]),
901 'r': lambda: ('%d' % ctx.rev()).zfill(revwidth or 0),
902 'r': lambda: ('%d' % ctx.rev()).zfill(revwidth or 0),
902 '%': lambda: '%',
903 '%': lambda: '%',
903 'b': lambda: os.path.basename(ctx.repo().root),
904 'b': lambda: os.path.basename(ctx.repo().root),
904 }
905 }
905
906
906 try:
907 try:
907 if total is not None:
908 if total is not None:
908 expander['N'] = lambda: '%d' % total
909 expander['N'] = lambda: '%d' % total
909 if seqno is not None:
910 if seqno is not None:
910 expander['n'] = lambda: '%d' % seqno
911 expander['n'] = lambda: '%d' % seqno
911 if total is not None and seqno is not None:
912 if total is not None and seqno is not None:
912 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
913 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
913 if pathname is not None:
914 if pathname is not None:
914 expander['s'] = lambda: os.path.basename(pathname)
915 expander['s'] = lambda: os.path.basename(pathname)
915 expander['d'] = lambda: os.path.dirname(pathname) or '.'
916 expander['d'] = lambda: os.path.dirname(pathname) or '.'
916 expander['p'] = lambda: pathname
917 expander['p'] = lambda: pathname
917
918
918 newname = []
919 newname = []
919 patlen = len(pat)
920 patlen = len(pat)
920 i = 0
921 i = 0
921 while i < patlen:
922 while i < patlen:
922 c = pat[i:i + 1]
923 c = pat[i:i + 1]
923 if c == '%':
924 if c == '%':
924 i += 1
925 i += 1
925 c = pat[i:i + 1]
926 c = pat[i:i + 1]
926 c = expander[c]()
927 c = expander[c]()
927 newname.append(c)
928 newname.append(c)
928 i += 1
929 i += 1
929 return ''.join(newname)
930 return ''.join(newname)
930 except KeyError as inst:
931 except KeyError as inst:
931 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
932 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
932 inst.args[0])
933 inst.args[0])
933
934
934 def isstdiofilename(pat):
935 def isstdiofilename(pat):
935 """True if the given pat looks like a filename denoting stdin/stdout"""
936 """True if the given pat looks like a filename denoting stdin/stdout"""
936 return not pat or pat == '-'
937 return not pat or pat == '-'
937
938
938 class _unclosablefile(object):
939 class _unclosablefile(object):
939 def __init__(self, fp):
940 def __init__(self, fp):
940 self._fp = fp
941 self._fp = fp
941
942
942 def close(self):
943 def close(self):
943 pass
944 pass
944
945
945 def __iter__(self):
946 def __iter__(self):
946 return iter(self._fp)
947 return iter(self._fp)
947
948
948 def __getattr__(self, attr):
949 def __getattr__(self, attr):
949 return getattr(self._fp, attr)
950 return getattr(self._fp, attr)
950
951
951 def __enter__(self):
952 def __enter__(self):
952 return self
953 return self
953
954
954 def __exit__(self, exc_type, exc_value, exc_tb):
955 def __exit__(self, exc_type, exc_value, exc_tb):
955 pass
956 pass
956
957
957 def makefileobj(ctx, pat, desc=None, total=None,
958 def makefileobj(ctx, pat, total=None,
958 seqno=None, revwidth=None, mode='wb', modemap=None,
959 seqno=None, revwidth=None, mode='wb', modemap=None,
959 pathname=None):
960 pathname=None):
960
961
961 writable = mode not in ('r', 'rb')
962 writable = mode not in ('r', 'rb')
962
963
963 if isstdiofilename(pat):
964 if isstdiofilename(pat):
964 repo = ctx.repo()
965 repo = ctx.repo()
965 if writable:
966 if writable:
966 fp = repo.ui.fout
967 fp = repo.ui.fout
967 else:
968 else:
968 fp = repo.ui.fin
969 fp = repo.ui.fin
969 return _unclosablefile(fp)
970 return _unclosablefile(fp)
970 fn = makefilename(ctx, pat, desc, total, seqno, revwidth, pathname)
971 fn = makefilename(ctx, pat, total, seqno, revwidth, pathname)
971 if modemap is not None:
972 if modemap is not None:
972 mode = modemap.get(fn, mode)
973 mode = modemap.get(fn, mode)
973 if mode == 'wb':
974 if mode == 'wb':
974 modemap[fn] = 'ab'
975 modemap[fn] = 'ab'
975 return open(fn, mode)
976 return open(fn, mode)
976
977
977 def openrevlog(repo, cmd, file_, opts):
978 def openrevlog(repo, cmd, file_, opts):
978 """opens the changelog, manifest, a filelog or a given revlog"""
979 """opens the changelog, manifest, a filelog or a given revlog"""
979 cl = opts['changelog']
980 cl = opts['changelog']
980 mf = opts['manifest']
981 mf = opts['manifest']
981 dir = opts['dir']
982 dir = opts['dir']
982 msg = None
983 msg = None
983 if cl and mf:
984 if cl and mf:
984 msg = _('cannot specify --changelog and --manifest at the same time')
985 msg = _('cannot specify --changelog and --manifest at the same time')
985 elif cl and dir:
986 elif cl and dir:
986 msg = _('cannot specify --changelog and --dir at the same time')
987 msg = _('cannot specify --changelog and --dir at the same time')
987 elif cl or mf or dir:
988 elif cl or mf or dir:
988 if file_:
989 if file_:
989 msg = _('cannot specify filename with --changelog or --manifest')
990 msg = _('cannot specify filename with --changelog or --manifest')
990 elif not repo:
991 elif not repo:
991 msg = _('cannot specify --changelog or --manifest or --dir '
992 msg = _('cannot specify --changelog or --manifest or --dir '
992 'without a repository')
993 'without a repository')
993 if msg:
994 if msg:
994 raise error.Abort(msg)
995 raise error.Abort(msg)
995
996
996 r = None
997 r = None
997 if repo:
998 if repo:
998 if cl:
999 if cl:
999 r = repo.unfiltered().changelog
1000 r = repo.unfiltered().changelog
1000 elif dir:
1001 elif dir:
1001 if 'treemanifest' not in repo.requirements:
1002 if 'treemanifest' not in repo.requirements:
1002 raise error.Abort(_("--dir can only be used on repos with "
1003 raise error.Abort(_("--dir can only be used on repos with "
1003 "treemanifest enabled"))
1004 "treemanifest enabled"))
1004 dirlog = repo.manifestlog._revlog.dirlog(dir)
1005 dirlog = repo.manifestlog._revlog.dirlog(dir)
1005 if len(dirlog):
1006 if len(dirlog):
1006 r = dirlog
1007 r = dirlog
1007 elif mf:
1008 elif mf:
1008 r = repo.manifestlog._revlog
1009 r = repo.manifestlog._revlog
1009 elif file_:
1010 elif file_:
1010 filelog = repo.file(file_)
1011 filelog = repo.file(file_)
1011 if len(filelog):
1012 if len(filelog):
1012 r = filelog
1013 r = filelog
1013 if not r:
1014 if not r:
1014 if not file_:
1015 if not file_:
1015 raise error.CommandError(cmd, _('invalid arguments'))
1016 raise error.CommandError(cmd, _('invalid arguments'))
1016 if not os.path.isfile(file_):
1017 if not os.path.isfile(file_):
1017 raise error.Abort(_("revlog '%s' not found") % file_)
1018 raise error.Abort(_("revlog '%s' not found") % file_)
1018 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1019 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1019 file_[:-2] + ".i")
1020 file_[:-2] + ".i")
1020 return r
1021 return r
1021
1022
1022 def copy(ui, repo, pats, opts, rename=False):
1023 def copy(ui, repo, pats, opts, rename=False):
1023 # called with the repo lock held
1024 # called with the repo lock held
1024 #
1025 #
1025 # hgsep => pathname that uses "/" to separate directories
1026 # hgsep => pathname that uses "/" to separate directories
1026 # ossep => pathname that uses os.sep to separate directories
1027 # ossep => pathname that uses os.sep to separate directories
1027 cwd = repo.getcwd()
1028 cwd = repo.getcwd()
1028 targets = {}
1029 targets = {}
1029 after = opts.get("after")
1030 after = opts.get("after")
1030 dryrun = opts.get("dry_run")
1031 dryrun = opts.get("dry_run")
1031 wctx = repo[None]
1032 wctx = repo[None]
1032
1033
1033 def walkpat(pat):
1034 def walkpat(pat):
1034 srcs = []
1035 srcs = []
1035 if after:
1036 if after:
1036 badstates = '?'
1037 badstates = '?'
1037 else:
1038 else:
1038 badstates = '?r'
1039 badstates = '?r'
1039 m = scmutil.match(wctx, [pat], opts, globbed=True)
1040 m = scmutil.match(wctx, [pat], opts, globbed=True)
1040 for abs in wctx.walk(m):
1041 for abs in wctx.walk(m):
1041 state = repo.dirstate[abs]
1042 state = repo.dirstate[abs]
1042 rel = m.rel(abs)
1043 rel = m.rel(abs)
1043 exact = m.exact(abs)
1044 exact = m.exact(abs)
1044 if state in badstates:
1045 if state in badstates:
1045 if exact and state == '?':
1046 if exact and state == '?':
1046 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1047 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1047 if exact and state == 'r':
1048 if exact and state == 'r':
1048 ui.warn(_('%s: not copying - file has been marked for'
1049 ui.warn(_('%s: not copying - file has been marked for'
1049 ' remove\n') % rel)
1050 ' remove\n') % rel)
1050 continue
1051 continue
1051 # abs: hgsep
1052 # abs: hgsep
1052 # rel: ossep
1053 # rel: ossep
1053 srcs.append((abs, rel, exact))
1054 srcs.append((abs, rel, exact))
1054 return srcs
1055 return srcs
1055
1056
1056 # abssrc: hgsep
1057 # abssrc: hgsep
1057 # relsrc: ossep
1058 # relsrc: ossep
1058 # otarget: ossep
1059 # otarget: ossep
1059 def copyfile(abssrc, relsrc, otarget, exact):
1060 def copyfile(abssrc, relsrc, otarget, exact):
1060 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1061 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1061 if '/' in abstarget:
1062 if '/' in abstarget:
1062 # We cannot normalize abstarget itself, this would prevent
1063 # We cannot normalize abstarget itself, this would prevent
1063 # case only renames, like a => A.
1064 # case only renames, like a => A.
1064 abspath, absname = abstarget.rsplit('/', 1)
1065 abspath, absname = abstarget.rsplit('/', 1)
1065 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1066 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1066 reltarget = repo.pathto(abstarget, cwd)
1067 reltarget = repo.pathto(abstarget, cwd)
1067 target = repo.wjoin(abstarget)
1068 target = repo.wjoin(abstarget)
1068 src = repo.wjoin(abssrc)
1069 src = repo.wjoin(abssrc)
1069 state = repo.dirstate[abstarget]
1070 state = repo.dirstate[abstarget]
1070
1071
1071 scmutil.checkportable(ui, abstarget)
1072 scmutil.checkportable(ui, abstarget)
1072
1073
1073 # check for collisions
1074 # check for collisions
1074 prevsrc = targets.get(abstarget)
1075 prevsrc = targets.get(abstarget)
1075 if prevsrc is not None:
1076 if prevsrc is not None:
1076 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1077 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1077 (reltarget, repo.pathto(abssrc, cwd),
1078 (reltarget, repo.pathto(abssrc, cwd),
1078 repo.pathto(prevsrc, cwd)))
1079 repo.pathto(prevsrc, cwd)))
1079 return
1080 return
1080
1081
1081 # check for overwrites
1082 # check for overwrites
1082 exists = os.path.lexists(target)
1083 exists = os.path.lexists(target)
1083 samefile = False
1084 samefile = False
1084 if exists and abssrc != abstarget:
1085 if exists and abssrc != abstarget:
1085 if (repo.dirstate.normalize(abssrc) ==
1086 if (repo.dirstate.normalize(abssrc) ==
1086 repo.dirstate.normalize(abstarget)):
1087 repo.dirstate.normalize(abstarget)):
1087 if not rename:
1088 if not rename:
1088 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1089 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1089 return
1090 return
1090 exists = False
1091 exists = False
1091 samefile = True
1092 samefile = True
1092
1093
1093 if not after and exists or after and state in 'mn':
1094 if not after and exists or after and state in 'mn':
1094 if not opts['force']:
1095 if not opts['force']:
1095 if state in 'mn':
1096 if state in 'mn':
1096 msg = _('%s: not overwriting - file already committed\n')
1097 msg = _('%s: not overwriting - file already committed\n')
1097 if after:
1098 if after:
1098 flags = '--after --force'
1099 flags = '--after --force'
1099 else:
1100 else:
1100 flags = '--force'
1101 flags = '--force'
1101 if rename:
1102 if rename:
1102 hint = _('(hg rename %s to replace the file by '
1103 hint = _('(hg rename %s to replace the file by '
1103 'recording a rename)\n') % flags
1104 'recording a rename)\n') % flags
1104 else:
1105 else:
1105 hint = _('(hg copy %s to replace the file by '
1106 hint = _('(hg copy %s to replace the file by '
1106 'recording a copy)\n') % flags
1107 'recording a copy)\n') % flags
1107 else:
1108 else:
1108 msg = _('%s: not overwriting - file exists\n')
1109 msg = _('%s: not overwriting - file exists\n')
1109 if rename:
1110 if rename:
1110 hint = _('(hg rename --after to record the rename)\n')
1111 hint = _('(hg rename --after to record the rename)\n')
1111 else:
1112 else:
1112 hint = _('(hg copy --after to record the copy)\n')
1113 hint = _('(hg copy --after to record the copy)\n')
1113 ui.warn(msg % reltarget)
1114 ui.warn(msg % reltarget)
1114 ui.warn(hint)
1115 ui.warn(hint)
1115 return
1116 return
1116
1117
1117 if after:
1118 if after:
1118 if not exists:
1119 if not exists:
1119 if rename:
1120 if rename:
1120 ui.warn(_('%s: not recording move - %s does not exist\n') %
1121 ui.warn(_('%s: not recording move - %s does not exist\n') %
1121 (relsrc, reltarget))
1122 (relsrc, reltarget))
1122 else:
1123 else:
1123 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1124 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1124 (relsrc, reltarget))
1125 (relsrc, reltarget))
1125 return
1126 return
1126 elif not dryrun:
1127 elif not dryrun:
1127 try:
1128 try:
1128 if exists:
1129 if exists:
1129 os.unlink(target)
1130 os.unlink(target)
1130 targetdir = os.path.dirname(target) or '.'
1131 targetdir = os.path.dirname(target) or '.'
1131 if not os.path.isdir(targetdir):
1132 if not os.path.isdir(targetdir):
1132 os.makedirs(targetdir)
1133 os.makedirs(targetdir)
1133 if samefile:
1134 if samefile:
1134 tmp = target + "~hgrename"
1135 tmp = target + "~hgrename"
1135 os.rename(src, tmp)
1136 os.rename(src, tmp)
1136 os.rename(tmp, target)
1137 os.rename(tmp, target)
1137 else:
1138 else:
1138 util.copyfile(src, target)
1139 util.copyfile(src, target)
1139 srcexists = True
1140 srcexists = True
1140 except IOError as inst:
1141 except IOError as inst:
1141 if inst.errno == errno.ENOENT:
1142 if inst.errno == errno.ENOENT:
1142 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1143 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1143 srcexists = False
1144 srcexists = False
1144 else:
1145 else:
1145 ui.warn(_('%s: cannot copy - %s\n') %
1146 ui.warn(_('%s: cannot copy - %s\n') %
1146 (relsrc, encoding.strtolocal(inst.strerror)))
1147 (relsrc, encoding.strtolocal(inst.strerror)))
1147 return True # report a failure
1148 return True # report a failure
1148
1149
1149 if ui.verbose or not exact:
1150 if ui.verbose or not exact:
1150 if rename:
1151 if rename:
1151 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1152 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1152 else:
1153 else:
1153 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1154 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1154
1155
1155 targets[abstarget] = abssrc
1156 targets[abstarget] = abssrc
1156
1157
1157 # fix up dirstate
1158 # fix up dirstate
1158 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1159 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1159 dryrun=dryrun, cwd=cwd)
1160 dryrun=dryrun, cwd=cwd)
1160 if rename and not dryrun:
1161 if rename and not dryrun:
1161 if not after and srcexists and not samefile:
1162 if not after and srcexists and not samefile:
1162 repo.wvfs.unlinkpath(abssrc)
1163 repo.wvfs.unlinkpath(abssrc)
1163 wctx.forget([abssrc])
1164 wctx.forget([abssrc])
1164
1165
1165 # pat: ossep
1166 # pat: ossep
1166 # dest ossep
1167 # dest ossep
1167 # srcs: list of (hgsep, hgsep, ossep, bool)
1168 # srcs: list of (hgsep, hgsep, ossep, bool)
1168 # return: function that takes hgsep and returns ossep
1169 # return: function that takes hgsep and returns ossep
1169 def targetpathfn(pat, dest, srcs):
1170 def targetpathfn(pat, dest, srcs):
1170 if os.path.isdir(pat):
1171 if os.path.isdir(pat):
1171 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1172 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1172 abspfx = util.localpath(abspfx)
1173 abspfx = util.localpath(abspfx)
1173 if destdirexists:
1174 if destdirexists:
1174 striplen = len(os.path.split(abspfx)[0])
1175 striplen = len(os.path.split(abspfx)[0])
1175 else:
1176 else:
1176 striplen = len(abspfx)
1177 striplen = len(abspfx)
1177 if striplen:
1178 if striplen:
1178 striplen += len(pycompat.ossep)
1179 striplen += len(pycompat.ossep)
1179 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1180 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1180 elif destdirexists:
1181 elif destdirexists:
1181 res = lambda p: os.path.join(dest,
1182 res = lambda p: os.path.join(dest,
1182 os.path.basename(util.localpath(p)))
1183 os.path.basename(util.localpath(p)))
1183 else:
1184 else:
1184 res = lambda p: dest
1185 res = lambda p: dest
1185 return res
1186 return res
1186
1187
1187 # pat: ossep
1188 # pat: ossep
1188 # dest ossep
1189 # dest ossep
1189 # srcs: list of (hgsep, hgsep, ossep, bool)
1190 # srcs: list of (hgsep, hgsep, ossep, bool)
1190 # return: function that takes hgsep and returns ossep
1191 # return: function that takes hgsep and returns ossep
1191 def targetpathafterfn(pat, dest, srcs):
1192 def targetpathafterfn(pat, dest, srcs):
1192 if matchmod.patkind(pat):
1193 if matchmod.patkind(pat):
1193 # a mercurial pattern
1194 # a mercurial pattern
1194 res = lambda p: os.path.join(dest,
1195 res = lambda p: os.path.join(dest,
1195 os.path.basename(util.localpath(p)))
1196 os.path.basename(util.localpath(p)))
1196 else:
1197 else:
1197 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1198 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1198 if len(abspfx) < len(srcs[0][0]):
1199 if len(abspfx) < len(srcs[0][0]):
1199 # A directory. Either the target path contains the last
1200 # A directory. Either the target path contains the last
1200 # component of the source path or it does not.
1201 # component of the source path or it does not.
1201 def evalpath(striplen):
1202 def evalpath(striplen):
1202 score = 0
1203 score = 0
1203 for s in srcs:
1204 for s in srcs:
1204 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1205 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1205 if os.path.lexists(t):
1206 if os.path.lexists(t):
1206 score += 1
1207 score += 1
1207 return score
1208 return score
1208
1209
1209 abspfx = util.localpath(abspfx)
1210 abspfx = util.localpath(abspfx)
1210 striplen = len(abspfx)
1211 striplen = len(abspfx)
1211 if striplen:
1212 if striplen:
1212 striplen += len(pycompat.ossep)
1213 striplen += len(pycompat.ossep)
1213 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1214 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1214 score = evalpath(striplen)
1215 score = evalpath(striplen)
1215 striplen1 = len(os.path.split(abspfx)[0])
1216 striplen1 = len(os.path.split(abspfx)[0])
1216 if striplen1:
1217 if striplen1:
1217 striplen1 += len(pycompat.ossep)
1218 striplen1 += len(pycompat.ossep)
1218 if evalpath(striplen1) > score:
1219 if evalpath(striplen1) > score:
1219 striplen = striplen1
1220 striplen = striplen1
1220 res = lambda p: os.path.join(dest,
1221 res = lambda p: os.path.join(dest,
1221 util.localpath(p)[striplen:])
1222 util.localpath(p)[striplen:])
1222 else:
1223 else:
1223 # a file
1224 # a file
1224 if destdirexists:
1225 if destdirexists:
1225 res = lambda p: os.path.join(dest,
1226 res = lambda p: os.path.join(dest,
1226 os.path.basename(util.localpath(p)))
1227 os.path.basename(util.localpath(p)))
1227 else:
1228 else:
1228 res = lambda p: dest
1229 res = lambda p: dest
1229 return res
1230 return res
1230
1231
1231 pats = scmutil.expandpats(pats)
1232 pats = scmutil.expandpats(pats)
1232 if not pats:
1233 if not pats:
1233 raise error.Abort(_('no source or destination specified'))
1234 raise error.Abort(_('no source or destination specified'))
1234 if len(pats) == 1:
1235 if len(pats) == 1:
1235 raise error.Abort(_('no destination specified'))
1236 raise error.Abort(_('no destination specified'))
1236 dest = pats.pop()
1237 dest = pats.pop()
1237 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1238 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1238 if not destdirexists:
1239 if not destdirexists:
1239 if len(pats) > 1 or matchmod.patkind(pats[0]):
1240 if len(pats) > 1 or matchmod.patkind(pats[0]):
1240 raise error.Abort(_('with multiple sources, destination must be an '
1241 raise error.Abort(_('with multiple sources, destination must be an '
1241 'existing directory'))
1242 'existing directory'))
1242 if util.endswithsep(dest):
1243 if util.endswithsep(dest):
1243 raise error.Abort(_('destination %s is not a directory') % dest)
1244 raise error.Abort(_('destination %s is not a directory') % dest)
1244
1245
1245 tfn = targetpathfn
1246 tfn = targetpathfn
1246 if after:
1247 if after:
1247 tfn = targetpathafterfn
1248 tfn = targetpathafterfn
1248 copylist = []
1249 copylist = []
1249 for pat in pats:
1250 for pat in pats:
1250 srcs = walkpat(pat)
1251 srcs = walkpat(pat)
1251 if not srcs:
1252 if not srcs:
1252 continue
1253 continue
1253 copylist.append((tfn(pat, dest, srcs), srcs))
1254 copylist.append((tfn(pat, dest, srcs), srcs))
1254 if not copylist:
1255 if not copylist:
1255 raise error.Abort(_('no files to copy'))
1256 raise error.Abort(_('no files to copy'))
1256
1257
1257 errors = 0
1258 errors = 0
1258 for targetpath, srcs in copylist:
1259 for targetpath, srcs in copylist:
1259 for abssrc, relsrc, exact in srcs:
1260 for abssrc, relsrc, exact in srcs:
1260 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1261 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1261 errors += 1
1262 errors += 1
1262
1263
1263 if errors:
1264 if errors:
1264 ui.warn(_('(consider using --after)\n'))
1265 ui.warn(_('(consider using --after)\n'))
1265
1266
1266 return errors != 0
1267 return errors != 0
1267
1268
1268 ## facility to let extension process additional data into an import patch
1269 ## facility to let extension process additional data into an import patch
1269 # list of identifier to be executed in order
1270 # list of identifier to be executed in order
1270 extrapreimport = [] # run before commit
1271 extrapreimport = [] # run before commit
1271 extrapostimport = [] # run after commit
1272 extrapostimport = [] # run after commit
1272 # mapping from identifier to actual import function
1273 # mapping from identifier to actual import function
1273 #
1274 #
1274 # 'preimport' are run before the commit is made and are provided the following
1275 # 'preimport' are run before the commit is made and are provided the following
1275 # arguments:
1276 # arguments:
1276 # - repo: the localrepository instance,
1277 # - repo: the localrepository instance,
1277 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1278 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1278 # - extra: the future extra dictionary of the changeset, please mutate it,
1279 # - extra: the future extra dictionary of the changeset, please mutate it,
1279 # - opts: the import options.
1280 # - opts: the import options.
1280 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1281 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1281 # mutation of in memory commit and more. Feel free to rework the code to get
1282 # mutation of in memory commit and more. Feel free to rework the code to get
1282 # there.
1283 # there.
1283 extrapreimportmap = {}
1284 extrapreimportmap = {}
1284 # 'postimport' are run after the commit is made and are provided the following
1285 # 'postimport' are run after the commit is made and are provided the following
1285 # argument:
1286 # argument:
1286 # - ctx: the changectx created by import.
1287 # - ctx: the changectx created by import.
1287 extrapostimportmap = {}
1288 extrapostimportmap = {}
1288
1289
1289 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1290 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1290 """Utility function used by commands.import to import a single patch
1291 """Utility function used by commands.import to import a single patch
1291
1292
1292 This function is explicitly defined here to help the evolve extension to
1293 This function is explicitly defined here to help the evolve extension to
1293 wrap this part of the import logic.
1294 wrap this part of the import logic.
1294
1295
1295 The API is currently a bit ugly because it a simple code translation from
1296 The API is currently a bit ugly because it a simple code translation from
1296 the import command. Feel free to make it better.
1297 the import command. Feel free to make it better.
1297
1298
1298 :hunk: a patch (as a binary string)
1299 :hunk: a patch (as a binary string)
1299 :parents: nodes that will be parent of the created commit
1300 :parents: nodes that will be parent of the created commit
1300 :opts: the full dict of option passed to the import command
1301 :opts: the full dict of option passed to the import command
1301 :msgs: list to save commit message to.
1302 :msgs: list to save commit message to.
1302 (used in case we need to save it when failing)
1303 (used in case we need to save it when failing)
1303 :updatefunc: a function that update a repo to a given node
1304 :updatefunc: a function that update a repo to a given node
1304 updatefunc(<repo>, <node>)
1305 updatefunc(<repo>, <node>)
1305 """
1306 """
1306 # avoid cycle context -> subrepo -> cmdutil
1307 # avoid cycle context -> subrepo -> cmdutil
1307 from . import context
1308 from . import context
1308 extractdata = patch.extract(ui, hunk)
1309 extractdata = patch.extract(ui, hunk)
1309 tmpname = extractdata.get('filename')
1310 tmpname = extractdata.get('filename')
1310 message = extractdata.get('message')
1311 message = extractdata.get('message')
1311 user = opts.get('user') or extractdata.get('user')
1312 user = opts.get('user') or extractdata.get('user')
1312 date = opts.get('date') or extractdata.get('date')
1313 date = opts.get('date') or extractdata.get('date')
1313 branch = extractdata.get('branch')
1314 branch = extractdata.get('branch')
1314 nodeid = extractdata.get('nodeid')
1315 nodeid = extractdata.get('nodeid')
1315 p1 = extractdata.get('p1')
1316 p1 = extractdata.get('p1')
1316 p2 = extractdata.get('p2')
1317 p2 = extractdata.get('p2')
1317
1318
1318 nocommit = opts.get('no_commit')
1319 nocommit = opts.get('no_commit')
1319 importbranch = opts.get('import_branch')
1320 importbranch = opts.get('import_branch')
1320 update = not opts.get('bypass')
1321 update = not opts.get('bypass')
1321 strip = opts["strip"]
1322 strip = opts["strip"]
1322 prefix = opts["prefix"]
1323 prefix = opts["prefix"]
1323 sim = float(opts.get('similarity') or 0)
1324 sim = float(opts.get('similarity') or 0)
1324 if not tmpname:
1325 if not tmpname:
1325 return (None, None, False)
1326 return (None, None, False)
1326
1327
1327 rejects = False
1328 rejects = False
1328
1329
1329 try:
1330 try:
1330 cmdline_message = logmessage(ui, opts)
1331 cmdline_message = logmessage(ui, opts)
1331 if cmdline_message:
1332 if cmdline_message:
1332 # pickup the cmdline msg
1333 # pickup the cmdline msg
1333 message = cmdline_message
1334 message = cmdline_message
1334 elif message:
1335 elif message:
1335 # pickup the patch msg
1336 # pickup the patch msg
1336 message = message.strip()
1337 message = message.strip()
1337 else:
1338 else:
1338 # launch the editor
1339 # launch the editor
1339 message = None
1340 message = None
1340 ui.debug('message:\n%s\n' % message)
1341 ui.debug('message:\n%s\n' % message)
1341
1342
1342 if len(parents) == 1:
1343 if len(parents) == 1:
1343 parents.append(repo[nullid])
1344 parents.append(repo[nullid])
1344 if opts.get('exact'):
1345 if opts.get('exact'):
1345 if not nodeid or not p1:
1346 if not nodeid or not p1:
1346 raise error.Abort(_('not a Mercurial patch'))
1347 raise error.Abort(_('not a Mercurial patch'))
1347 p1 = repo[p1]
1348 p1 = repo[p1]
1348 p2 = repo[p2 or nullid]
1349 p2 = repo[p2 or nullid]
1349 elif p2:
1350 elif p2:
1350 try:
1351 try:
1351 p1 = repo[p1]
1352 p1 = repo[p1]
1352 p2 = repo[p2]
1353 p2 = repo[p2]
1353 # Without any options, consider p2 only if the
1354 # Without any options, consider p2 only if the
1354 # patch is being applied on top of the recorded
1355 # patch is being applied on top of the recorded
1355 # first parent.
1356 # first parent.
1356 if p1 != parents[0]:
1357 if p1 != parents[0]:
1357 p1 = parents[0]
1358 p1 = parents[0]
1358 p2 = repo[nullid]
1359 p2 = repo[nullid]
1359 except error.RepoError:
1360 except error.RepoError:
1360 p1, p2 = parents
1361 p1, p2 = parents
1361 if p2.node() == nullid:
1362 if p2.node() == nullid:
1362 ui.warn(_("warning: import the patch as a normal revision\n"
1363 ui.warn(_("warning: import the patch as a normal revision\n"
1363 "(use --exact to import the patch as a merge)\n"))
1364 "(use --exact to import the patch as a merge)\n"))
1364 else:
1365 else:
1365 p1, p2 = parents
1366 p1, p2 = parents
1366
1367
1367 n = None
1368 n = None
1368 if update:
1369 if update:
1369 if p1 != parents[0]:
1370 if p1 != parents[0]:
1370 updatefunc(repo, p1.node())
1371 updatefunc(repo, p1.node())
1371 if p2 != parents[1]:
1372 if p2 != parents[1]:
1372 repo.setparents(p1.node(), p2.node())
1373 repo.setparents(p1.node(), p2.node())
1373
1374
1374 if opts.get('exact') or importbranch:
1375 if opts.get('exact') or importbranch:
1375 repo.dirstate.setbranch(branch or 'default')
1376 repo.dirstate.setbranch(branch or 'default')
1376
1377
1377 partial = opts.get('partial', False)
1378 partial = opts.get('partial', False)
1378 files = set()
1379 files = set()
1379 try:
1380 try:
1380 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1381 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1381 files=files, eolmode=None, similarity=sim / 100.0)
1382 files=files, eolmode=None, similarity=sim / 100.0)
1382 except error.PatchError as e:
1383 except error.PatchError as e:
1383 if not partial:
1384 if not partial:
1384 raise error.Abort(str(e))
1385 raise error.Abort(str(e))
1385 if partial:
1386 if partial:
1386 rejects = True
1387 rejects = True
1387
1388
1388 files = list(files)
1389 files = list(files)
1389 if nocommit:
1390 if nocommit:
1390 if message:
1391 if message:
1391 msgs.append(message)
1392 msgs.append(message)
1392 else:
1393 else:
1393 if opts.get('exact') or p2:
1394 if opts.get('exact') or p2:
1394 # If you got here, you either use --force and know what
1395 # If you got here, you either use --force and know what
1395 # you are doing or used --exact or a merge patch while
1396 # you are doing or used --exact or a merge patch while
1396 # being updated to its first parent.
1397 # being updated to its first parent.
1397 m = None
1398 m = None
1398 else:
1399 else:
1399 m = scmutil.matchfiles(repo, files or [])
1400 m = scmutil.matchfiles(repo, files or [])
1400 editform = mergeeditform(repo[None], 'import.normal')
1401 editform = mergeeditform(repo[None], 'import.normal')
1401 if opts.get('exact'):
1402 if opts.get('exact'):
1402 editor = None
1403 editor = None
1403 else:
1404 else:
1404 editor = getcommiteditor(editform=editform,
1405 editor = getcommiteditor(editform=editform,
1405 **pycompat.strkwargs(opts))
1406 **pycompat.strkwargs(opts))
1406 extra = {}
1407 extra = {}
1407 for idfunc in extrapreimport:
1408 for idfunc in extrapreimport:
1408 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1409 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1409 overrides = {}
1410 overrides = {}
1410 if partial:
1411 if partial:
1411 overrides[('ui', 'allowemptycommit')] = True
1412 overrides[('ui', 'allowemptycommit')] = True
1412 with repo.ui.configoverride(overrides, 'import'):
1413 with repo.ui.configoverride(overrides, 'import'):
1413 n = repo.commit(message, user,
1414 n = repo.commit(message, user,
1414 date, match=m,
1415 date, match=m,
1415 editor=editor, extra=extra)
1416 editor=editor, extra=extra)
1416 for idfunc in extrapostimport:
1417 for idfunc in extrapostimport:
1417 extrapostimportmap[idfunc](repo[n])
1418 extrapostimportmap[idfunc](repo[n])
1418 else:
1419 else:
1419 if opts.get('exact') or importbranch:
1420 if opts.get('exact') or importbranch:
1420 branch = branch or 'default'
1421 branch = branch or 'default'
1421 else:
1422 else:
1422 branch = p1.branch()
1423 branch = p1.branch()
1423 store = patch.filestore()
1424 store = patch.filestore()
1424 try:
1425 try:
1425 files = set()
1426 files = set()
1426 try:
1427 try:
1427 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1428 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1428 files, eolmode=None)
1429 files, eolmode=None)
1429 except error.PatchError as e:
1430 except error.PatchError as e:
1430 raise error.Abort(str(e))
1431 raise error.Abort(str(e))
1431 if opts.get('exact'):
1432 if opts.get('exact'):
1432 editor = None
1433 editor = None
1433 else:
1434 else:
1434 editor = getcommiteditor(editform='import.bypass')
1435 editor = getcommiteditor(editform='import.bypass')
1435 memctx = context.memctx(repo, (p1.node(), p2.node()),
1436 memctx = context.memctx(repo, (p1.node(), p2.node()),
1436 message,
1437 message,
1437 files=files,
1438 files=files,
1438 filectxfn=store,
1439 filectxfn=store,
1439 user=user,
1440 user=user,
1440 date=date,
1441 date=date,
1441 branch=branch,
1442 branch=branch,
1442 editor=editor)
1443 editor=editor)
1443 n = memctx.commit()
1444 n = memctx.commit()
1444 finally:
1445 finally:
1445 store.close()
1446 store.close()
1446 if opts.get('exact') and nocommit:
1447 if opts.get('exact') and nocommit:
1447 # --exact with --no-commit is still useful in that it does merge
1448 # --exact with --no-commit is still useful in that it does merge
1448 # and branch bits
1449 # and branch bits
1449 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1450 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1450 elif opts.get('exact') and hex(n) != nodeid:
1451 elif opts.get('exact') and hex(n) != nodeid:
1451 raise error.Abort(_('patch is damaged or loses information'))
1452 raise error.Abort(_('patch is damaged or loses information'))
1452 msg = _('applied to working directory')
1453 msg = _('applied to working directory')
1453 if n:
1454 if n:
1454 # i18n: refers to a short changeset id
1455 # i18n: refers to a short changeset id
1455 msg = _('created %s') % short(n)
1456 msg = _('created %s') % short(n)
1456 return (msg, n, rejects)
1457 return (msg, n, rejects)
1457 finally:
1458 finally:
1458 os.unlink(tmpname)
1459 os.unlink(tmpname)
1459
1460
1460 # facility to let extensions include additional data in an exported patch
1461 # facility to let extensions include additional data in an exported patch
1461 # list of identifiers to be executed in order
1462 # list of identifiers to be executed in order
1462 extraexport = []
1463 extraexport = []
1463 # mapping from identifier to actual export function
1464 # mapping from identifier to actual export function
1464 # function as to return a string to be added to the header or None
1465 # function as to return a string to be added to the header or None
1465 # it is given two arguments (sequencenumber, changectx)
1466 # it is given two arguments (sequencenumber, changectx)
1466 extraexportmap = {}
1467 extraexportmap = {}
1467
1468
1468 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1469 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1469 node = scmutil.binnode(ctx)
1470 node = scmutil.binnode(ctx)
1470 parents = [p.node() for p in ctx.parents() if p]
1471 parents = [p.node() for p in ctx.parents() if p]
1471 branch = ctx.branch()
1472 branch = ctx.branch()
1472 if switch_parent:
1473 if switch_parent:
1473 parents.reverse()
1474 parents.reverse()
1474
1475
1475 if parents:
1476 if parents:
1476 prev = parents[0]
1477 prev = parents[0]
1477 else:
1478 else:
1478 prev = nullid
1479 prev = nullid
1479
1480
1480 write("# HG changeset patch\n")
1481 write("# HG changeset patch\n")
1481 write("# User %s\n" % ctx.user())
1482 write("# User %s\n" % ctx.user())
1482 write("# Date %d %d\n" % ctx.date())
1483 write("# Date %d %d\n" % ctx.date())
1483 write("# %s\n" % util.datestr(ctx.date()))
1484 write("# %s\n" % util.datestr(ctx.date()))
1484 if branch and branch != 'default':
1485 if branch and branch != 'default':
1485 write("# Branch %s\n" % branch)
1486 write("# Branch %s\n" % branch)
1486 write("# Node ID %s\n" % hex(node))
1487 write("# Node ID %s\n" % hex(node))
1487 write("# Parent %s\n" % hex(prev))
1488 write("# Parent %s\n" % hex(prev))
1488 if len(parents) > 1:
1489 if len(parents) > 1:
1489 write("# Parent %s\n" % hex(parents[1]))
1490 write("# Parent %s\n" % hex(parents[1]))
1490
1491
1491 for headerid in extraexport:
1492 for headerid in extraexport:
1492 header = extraexportmap[headerid](seqno, ctx)
1493 header = extraexportmap[headerid](seqno, ctx)
1493 if header is not None:
1494 if header is not None:
1494 write('# %s\n' % header)
1495 write('# %s\n' % header)
1495 write(ctx.description().rstrip())
1496 write(ctx.description().rstrip())
1496 write("\n\n")
1497 write("\n\n")
1497
1498
1498 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1499 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1499 write(chunk, label=label)
1500 write(chunk, label=label)
1500
1501
1501 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1502 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1502 opts=None, match=None):
1503 opts=None, match=None):
1503 '''export changesets as hg patches
1504 '''export changesets as hg patches
1504
1505
1505 Args:
1506 Args:
1506 repo: The repository from which we're exporting revisions.
1507 repo: The repository from which we're exporting revisions.
1507 revs: A list of revisions to export as revision numbers.
1508 revs: A list of revisions to export as revision numbers.
1508 fntemplate: An optional string to use for generating patch file names.
1509 fntemplate: An optional string to use for generating patch file names.
1509 fp: An optional file-like object to which patches should be written.
1510 fp: An optional file-like object to which patches should be written.
1510 switch_parent: If True, show diffs against second parent when not nullid.
1511 switch_parent: If True, show diffs against second parent when not nullid.
1511 Default is false, which always shows diff against p1.
1512 Default is false, which always shows diff against p1.
1512 opts: diff options to use for generating the patch.
1513 opts: diff options to use for generating the patch.
1513 match: If specified, only export changes to files matching this matcher.
1514 match: If specified, only export changes to files matching this matcher.
1514
1515
1515 Returns:
1516 Returns:
1516 Nothing.
1517 Nothing.
1517
1518
1518 Side Effect:
1519 Side Effect:
1519 "HG Changeset Patch" data is emitted to one of the following
1520 "HG Changeset Patch" data is emitted to one of the following
1520 destinations:
1521 destinations:
1521 fp is specified: All revs are written to the specified
1522 fp is specified: All revs are written to the specified
1522 file-like object.
1523 file-like object.
1523 fntemplate specified: Each rev is written to a unique file named using
1524 fntemplate specified: Each rev is written to a unique file named using
1524 the given template.
1525 the given template.
1525 Neither fp nor template specified: All revs written to repo.ui.write()
1526 Neither fp nor template specified: All revs written to repo.ui.write()
1526 '''
1527 '''
1527
1528
1528 total = len(revs)
1529 total = len(revs)
1529 revwidth = max(len(str(rev)) for rev in revs)
1530 revwidth = max(len(str(rev)) for rev in revs)
1530 filemode = {}
1531 filemode = {}
1531
1532
1532 write = None
1533 write = None
1533 dest = '<unnamed>'
1534 dest = '<unnamed>'
1534 if fp:
1535 if fp:
1535 dest = getattr(fp, 'name', dest)
1536 dest = getattr(fp, 'name', dest)
1536 def write(s, **kw):
1537 def write(s, **kw):
1537 fp.write(s)
1538 fp.write(s)
1538 elif not fntemplate:
1539 elif not fntemplate:
1539 write = repo.ui.write
1540 write = repo.ui.write
1540
1541
1541 for seqno, rev in enumerate(revs, 1):
1542 for seqno, rev in enumerate(revs, 1):
1542 ctx = repo[rev]
1543 ctx = repo[rev]
1543 fo = None
1544 fo = None
1544 if not fp and fntemplate:
1545 if not fp and fntemplate:
1545 desc_lines = ctx.description().rstrip().split('\n')
1546 fo = makefileobj(ctx, fntemplate,
1546 desc = desc_lines[0] #Commit always has a first line.
1547 fo = makefileobj(ctx, fntemplate, desc=desc,
1548 total=total, seqno=seqno, revwidth=revwidth,
1547 total=total, seqno=seqno, revwidth=revwidth,
1549 mode='wb', modemap=filemode)
1548 mode='wb', modemap=filemode)
1550 dest = fo.name
1549 dest = fo.name
1551 def write(s, **kw):
1550 def write(s, **kw):
1552 fo.write(s)
1551 fo.write(s)
1553 if not dest.startswith('<'):
1552 if not dest.startswith('<'):
1554 repo.ui.note("%s\n" % dest)
1553 repo.ui.note("%s\n" % dest)
1555 _exportsingle(
1554 _exportsingle(
1556 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1555 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1557 if fo is not None:
1556 if fo is not None:
1558 fo.close()
1557 fo.close()
1559
1558
1560 def showmarker(fm, marker, index=None):
1559 def showmarker(fm, marker, index=None):
1561 """utility function to display obsolescence marker in a readable way
1560 """utility function to display obsolescence marker in a readable way
1562
1561
1563 To be used by debug function."""
1562 To be used by debug function."""
1564 if index is not None:
1563 if index is not None:
1565 fm.write('index', '%i ', index)
1564 fm.write('index', '%i ', index)
1566 fm.write('prednode', '%s ', hex(marker.prednode()))
1565 fm.write('prednode', '%s ', hex(marker.prednode()))
1567 succs = marker.succnodes()
1566 succs = marker.succnodes()
1568 fm.condwrite(succs, 'succnodes', '%s ',
1567 fm.condwrite(succs, 'succnodes', '%s ',
1569 fm.formatlist(map(hex, succs), name='node'))
1568 fm.formatlist(map(hex, succs), name='node'))
1570 fm.write('flag', '%X ', marker.flags())
1569 fm.write('flag', '%X ', marker.flags())
1571 parents = marker.parentnodes()
1570 parents = marker.parentnodes()
1572 if parents is not None:
1571 if parents is not None:
1573 fm.write('parentnodes', '{%s} ',
1572 fm.write('parentnodes', '{%s} ',
1574 fm.formatlist(map(hex, parents), name='node', sep=', '))
1573 fm.formatlist(map(hex, parents), name='node', sep=', '))
1575 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1574 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1576 meta = marker.metadata().copy()
1575 meta = marker.metadata().copy()
1577 meta.pop('date', None)
1576 meta.pop('date', None)
1578 smeta = util.rapply(pycompat.maybebytestr, meta)
1577 smeta = util.rapply(pycompat.maybebytestr, meta)
1579 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1578 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1580 fm.plain('\n')
1579 fm.plain('\n')
1581
1580
1582 def finddate(ui, repo, date):
1581 def finddate(ui, repo, date):
1583 """Find the tipmost changeset that matches the given date spec"""
1582 """Find the tipmost changeset that matches the given date spec"""
1584
1583
1585 df = util.matchdate(date)
1584 df = util.matchdate(date)
1586 m = scmutil.matchall(repo)
1585 m = scmutil.matchall(repo)
1587 results = {}
1586 results = {}
1588
1587
1589 def prep(ctx, fns):
1588 def prep(ctx, fns):
1590 d = ctx.date()
1589 d = ctx.date()
1591 if df(d[0]):
1590 if df(d[0]):
1592 results[ctx.rev()] = d
1591 results[ctx.rev()] = d
1593
1592
1594 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1593 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1595 rev = ctx.rev()
1594 rev = ctx.rev()
1596 if rev in results:
1595 if rev in results:
1597 ui.status(_("found revision %s from %s\n") %
1596 ui.status(_("found revision %s from %s\n") %
1598 (rev, util.datestr(results[rev])))
1597 (rev, util.datestr(results[rev])))
1599 return '%d' % rev
1598 return '%d' % rev
1600
1599
1601 raise error.Abort(_("revision matching date not found"))
1600 raise error.Abort(_("revision matching date not found"))
1602
1601
1603 def increasingwindows(windowsize=8, sizelimit=512):
1602 def increasingwindows(windowsize=8, sizelimit=512):
1604 while True:
1603 while True:
1605 yield windowsize
1604 yield windowsize
1606 if windowsize < sizelimit:
1605 if windowsize < sizelimit:
1607 windowsize *= 2
1606 windowsize *= 2
1608
1607
1609 def _walkrevs(repo, opts):
1608 def _walkrevs(repo, opts):
1610 # Default --rev value depends on --follow but --follow behavior
1609 # Default --rev value depends on --follow but --follow behavior
1611 # depends on revisions resolved from --rev...
1610 # depends on revisions resolved from --rev...
1612 follow = opts.get('follow') or opts.get('follow_first')
1611 follow = opts.get('follow') or opts.get('follow_first')
1613 if opts.get('rev'):
1612 if opts.get('rev'):
1614 revs = scmutil.revrange(repo, opts['rev'])
1613 revs = scmutil.revrange(repo, opts['rev'])
1615 elif follow and repo.dirstate.p1() == nullid:
1614 elif follow and repo.dirstate.p1() == nullid:
1616 revs = smartset.baseset()
1615 revs = smartset.baseset()
1617 elif follow:
1616 elif follow:
1618 revs = repo.revs('reverse(:.)')
1617 revs = repo.revs('reverse(:.)')
1619 else:
1618 else:
1620 revs = smartset.spanset(repo)
1619 revs = smartset.spanset(repo)
1621 revs.reverse()
1620 revs.reverse()
1622 return revs
1621 return revs
1623
1622
1624 class FileWalkError(Exception):
1623 class FileWalkError(Exception):
1625 pass
1624 pass
1626
1625
1627 def walkfilerevs(repo, match, follow, revs, fncache):
1626 def walkfilerevs(repo, match, follow, revs, fncache):
1628 '''Walks the file history for the matched files.
1627 '''Walks the file history for the matched files.
1629
1628
1630 Returns the changeset revs that are involved in the file history.
1629 Returns the changeset revs that are involved in the file history.
1631
1630
1632 Throws FileWalkError if the file history can't be walked using
1631 Throws FileWalkError if the file history can't be walked using
1633 filelogs alone.
1632 filelogs alone.
1634 '''
1633 '''
1635 wanted = set()
1634 wanted = set()
1636 copies = []
1635 copies = []
1637 minrev, maxrev = min(revs), max(revs)
1636 minrev, maxrev = min(revs), max(revs)
1638 def filerevgen(filelog, last):
1637 def filerevgen(filelog, last):
1639 """
1638 """
1640 Only files, no patterns. Check the history of each file.
1639 Only files, no patterns. Check the history of each file.
1641
1640
1642 Examines filelog entries within minrev, maxrev linkrev range
1641 Examines filelog entries within minrev, maxrev linkrev range
1643 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1642 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1644 tuples in backwards order
1643 tuples in backwards order
1645 """
1644 """
1646 cl_count = len(repo)
1645 cl_count = len(repo)
1647 revs = []
1646 revs = []
1648 for j in xrange(0, last + 1):
1647 for j in xrange(0, last + 1):
1649 linkrev = filelog.linkrev(j)
1648 linkrev = filelog.linkrev(j)
1650 if linkrev < minrev:
1649 if linkrev < minrev:
1651 continue
1650 continue
1652 # only yield rev for which we have the changelog, it can
1651 # only yield rev for which we have the changelog, it can
1653 # happen while doing "hg log" during a pull or commit
1652 # happen while doing "hg log" during a pull or commit
1654 if linkrev >= cl_count:
1653 if linkrev >= cl_count:
1655 break
1654 break
1656
1655
1657 parentlinkrevs = []
1656 parentlinkrevs = []
1658 for p in filelog.parentrevs(j):
1657 for p in filelog.parentrevs(j):
1659 if p != nullrev:
1658 if p != nullrev:
1660 parentlinkrevs.append(filelog.linkrev(p))
1659 parentlinkrevs.append(filelog.linkrev(p))
1661 n = filelog.node(j)
1660 n = filelog.node(j)
1662 revs.append((linkrev, parentlinkrevs,
1661 revs.append((linkrev, parentlinkrevs,
1663 follow and filelog.renamed(n)))
1662 follow and filelog.renamed(n)))
1664
1663
1665 return reversed(revs)
1664 return reversed(revs)
1666 def iterfiles():
1665 def iterfiles():
1667 pctx = repo['.']
1666 pctx = repo['.']
1668 for filename in match.files():
1667 for filename in match.files():
1669 if follow:
1668 if follow:
1670 if filename not in pctx:
1669 if filename not in pctx:
1671 raise error.Abort(_('cannot follow file not in parent '
1670 raise error.Abort(_('cannot follow file not in parent '
1672 'revision: "%s"') % filename)
1671 'revision: "%s"') % filename)
1673 yield filename, pctx[filename].filenode()
1672 yield filename, pctx[filename].filenode()
1674 else:
1673 else:
1675 yield filename, None
1674 yield filename, None
1676 for filename_node in copies:
1675 for filename_node in copies:
1677 yield filename_node
1676 yield filename_node
1678
1677
1679 for file_, node in iterfiles():
1678 for file_, node in iterfiles():
1680 filelog = repo.file(file_)
1679 filelog = repo.file(file_)
1681 if not len(filelog):
1680 if not len(filelog):
1682 if node is None:
1681 if node is None:
1683 # A zero count may be a directory or deleted file, so
1682 # A zero count may be a directory or deleted file, so
1684 # try to find matching entries on the slow path.
1683 # try to find matching entries on the slow path.
1685 if follow:
1684 if follow:
1686 raise error.Abort(
1685 raise error.Abort(
1687 _('cannot follow nonexistent file: "%s"') % file_)
1686 _('cannot follow nonexistent file: "%s"') % file_)
1688 raise FileWalkError("Cannot walk via filelog")
1687 raise FileWalkError("Cannot walk via filelog")
1689 else:
1688 else:
1690 continue
1689 continue
1691
1690
1692 if node is None:
1691 if node is None:
1693 last = len(filelog) - 1
1692 last = len(filelog) - 1
1694 else:
1693 else:
1695 last = filelog.rev(node)
1694 last = filelog.rev(node)
1696
1695
1697 # keep track of all ancestors of the file
1696 # keep track of all ancestors of the file
1698 ancestors = {filelog.linkrev(last)}
1697 ancestors = {filelog.linkrev(last)}
1699
1698
1700 # iterate from latest to oldest revision
1699 # iterate from latest to oldest revision
1701 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1700 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1702 if not follow:
1701 if not follow:
1703 if rev > maxrev:
1702 if rev > maxrev:
1704 continue
1703 continue
1705 else:
1704 else:
1706 # Note that last might not be the first interesting
1705 # Note that last might not be the first interesting
1707 # rev to us:
1706 # rev to us:
1708 # if the file has been changed after maxrev, we'll
1707 # if the file has been changed after maxrev, we'll
1709 # have linkrev(last) > maxrev, and we still need
1708 # have linkrev(last) > maxrev, and we still need
1710 # to explore the file graph
1709 # to explore the file graph
1711 if rev not in ancestors:
1710 if rev not in ancestors:
1712 continue
1711 continue
1713 # XXX insert 1327 fix here
1712 # XXX insert 1327 fix here
1714 if flparentlinkrevs:
1713 if flparentlinkrevs:
1715 ancestors.update(flparentlinkrevs)
1714 ancestors.update(flparentlinkrevs)
1716
1715
1717 fncache.setdefault(rev, []).append(file_)
1716 fncache.setdefault(rev, []).append(file_)
1718 wanted.add(rev)
1717 wanted.add(rev)
1719 if copied:
1718 if copied:
1720 copies.append(copied)
1719 copies.append(copied)
1721
1720
1722 return wanted
1721 return wanted
1723
1722
1724 class _followfilter(object):
1723 class _followfilter(object):
1725 def __init__(self, repo, onlyfirst=False):
1724 def __init__(self, repo, onlyfirst=False):
1726 self.repo = repo
1725 self.repo = repo
1727 self.startrev = nullrev
1726 self.startrev = nullrev
1728 self.roots = set()
1727 self.roots = set()
1729 self.onlyfirst = onlyfirst
1728 self.onlyfirst = onlyfirst
1730
1729
1731 def match(self, rev):
1730 def match(self, rev):
1732 def realparents(rev):
1731 def realparents(rev):
1733 if self.onlyfirst:
1732 if self.onlyfirst:
1734 return self.repo.changelog.parentrevs(rev)[0:1]
1733 return self.repo.changelog.parentrevs(rev)[0:1]
1735 else:
1734 else:
1736 return filter(lambda x: x != nullrev,
1735 return filter(lambda x: x != nullrev,
1737 self.repo.changelog.parentrevs(rev))
1736 self.repo.changelog.parentrevs(rev))
1738
1737
1739 if self.startrev == nullrev:
1738 if self.startrev == nullrev:
1740 self.startrev = rev
1739 self.startrev = rev
1741 return True
1740 return True
1742
1741
1743 if rev > self.startrev:
1742 if rev > self.startrev:
1744 # forward: all descendants
1743 # forward: all descendants
1745 if not self.roots:
1744 if not self.roots:
1746 self.roots.add(self.startrev)
1745 self.roots.add(self.startrev)
1747 for parent in realparents(rev):
1746 for parent in realparents(rev):
1748 if parent in self.roots:
1747 if parent in self.roots:
1749 self.roots.add(rev)
1748 self.roots.add(rev)
1750 return True
1749 return True
1751 else:
1750 else:
1752 # backwards: all parents
1751 # backwards: all parents
1753 if not self.roots:
1752 if not self.roots:
1754 self.roots.update(realparents(self.startrev))
1753 self.roots.update(realparents(self.startrev))
1755 if rev in self.roots:
1754 if rev in self.roots:
1756 self.roots.remove(rev)
1755 self.roots.remove(rev)
1757 self.roots.update(realparents(rev))
1756 self.roots.update(realparents(rev))
1758 return True
1757 return True
1759
1758
1760 return False
1759 return False
1761
1760
1762 def walkchangerevs(repo, match, opts, prepare):
1761 def walkchangerevs(repo, match, opts, prepare):
1763 '''Iterate over files and the revs in which they changed.
1762 '''Iterate over files and the revs in which they changed.
1764
1763
1765 Callers most commonly need to iterate backwards over the history
1764 Callers most commonly need to iterate backwards over the history
1766 in which they are interested. Doing so has awful (quadratic-looking)
1765 in which they are interested. Doing so has awful (quadratic-looking)
1767 performance, so we use iterators in a "windowed" way.
1766 performance, so we use iterators in a "windowed" way.
1768
1767
1769 We walk a window of revisions in the desired order. Within the
1768 We walk a window of revisions in the desired order. Within the
1770 window, we first walk forwards to gather data, then in the desired
1769 window, we first walk forwards to gather data, then in the desired
1771 order (usually backwards) to display it.
1770 order (usually backwards) to display it.
1772
1771
1773 This function returns an iterator yielding contexts. Before
1772 This function returns an iterator yielding contexts. Before
1774 yielding each context, the iterator will first call the prepare
1773 yielding each context, the iterator will first call the prepare
1775 function on each context in the window in forward order.'''
1774 function on each context in the window in forward order.'''
1776
1775
1777 follow = opts.get('follow') or opts.get('follow_first')
1776 follow = opts.get('follow') or opts.get('follow_first')
1778 revs = _walkrevs(repo, opts)
1777 revs = _walkrevs(repo, opts)
1779 if not revs:
1778 if not revs:
1780 return []
1779 return []
1781 wanted = set()
1780 wanted = set()
1782 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1781 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1783 fncache = {}
1782 fncache = {}
1784 change = repo.changectx
1783 change = repo.changectx
1785
1784
1786 # First step is to fill wanted, the set of revisions that we want to yield.
1785 # First step is to fill wanted, the set of revisions that we want to yield.
1787 # When it does not induce extra cost, we also fill fncache for revisions in
1786 # When it does not induce extra cost, we also fill fncache for revisions in
1788 # wanted: a cache of filenames that were changed (ctx.files()) and that
1787 # wanted: a cache of filenames that were changed (ctx.files()) and that
1789 # match the file filtering conditions.
1788 # match the file filtering conditions.
1790
1789
1791 if match.always():
1790 if match.always():
1792 # No files, no patterns. Display all revs.
1791 # No files, no patterns. Display all revs.
1793 wanted = revs
1792 wanted = revs
1794 elif not slowpath:
1793 elif not slowpath:
1795 # We only have to read through the filelog to find wanted revisions
1794 # We only have to read through the filelog to find wanted revisions
1796
1795
1797 try:
1796 try:
1798 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1797 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1799 except FileWalkError:
1798 except FileWalkError:
1800 slowpath = True
1799 slowpath = True
1801
1800
1802 # We decided to fall back to the slowpath because at least one
1801 # We decided to fall back to the slowpath because at least one
1803 # of the paths was not a file. Check to see if at least one of them
1802 # of the paths was not a file. Check to see if at least one of them
1804 # existed in history, otherwise simply return
1803 # existed in history, otherwise simply return
1805 for path in match.files():
1804 for path in match.files():
1806 if path == '.' or path in repo.store:
1805 if path == '.' or path in repo.store:
1807 break
1806 break
1808 else:
1807 else:
1809 return []
1808 return []
1810
1809
1811 if slowpath:
1810 if slowpath:
1812 # We have to read the changelog to match filenames against
1811 # We have to read the changelog to match filenames against
1813 # changed files
1812 # changed files
1814
1813
1815 if follow:
1814 if follow:
1816 raise error.Abort(_('can only follow copies/renames for explicit '
1815 raise error.Abort(_('can only follow copies/renames for explicit '
1817 'filenames'))
1816 'filenames'))
1818
1817
1819 # The slow path checks files modified in every changeset.
1818 # The slow path checks files modified in every changeset.
1820 # This is really slow on large repos, so compute the set lazily.
1819 # This is really slow on large repos, so compute the set lazily.
1821 class lazywantedset(object):
1820 class lazywantedset(object):
1822 def __init__(self):
1821 def __init__(self):
1823 self.set = set()
1822 self.set = set()
1824 self.revs = set(revs)
1823 self.revs = set(revs)
1825
1824
1826 # No need to worry about locality here because it will be accessed
1825 # No need to worry about locality here because it will be accessed
1827 # in the same order as the increasing window below.
1826 # in the same order as the increasing window below.
1828 def __contains__(self, value):
1827 def __contains__(self, value):
1829 if value in self.set:
1828 if value in self.set:
1830 return True
1829 return True
1831 elif not value in self.revs:
1830 elif not value in self.revs:
1832 return False
1831 return False
1833 else:
1832 else:
1834 self.revs.discard(value)
1833 self.revs.discard(value)
1835 ctx = change(value)
1834 ctx = change(value)
1836 matches = filter(match, ctx.files())
1835 matches = filter(match, ctx.files())
1837 if matches:
1836 if matches:
1838 fncache[value] = matches
1837 fncache[value] = matches
1839 self.set.add(value)
1838 self.set.add(value)
1840 return True
1839 return True
1841 return False
1840 return False
1842
1841
1843 def discard(self, value):
1842 def discard(self, value):
1844 self.revs.discard(value)
1843 self.revs.discard(value)
1845 self.set.discard(value)
1844 self.set.discard(value)
1846
1845
1847 wanted = lazywantedset()
1846 wanted = lazywantedset()
1848
1847
1849 # it might be worthwhile to do this in the iterator if the rev range
1848 # it might be worthwhile to do this in the iterator if the rev range
1850 # is descending and the prune args are all within that range
1849 # is descending and the prune args are all within that range
1851 for rev in opts.get('prune', ()):
1850 for rev in opts.get('prune', ()):
1852 rev = repo[rev].rev()
1851 rev = repo[rev].rev()
1853 ff = _followfilter(repo)
1852 ff = _followfilter(repo)
1854 stop = min(revs[0], revs[-1])
1853 stop = min(revs[0], revs[-1])
1855 for x in xrange(rev, stop - 1, -1):
1854 for x in xrange(rev, stop - 1, -1):
1856 if ff.match(x):
1855 if ff.match(x):
1857 wanted = wanted - [x]
1856 wanted = wanted - [x]
1858
1857
1859 # Now that wanted is correctly initialized, we can iterate over the
1858 # Now that wanted is correctly initialized, we can iterate over the
1860 # revision range, yielding only revisions in wanted.
1859 # revision range, yielding only revisions in wanted.
1861 def iterate():
1860 def iterate():
1862 if follow and match.always():
1861 if follow and match.always():
1863 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1862 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1864 def want(rev):
1863 def want(rev):
1865 return ff.match(rev) and rev in wanted
1864 return ff.match(rev) and rev in wanted
1866 else:
1865 else:
1867 def want(rev):
1866 def want(rev):
1868 return rev in wanted
1867 return rev in wanted
1869
1868
1870 it = iter(revs)
1869 it = iter(revs)
1871 stopiteration = False
1870 stopiteration = False
1872 for windowsize in increasingwindows():
1871 for windowsize in increasingwindows():
1873 nrevs = []
1872 nrevs = []
1874 for i in xrange(windowsize):
1873 for i in xrange(windowsize):
1875 rev = next(it, None)
1874 rev = next(it, None)
1876 if rev is None:
1875 if rev is None:
1877 stopiteration = True
1876 stopiteration = True
1878 break
1877 break
1879 elif want(rev):
1878 elif want(rev):
1880 nrevs.append(rev)
1879 nrevs.append(rev)
1881 for rev in sorted(nrevs):
1880 for rev in sorted(nrevs):
1882 fns = fncache.get(rev)
1881 fns = fncache.get(rev)
1883 ctx = change(rev)
1882 ctx = change(rev)
1884 if not fns:
1883 if not fns:
1885 def fns_generator():
1884 def fns_generator():
1886 for f in ctx.files():
1885 for f in ctx.files():
1887 if match(f):
1886 if match(f):
1888 yield f
1887 yield f
1889 fns = fns_generator()
1888 fns = fns_generator()
1890 prepare(ctx, fns)
1889 prepare(ctx, fns)
1891 for rev in nrevs:
1890 for rev in nrevs:
1892 yield change(rev)
1891 yield change(rev)
1893
1892
1894 if stopiteration:
1893 if stopiteration:
1895 break
1894 break
1896
1895
1897 return iterate()
1896 return iterate()
1898
1897
1899 def add(ui, repo, match, prefix, explicitonly, **opts):
1898 def add(ui, repo, match, prefix, explicitonly, **opts):
1900 join = lambda f: os.path.join(prefix, f)
1899 join = lambda f: os.path.join(prefix, f)
1901 bad = []
1900 bad = []
1902
1901
1903 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1902 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1904 names = []
1903 names = []
1905 wctx = repo[None]
1904 wctx = repo[None]
1906 cca = None
1905 cca = None
1907 abort, warn = scmutil.checkportabilityalert(ui)
1906 abort, warn = scmutil.checkportabilityalert(ui)
1908 if abort or warn:
1907 if abort or warn:
1909 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1908 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1910
1909
1911 badmatch = matchmod.badmatch(match, badfn)
1910 badmatch = matchmod.badmatch(match, badfn)
1912 dirstate = repo.dirstate
1911 dirstate = repo.dirstate
1913 # We don't want to just call wctx.walk here, since it would return a lot of
1912 # We don't want to just call wctx.walk here, since it would return a lot of
1914 # clean files, which we aren't interested in and takes time.
1913 # clean files, which we aren't interested in and takes time.
1915 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1914 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1916 unknown=True, ignored=False, full=False)):
1915 unknown=True, ignored=False, full=False)):
1917 exact = match.exact(f)
1916 exact = match.exact(f)
1918 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1917 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1919 if cca:
1918 if cca:
1920 cca(f)
1919 cca(f)
1921 names.append(f)
1920 names.append(f)
1922 if ui.verbose or not exact:
1921 if ui.verbose or not exact:
1923 ui.status(_('adding %s\n') % match.rel(f))
1922 ui.status(_('adding %s\n') % match.rel(f))
1924
1923
1925 for subpath in sorted(wctx.substate):
1924 for subpath in sorted(wctx.substate):
1926 sub = wctx.sub(subpath)
1925 sub = wctx.sub(subpath)
1927 try:
1926 try:
1928 submatch = matchmod.subdirmatcher(subpath, match)
1927 submatch = matchmod.subdirmatcher(subpath, match)
1929 if opts.get(r'subrepos'):
1928 if opts.get(r'subrepos'):
1930 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1929 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1931 else:
1930 else:
1932 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1931 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1933 except error.LookupError:
1932 except error.LookupError:
1934 ui.status(_("skipping missing subrepository: %s\n")
1933 ui.status(_("skipping missing subrepository: %s\n")
1935 % join(subpath))
1934 % join(subpath))
1936
1935
1937 if not opts.get(r'dry_run'):
1936 if not opts.get(r'dry_run'):
1938 rejected = wctx.add(names, prefix)
1937 rejected = wctx.add(names, prefix)
1939 bad.extend(f for f in rejected if f in match.files())
1938 bad.extend(f for f in rejected if f in match.files())
1940 return bad
1939 return bad
1941
1940
1942 def addwebdirpath(repo, serverpath, webconf):
1941 def addwebdirpath(repo, serverpath, webconf):
1943 webconf[serverpath] = repo.root
1942 webconf[serverpath] = repo.root
1944 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
1943 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
1945
1944
1946 for r in repo.revs('filelog("path:.hgsub")'):
1945 for r in repo.revs('filelog("path:.hgsub")'):
1947 ctx = repo[r]
1946 ctx = repo[r]
1948 for subpath in ctx.substate:
1947 for subpath in ctx.substate:
1949 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
1948 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
1950
1949
1951 def forget(ui, repo, match, prefix, explicitonly):
1950 def forget(ui, repo, match, prefix, explicitonly):
1952 join = lambda f: os.path.join(prefix, f)
1951 join = lambda f: os.path.join(prefix, f)
1953 bad = []
1952 bad = []
1954 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1953 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1955 wctx = repo[None]
1954 wctx = repo[None]
1956 forgot = []
1955 forgot = []
1957
1956
1958 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
1957 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
1959 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1958 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1960 if explicitonly:
1959 if explicitonly:
1961 forget = [f for f in forget if match.exact(f)]
1960 forget = [f for f in forget if match.exact(f)]
1962
1961
1963 for subpath in sorted(wctx.substate):
1962 for subpath in sorted(wctx.substate):
1964 sub = wctx.sub(subpath)
1963 sub = wctx.sub(subpath)
1965 try:
1964 try:
1966 submatch = matchmod.subdirmatcher(subpath, match)
1965 submatch = matchmod.subdirmatcher(subpath, match)
1967 subbad, subforgot = sub.forget(submatch, prefix)
1966 subbad, subforgot = sub.forget(submatch, prefix)
1968 bad.extend([subpath + '/' + f for f in subbad])
1967 bad.extend([subpath + '/' + f for f in subbad])
1969 forgot.extend([subpath + '/' + f for f in subforgot])
1968 forgot.extend([subpath + '/' + f for f in subforgot])
1970 except error.LookupError:
1969 except error.LookupError:
1971 ui.status(_("skipping missing subrepository: %s\n")
1970 ui.status(_("skipping missing subrepository: %s\n")
1972 % join(subpath))
1971 % join(subpath))
1973
1972
1974 if not explicitonly:
1973 if not explicitonly:
1975 for f in match.files():
1974 for f in match.files():
1976 if f not in repo.dirstate and not repo.wvfs.isdir(f):
1975 if f not in repo.dirstate and not repo.wvfs.isdir(f):
1977 if f not in forgot:
1976 if f not in forgot:
1978 if repo.wvfs.exists(f):
1977 if repo.wvfs.exists(f):
1979 # Don't complain if the exact case match wasn't given.
1978 # Don't complain if the exact case match wasn't given.
1980 # But don't do this until after checking 'forgot', so
1979 # But don't do this until after checking 'forgot', so
1981 # that subrepo files aren't normalized, and this op is
1980 # that subrepo files aren't normalized, and this op is
1982 # purely from data cached by the status walk above.
1981 # purely from data cached by the status walk above.
1983 if repo.dirstate.normalize(f) in repo.dirstate:
1982 if repo.dirstate.normalize(f) in repo.dirstate:
1984 continue
1983 continue
1985 ui.warn(_('not removing %s: '
1984 ui.warn(_('not removing %s: '
1986 'file is already untracked\n')
1985 'file is already untracked\n')
1987 % match.rel(f))
1986 % match.rel(f))
1988 bad.append(f)
1987 bad.append(f)
1989
1988
1990 for f in forget:
1989 for f in forget:
1991 if ui.verbose or not match.exact(f):
1990 if ui.verbose or not match.exact(f):
1992 ui.status(_('removing %s\n') % match.rel(f))
1991 ui.status(_('removing %s\n') % match.rel(f))
1993
1992
1994 rejected = wctx.forget(forget, prefix)
1993 rejected = wctx.forget(forget, prefix)
1995 bad.extend(f for f in rejected if f in match.files())
1994 bad.extend(f for f in rejected if f in match.files())
1996 forgot.extend(f for f in forget if f not in rejected)
1995 forgot.extend(f for f in forget if f not in rejected)
1997 return bad, forgot
1996 return bad, forgot
1998
1997
1999 def files(ui, ctx, m, fm, fmt, subrepos):
1998 def files(ui, ctx, m, fm, fmt, subrepos):
2000 rev = ctx.rev()
1999 rev = ctx.rev()
2001 ret = 1
2000 ret = 1
2002 ds = ctx.repo().dirstate
2001 ds = ctx.repo().dirstate
2003
2002
2004 for f in ctx.matches(m):
2003 for f in ctx.matches(m):
2005 if rev is None and ds[f] == 'r':
2004 if rev is None and ds[f] == 'r':
2006 continue
2005 continue
2007 fm.startitem()
2006 fm.startitem()
2008 if ui.verbose:
2007 if ui.verbose:
2009 fc = ctx[f]
2008 fc = ctx[f]
2010 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2009 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2011 fm.data(abspath=f)
2010 fm.data(abspath=f)
2012 fm.write('path', fmt, m.rel(f))
2011 fm.write('path', fmt, m.rel(f))
2013 ret = 0
2012 ret = 0
2014
2013
2015 for subpath in sorted(ctx.substate):
2014 for subpath in sorted(ctx.substate):
2016 submatch = matchmod.subdirmatcher(subpath, m)
2015 submatch = matchmod.subdirmatcher(subpath, m)
2017 if (subrepos or m.exact(subpath) or any(submatch.files())):
2016 if (subrepos or m.exact(subpath) or any(submatch.files())):
2018 sub = ctx.sub(subpath)
2017 sub = ctx.sub(subpath)
2019 try:
2018 try:
2020 recurse = m.exact(subpath) or subrepos
2019 recurse = m.exact(subpath) or subrepos
2021 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2020 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2022 ret = 0
2021 ret = 0
2023 except error.LookupError:
2022 except error.LookupError:
2024 ui.status(_("skipping missing subrepository: %s\n")
2023 ui.status(_("skipping missing subrepository: %s\n")
2025 % m.abs(subpath))
2024 % m.abs(subpath))
2026
2025
2027 return ret
2026 return ret
2028
2027
2029 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2028 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2030 join = lambda f: os.path.join(prefix, f)
2029 join = lambda f: os.path.join(prefix, f)
2031 ret = 0
2030 ret = 0
2032 s = repo.status(match=m, clean=True)
2031 s = repo.status(match=m, clean=True)
2033 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2032 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2034
2033
2035 wctx = repo[None]
2034 wctx = repo[None]
2036
2035
2037 if warnings is None:
2036 if warnings is None:
2038 warnings = []
2037 warnings = []
2039 warn = True
2038 warn = True
2040 else:
2039 else:
2041 warn = False
2040 warn = False
2042
2041
2043 subs = sorted(wctx.substate)
2042 subs = sorted(wctx.substate)
2044 total = len(subs)
2043 total = len(subs)
2045 count = 0
2044 count = 0
2046 for subpath in subs:
2045 for subpath in subs:
2047 count += 1
2046 count += 1
2048 submatch = matchmod.subdirmatcher(subpath, m)
2047 submatch = matchmod.subdirmatcher(subpath, m)
2049 if subrepos or m.exact(subpath) or any(submatch.files()):
2048 if subrepos or m.exact(subpath) or any(submatch.files()):
2050 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2049 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2051 sub = wctx.sub(subpath)
2050 sub = wctx.sub(subpath)
2052 try:
2051 try:
2053 if sub.removefiles(submatch, prefix, after, force, subrepos,
2052 if sub.removefiles(submatch, prefix, after, force, subrepos,
2054 warnings):
2053 warnings):
2055 ret = 1
2054 ret = 1
2056 except error.LookupError:
2055 except error.LookupError:
2057 warnings.append(_("skipping missing subrepository: %s\n")
2056 warnings.append(_("skipping missing subrepository: %s\n")
2058 % join(subpath))
2057 % join(subpath))
2059 ui.progress(_('searching'), None)
2058 ui.progress(_('searching'), None)
2060
2059
2061 # warn about failure to delete explicit files/dirs
2060 # warn about failure to delete explicit files/dirs
2062 deleteddirs = util.dirs(deleted)
2061 deleteddirs = util.dirs(deleted)
2063 files = m.files()
2062 files = m.files()
2064 total = len(files)
2063 total = len(files)
2065 count = 0
2064 count = 0
2066 for f in files:
2065 for f in files:
2067 def insubrepo():
2066 def insubrepo():
2068 for subpath in wctx.substate:
2067 for subpath in wctx.substate:
2069 if f.startswith(subpath + '/'):
2068 if f.startswith(subpath + '/'):
2070 return True
2069 return True
2071 return False
2070 return False
2072
2071
2073 count += 1
2072 count += 1
2074 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2073 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2075 isdir = f in deleteddirs or wctx.hasdir(f)
2074 isdir = f in deleteddirs or wctx.hasdir(f)
2076 if (f in repo.dirstate or isdir or f == '.'
2075 if (f in repo.dirstate or isdir or f == '.'
2077 or insubrepo() or f in subs):
2076 or insubrepo() or f in subs):
2078 continue
2077 continue
2079
2078
2080 if repo.wvfs.exists(f):
2079 if repo.wvfs.exists(f):
2081 if repo.wvfs.isdir(f):
2080 if repo.wvfs.isdir(f):
2082 warnings.append(_('not removing %s: no tracked files\n')
2081 warnings.append(_('not removing %s: no tracked files\n')
2083 % m.rel(f))
2082 % m.rel(f))
2084 else:
2083 else:
2085 warnings.append(_('not removing %s: file is untracked\n')
2084 warnings.append(_('not removing %s: file is untracked\n')
2086 % m.rel(f))
2085 % m.rel(f))
2087 # missing files will generate a warning elsewhere
2086 # missing files will generate a warning elsewhere
2088 ret = 1
2087 ret = 1
2089 ui.progress(_('deleting'), None)
2088 ui.progress(_('deleting'), None)
2090
2089
2091 if force:
2090 if force:
2092 list = modified + deleted + clean + added
2091 list = modified + deleted + clean + added
2093 elif after:
2092 elif after:
2094 list = deleted
2093 list = deleted
2095 remaining = modified + added + clean
2094 remaining = modified + added + clean
2096 total = len(remaining)
2095 total = len(remaining)
2097 count = 0
2096 count = 0
2098 for f in remaining:
2097 for f in remaining:
2099 count += 1
2098 count += 1
2100 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2099 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2101 if ui.verbose or (f in files):
2100 if ui.verbose or (f in files):
2102 warnings.append(_('not removing %s: file still exists\n')
2101 warnings.append(_('not removing %s: file still exists\n')
2103 % m.rel(f))
2102 % m.rel(f))
2104 ret = 1
2103 ret = 1
2105 ui.progress(_('skipping'), None)
2104 ui.progress(_('skipping'), None)
2106 else:
2105 else:
2107 list = deleted + clean
2106 list = deleted + clean
2108 total = len(modified) + len(added)
2107 total = len(modified) + len(added)
2109 count = 0
2108 count = 0
2110 for f in modified:
2109 for f in modified:
2111 count += 1
2110 count += 1
2112 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2111 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2113 warnings.append(_('not removing %s: file is modified (use -f'
2112 warnings.append(_('not removing %s: file is modified (use -f'
2114 ' to force removal)\n') % m.rel(f))
2113 ' to force removal)\n') % m.rel(f))
2115 ret = 1
2114 ret = 1
2116 for f in added:
2115 for f in added:
2117 count += 1
2116 count += 1
2118 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2117 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2119 warnings.append(_("not removing %s: file has been marked for add"
2118 warnings.append(_("not removing %s: file has been marked for add"
2120 " (use 'hg forget' to undo add)\n") % m.rel(f))
2119 " (use 'hg forget' to undo add)\n") % m.rel(f))
2121 ret = 1
2120 ret = 1
2122 ui.progress(_('skipping'), None)
2121 ui.progress(_('skipping'), None)
2123
2122
2124 list = sorted(list)
2123 list = sorted(list)
2125 total = len(list)
2124 total = len(list)
2126 count = 0
2125 count = 0
2127 for f in list:
2126 for f in list:
2128 count += 1
2127 count += 1
2129 if ui.verbose or not m.exact(f):
2128 if ui.verbose or not m.exact(f):
2130 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2129 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2131 ui.status(_('removing %s\n') % m.rel(f))
2130 ui.status(_('removing %s\n') % m.rel(f))
2132 ui.progress(_('deleting'), None)
2131 ui.progress(_('deleting'), None)
2133
2132
2134 with repo.wlock():
2133 with repo.wlock():
2135 if not after:
2134 if not after:
2136 for f in list:
2135 for f in list:
2137 if f in added:
2136 if f in added:
2138 continue # we never unlink added files on remove
2137 continue # we never unlink added files on remove
2139 repo.wvfs.unlinkpath(f, ignoremissing=True)
2138 repo.wvfs.unlinkpath(f, ignoremissing=True)
2140 repo[None].forget(list)
2139 repo[None].forget(list)
2141
2140
2142 if warn:
2141 if warn:
2143 for warning in warnings:
2142 for warning in warnings:
2144 ui.warn(warning)
2143 ui.warn(warning)
2145
2144
2146 return ret
2145 return ret
2147
2146
2148 def _updatecatformatter(fm, ctx, matcher, path, decode):
2147 def _updatecatformatter(fm, ctx, matcher, path, decode):
2149 """Hook for adding data to the formatter used by ``hg cat``.
2148 """Hook for adding data to the formatter used by ``hg cat``.
2150
2149
2151 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2150 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2152 this method first."""
2151 this method first."""
2153 data = ctx[path].data()
2152 data = ctx[path].data()
2154 if decode:
2153 if decode:
2155 data = ctx.repo().wwritedata(path, data)
2154 data = ctx.repo().wwritedata(path, data)
2156 fm.startitem()
2155 fm.startitem()
2157 fm.write('data', '%s', data)
2156 fm.write('data', '%s', data)
2158 fm.data(abspath=path, path=matcher.rel(path))
2157 fm.data(abspath=path, path=matcher.rel(path))
2159
2158
2160 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2159 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2161 err = 1
2160 err = 1
2162 opts = pycompat.byteskwargs(opts)
2161 opts = pycompat.byteskwargs(opts)
2163
2162
2164 def write(path):
2163 def write(path):
2165 filename = None
2164 filename = None
2166 if fntemplate:
2165 if fntemplate:
2167 filename = makefilename(ctx, fntemplate,
2166 filename = makefilename(ctx, fntemplate,
2168 pathname=os.path.join(prefix, path))
2167 pathname=os.path.join(prefix, path))
2169 # attempt to create the directory if it does not already exist
2168 # attempt to create the directory if it does not already exist
2170 try:
2169 try:
2171 os.makedirs(os.path.dirname(filename))
2170 os.makedirs(os.path.dirname(filename))
2172 except OSError:
2171 except OSError:
2173 pass
2172 pass
2174 with formatter.maybereopen(basefm, filename, opts) as fm:
2173 with formatter.maybereopen(basefm, filename, opts) as fm:
2175 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2174 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2176
2175
2177 # Automation often uses hg cat on single files, so special case it
2176 # Automation often uses hg cat on single files, so special case it
2178 # for performance to avoid the cost of parsing the manifest.
2177 # for performance to avoid the cost of parsing the manifest.
2179 if len(matcher.files()) == 1 and not matcher.anypats():
2178 if len(matcher.files()) == 1 and not matcher.anypats():
2180 file = matcher.files()[0]
2179 file = matcher.files()[0]
2181 mfl = repo.manifestlog
2180 mfl = repo.manifestlog
2182 mfnode = ctx.manifestnode()
2181 mfnode = ctx.manifestnode()
2183 try:
2182 try:
2184 if mfnode and mfl[mfnode].find(file)[0]:
2183 if mfnode and mfl[mfnode].find(file)[0]:
2185 scmutil.fileprefetchhooks(repo, ctx, [file])
2184 scmutil.fileprefetchhooks(repo, ctx, [file])
2186 write(file)
2185 write(file)
2187 return 0
2186 return 0
2188 except KeyError:
2187 except KeyError:
2189 pass
2188 pass
2190
2189
2191 files = [f for f in ctx.walk(matcher)]
2190 files = [f for f in ctx.walk(matcher)]
2192 scmutil.fileprefetchhooks(repo, ctx, files)
2191 scmutil.fileprefetchhooks(repo, ctx, files)
2193
2192
2194 for abs in files:
2193 for abs in files:
2195 write(abs)
2194 write(abs)
2196 err = 0
2195 err = 0
2197
2196
2198 for subpath in sorted(ctx.substate):
2197 for subpath in sorted(ctx.substate):
2199 sub = ctx.sub(subpath)
2198 sub = ctx.sub(subpath)
2200 try:
2199 try:
2201 submatch = matchmod.subdirmatcher(subpath, matcher)
2200 submatch = matchmod.subdirmatcher(subpath, matcher)
2202
2201
2203 if not sub.cat(submatch, basefm, fntemplate,
2202 if not sub.cat(submatch, basefm, fntemplate,
2204 os.path.join(prefix, sub._path),
2203 os.path.join(prefix, sub._path),
2205 **pycompat.strkwargs(opts)):
2204 **pycompat.strkwargs(opts)):
2206 err = 0
2205 err = 0
2207 except error.RepoLookupError:
2206 except error.RepoLookupError:
2208 ui.status(_("skipping missing subrepository: %s\n")
2207 ui.status(_("skipping missing subrepository: %s\n")
2209 % os.path.join(prefix, subpath))
2208 % os.path.join(prefix, subpath))
2210
2209
2211 return err
2210 return err
2212
2211
2213 def commit(ui, repo, commitfunc, pats, opts):
2212 def commit(ui, repo, commitfunc, pats, opts):
2214 '''commit the specified files or all outstanding changes'''
2213 '''commit the specified files or all outstanding changes'''
2215 date = opts.get('date')
2214 date = opts.get('date')
2216 if date:
2215 if date:
2217 opts['date'] = util.parsedate(date)
2216 opts['date'] = util.parsedate(date)
2218 message = logmessage(ui, opts)
2217 message = logmessage(ui, opts)
2219 matcher = scmutil.match(repo[None], pats, opts)
2218 matcher = scmutil.match(repo[None], pats, opts)
2220
2219
2221 dsguard = None
2220 dsguard = None
2222 # extract addremove carefully -- this function can be called from a command
2221 # extract addremove carefully -- this function can be called from a command
2223 # that doesn't support addremove
2222 # that doesn't support addremove
2224 if opts.get('addremove'):
2223 if opts.get('addremove'):
2225 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2224 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2226 with dsguard or util.nullcontextmanager():
2225 with dsguard or util.nullcontextmanager():
2227 if dsguard:
2226 if dsguard:
2228 if scmutil.addremove(repo, matcher, "", opts) != 0:
2227 if scmutil.addremove(repo, matcher, "", opts) != 0:
2229 raise error.Abort(
2228 raise error.Abort(
2230 _("failed to mark all new/missing files as added/removed"))
2229 _("failed to mark all new/missing files as added/removed"))
2231
2230
2232 return commitfunc(ui, repo, message, matcher, opts)
2231 return commitfunc(ui, repo, message, matcher, opts)
2233
2232
2234 def samefile(f, ctx1, ctx2):
2233 def samefile(f, ctx1, ctx2):
2235 if f in ctx1.manifest():
2234 if f in ctx1.manifest():
2236 a = ctx1.filectx(f)
2235 a = ctx1.filectx(f)
2237 if f in ctx2.manifest():
2236 if f in ctx2.manifest():
2238 b = ctx2.filectx(f)
2237 b = ctx2.filectx(f)
2239 return (not a.cmp(b)
2238 return (not a.cmp(b)
2240 and a.flags() == b.flags())
2239 and a.flags() == b.flags())
2241 else:
2240 else:
2242 return False
2241 return False
2243 else:
2242 else:
2244 return f not in ctx2.manifest()
2243 return f not in ctx2.manifest()
2245
2244
2246 def amend(ui, repo, old, extra, pats, opts):
2245 def amend(ui, repo, old, extra, pats, opts):
2247 # avoid cycle context -> subrepo -> cmdutil
2246 # avoid cycle context -> subrepo -> cmdutil
2248 from . import context
2247 from . import context
2249
2248
2250 # amend will reuse the existing user if not specified, but the obsolete
2249 # amend will reuse the existing user if not specified, but the obsolete
2251 # marker creation requires that the current user's name is specified.
2250 # marker creation requires that the current user's name is specified.
2252 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2251 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2253 ui.username() # raise exception if username not set
2252 ui.username() # raise exception if username not set
2254
2253
2255 ui.note(_('amending changeset %s\n') % old)
2254 ui.note(_('amending changeset %s\n') % old)
2256 base = old.p1()
2255 base = old.p1()
2257
2256
2258 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2257 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2259 # Participating changesets:
2258 # Participating changesets:
2260 #
2259 #
2261 # wctx o - workingctx that contains changes from working copy
2260 # wctx o - workingctx that contains changes from working copy
2262 # | to go into amending commit
2261 # | to go into amending commit
2263 # |
2262 # |
2264 # old o - changeset to amend
2263 # old o - changeset to amend
2265 # |
2264 # |
2266 # base o - first parent of the changeset to amend
2265 # base o - first parent of the changeset to amend
2267 wctx = repo[None]
2266 wctx = repo[None]
2268
2267
2269 # Copy to avoid mutating input
2268 # Copy to avoid mutating input
2270 extra = extra.copy()
2269 extra = extra.copy()
2271 # Update extra dict from amended commit (e.g. to preserve graft
2270 # Update extra dict from amended commit (e.g. to preserve graft
2272 # source)
2271 # source)
2273 extra.update(old.extra())
2272 extra.update(old.extra())
2274
2273
2275 # Also update it from the from the wctx
2274 # Also update it from the from the wctx
2276 extra.update(wctx.extra())
2275 extra.update(wctx.extra())
2277
2276
2278 user = opts.get('user') or old.user()
2277 user = opts.get('user') or old.user()
2279 date = opts.get('date') or old.date()
2278 date = opts.get('date') or old.date()
2280
2279
2281 # Parse the date to allow comparison between date and old.date()
2280 # Parse the date to allow comparison between date and old.date()
2282 date = util.parsedate(date)
2281 date = util.parsedate(date)
2283
2282
2284 if len(old.parents()) > 1:
2283 if len(old.parents()) > 1:
2285 # ctx.files() isn't reliable for merges, so fall back to the
2284 # ctx.files() isn't reliable for merges, so fall back to the
2286 # slower repo.status() method
2285 # slower repo.status() method
2287 files = set([fn for st in repo.status(base, old)[:3]
2286 files = set([fn for st in repo.status(base, old)[:3]
2288 for fn in st])
2287 for fn in st])
2289 else:
2288 else:
2290 files = set(old.files())
2289 files = set(old.files())
2291
2290
2292 # add/remove the files to the working copy if the "addremove" option
2291 # add/remove the files to the working copy if the "addremove" option
2293 # was specified.
2292 # was specified.
2294 matcher = scmutil.match(wctx, pats, opts)
2293 matcher = scmutil.match(wctx, pats, opts)
2295 if (opts.get('addremove')
2294 if (opts.get('addremove')
2296 and scmutil.addremove(repo, matcher, "", opts)):
2295 and scmutil.addremove(repo, matcher, "", opts)):
2297 raise error.Abort(
2296 raise error.Abort(
2298 _("failed to mark all new/missing files as added/removed"))
2297 _("failed to mark all new/missing files as added/removed"))
2299
2298
2300 # Check subrepos. This depends on in-place wctx._status update in
2299 # Check subrepos. This depends on in-place wctx._status update in
2301 # subrepo.precommit(). To minimize the risk of this hack, we do
2300 # subrepo.precommit(). To minimize the risk of this hack, we do
2302 # nothing if .hgsub does not exist.
2301 # nothing if .hgsub does not exist.
2303 if '.hgsub' in wctx or '.hgsub' in old:
2302 if '.hgsub' in wctx or '.hgsub' in old:
2304 subs, commitsubs, newsubstate = subrepoutil.precommit(
2303 subs, commitsubs, newsubstate = subrepoutil.precommit(
2305 ui, wctx, wctx._status, matcher)
2304 ui, wctx, wctx._status, matcher)
2306 # amend should abort if commitsubrepos is enabled
2305 # amend should abort if commitsubrepos is enabled
2307 assert not commitsubs
2306 assert not commitsubs
2308 if subs:
2307 if subs:
2309 subrepoutil.writestate(repo, newsubstate)
2308 subrepoutil.writestate(repo, newsubstate)
2310
2309
2311 filestoamend = set(f for f in wctx.files() if matcher(f))
2310 filestoamend = set(f for f in wctx.files() if matcher(f))
2312
2311
2313 changes = (len(filestoamend) > 0)
2312 changes = (len(filestoamend) > 0)
2314 if changes:
2313 if changes:
2315 # Recompute copies (avoid recording a -> b -> a)
2314 # Recompute copies (avoid recording a -> b -> a)
2316 copied = copies.pathcopies(base, wctx, matcher)
2315 copied = copies.pathcopies(base, wctx, matcher)
2317 if old.p2:
2316 if old.p2:
2318 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2317 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2319
2318
2320 # Prune files which were reverted by the updates: if old
2319 # Prune files which were reverted by the updates: if old
2321 # introduced file X and the file was renamed in the working
2320 # introduced file X and the file was renamed in the working
2322 # copy, then those two files are the same and
2321 # copy, then those two files are the same and
2323 # we can discard X from our list of files. Likewise if X
2322 # we can discard X from our list of files. Likewise if X
2324 # was removed, it's no longer relevant. If X is missing (aka
2323 # was removed, it's no longer relevant. If X is missing (aka
2325 # deleted), old X must be preserved.
2324 # deleted), old X must be preserved.
2326 files.update(filestoamend)
2325 files.update(filestoamend)
2327 files = [f for f in files if (not samefile(f, wctx, base)
2326 files = [f for f in files if (not samefile(f, wctx, base)
2328 or f in wctx.deleted())]
2327 or f in wctx.deleted())]
2329
2328
2330 def filectxfn(repo, ctx_, path):
2329 def filectxfn(repo, ctx_, path):
2331 try:
2330 try:
2332 # If the file being considered is not amongst the files
2331 # If the file being considered is not amongst the files
2333 # to be amended, we should return the file context from the
2332 # to be amended, we should return the file context from the
2334 # old changeset. This avoids issues when only some files in
2333 # old changeset. This avoids issues when only some files in
2335 # the working copy are being amended but there are also
2334 # the working copy are being amended but there are also
2336 # changes to other files from the old changeset.
2335 # changes to other files from the old changeset.
2337 if path not in filestoamend:
2336 if path not in filestoamend:
2338 return old.filectx(path)
2337 return old.filectx(path)
2339
2338
2340 # Return None for removed files.
2339 # Return None for removed files.
2341 if path in wctx.removed():
2340 if path in wctx.removed():
2342 return None
2341 return None
2343
2342
2344 fctx = wctx[path]
2343 fctx = wctx[path]
2345 flags = fctx.flags()
2344 flags = fctx.flags()
2346 mctx = context.memfilectx(repo, ctx_,
2345 mctx = context.memfilectx(repo, ctx_,
2347 fctx.path(), fctx.data(),
2346 fctx.path(), fctx.data(),
2348 islink='l' in flags,
2347 islink='l' in flags,
2349 isexec='x' in flags,
2348 isexec='x' in flags,
2350 copied=copied.get(path))
2349 copied=copied.get(path))
2351 return mctx
2350 return mctx
2352 except KeyError:
2351 except KeyError:
2353 return None
2352 return None
2354 else:
2353 else:
2355 ui.note(_('copying changeset %s to %s\n') % (old, base))
2354 ui.note(_('copying changeset %s to %s\n') % (old, base))
2356
2355
2357 # Use version of files as in the old cset
2356 # Use version of files as in the old cset
2358 def filectxfn(repo, ctx_, path):
2357 def filectxfn(repo, ctx_, path):
2359 try:
2358 try:
2360 return old.filectx(path)
2359 return old.filectx(path)
2361 except KeyError:
2360 except KeyError:
2362 return None
2361 return None
2363
2362
2364 # See if we got a message from -m or -l, if not, open the editor with
2363 # See if we got a message from -m or -l, if not, open the editor with
2365 # the message of the changeset to amend.
2364 # the message of the changeset to amend.
2366 message = logmessage(ui, opts)
2365 message = logmessage(ui, opts)
2367
2366
2368 editform = mergeeditform(old, 'commit.amend')
2367 editform = mergeeditform(old, 'commit.amend')
2369 editor = getcommiteditor(editform=editform,
2368 editor = getcommiteditor(editform=editform,
2370 **pycompat.strkwargs(opts))
2369 **pycompat.strkwargs(opts))
2371
2370
2372 if not message:
2371 if not message:
2373 editor = getcommiteditor(edit=True, editform=editform)
2372 editor = getcommiteditor(edit=True, editform=editform)
2374 message = old.description()
2373 message = old.description()
2375
2374
2376 pureextra = extra.copy()
2375 pureextra = extra.copy()
2377 extra['amend_source'] = old.hex()
2376 extra['amend_source'] = old.hex()
2378
2377
2379 new = context.memctx(repo,
2378 new = context.memctx(repo,
2380 parents=[base.node(), old.p2().node()],
2379 parents=[base.node(), old.p2().node()],
2381 text=message,
2380 text=message,
2382 files=files,
2381 files=files,
2383 filectxfn=filectxfn,
2382 filectxfn=filectxfn,
2384 user=user,
2383 user=user,
2385 date=date,
2384 date=date,
2386 extra=extra,
2385 extra=extra,
2387 editor=editor)
2386 editor=editor)
2388
2387
2389 newdesc = changelog.stripdesc(new.description())
2388 newdesc = changelog.stripdesc(new.description())
2390 if ((not changes)
2389 if ((not changes)
2391 and newdesc == old.description()
2390 and newdesc == old.description()
2392 and user == old.user()
2391 and user == old.user()
2393 and date == old.date()
2392 and date == old.date()
2394 and pureextra == old.extra()):
2393 and pureextra == old.extra()):
2395 # nothing changed. continuing here would create a new node
2394 # nothing changed. continuing here would create a new node
2396 # anyway because of the amend_source noise.
2395 # anyway because of the amend_source noise.
2397 #
2396 #
2398 # This not what we expect from amend.
2397 # This not what we expect from amend.
2399 return old.node()
2398 return old.node()
2400
2399
2401 if opts.get('secret'):
2400 if opts.get('secret'):
2402 commitphase = 'secret'
2401 commitphase = 'secret'
2403 else:
2402 else:
2404 commitphase = old.phase()
2403 commitphase = old.phase()
2405 overrides = {('phases', 'new-commit'): commitphase}
2404 overrides = {('phases', 'new-commit'): commitphase}
2406 with ui.configoverride(overrides, 'amend'):
2405 with ui.configoverride(overrides, 'amend'):
2407 newid = repo.commitctx(new)
2406 newid = repo.commitctx(new)
2408
2407
2409 # Reroute the working copy parent to the new changeset
2408 # Reroute the working copy parent to the new changeset
2410 repo.setparents(newid, nullid)
2409 repo.setparents(newid, nullid)
2411 mapping = {old.node(): (newid,)}
2410 mapping = {old.node(): (newid,)}
2412 obsmetadata = None
2411 obsmetadata = None
2413 if opts.get('note'):
2412 if opts.get('note'):
2414 obsmetadata = {'note': opts['note']}
2413 obsmetadata = {'note': opts['note']}
2415 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2414 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2416
2415
2417 # Fixing the dirstate because localrepo.commitctx does not update
2416 # Fixing the dirstate because localrepo.commitctx does not update
2418 # it. This is rather convenient because we did not need to update
2417 # it. This is rather convenient because we did not need to update
2419 # the dirstate for all the files in the new commit which commitctx
2418 # the dirstate for all the files in the new commit which commitctx
2420 # could have done if it updated the dirstate. Now, we can
2419 # could have done if it updated the dirstate. Now, we can
2421 # selectively update the dirstate only for the amended files.
2420 # selectively update the dirstate only for the amended files.
2422 dirstate = repo.dirstate
2421 dirstate = repo.dirstate
2423
2422
2424 # Update the state of the files which were added and
2423 # Update the state of the files which were added and
2425 # and modified in the amend to "normal" in the dirstate.
2424 # and modified in the amend to "normal" in the dirstate.
2426 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2425 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2427 for f in normalfiles:
2426 for f in normalfiles:
2428 dirstate.normal(f)
2427 dirstate.normal(f)
2429
2428
2430 # Update the state of files which were removed in the amend
2429 # Update the state of files which were removed in the amend
2431 # to "removed" in the dirstate.
2430 # to "removed" in the dirstate.
2432 removedfiles = set(wctx.removed()) & filestoamend
2431 removedfiles = set(wctx.removed()) & filestoamend
2433 for f in removedfiles:
2432 for f in removedfiles:
2434 dirstate.drop(f)
2433 dirstate.drop(f)
2435
2434
2436 return newid
2435 return newid
2437
2436
2438 def commiteditor(repo, ctx, subs, editform=''):
2437 def commiteditor(repo, ctx, subs, editform=''):
2439 if ctx.description():
2438 if ctx.description():
2440 return ctx.description()
2439 return ctx.description()
2441 return commitforceeditor(repo, ctx, subs, editform=editform,
2440 return commitforceeditor(repo, ctx, subs, editform=editform,
2442 unchangedmessagedetection=True)
2441 unchangedmessagedetection=True)
2443
2442
2444 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2443 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2445 editform='', unchangedmessagedetection=False):
2444 editform='', unchangedmessagedetection=False):
2446 if not extramsg:
2445 if not extramsg:
2447 extramsg = _("Leave message empty to abort commit.")
2446 extramsg = _("Leave message empty to abort commit.")
2448
2447
2449 forms = [e for e in editform.split('.') if e]
2448 forms = [e for e in editform.split('.') if e]
2450 forms.insert(0, 'changeset')
2449 forms.insert(0, 'changeset')
2451 templatetext = None
2450 templatetext = None
2452 while forms:
2451 while forms:
2453 ref = '.'.join(forms)
2452 ref = '.'.join(forms)
2454 if repo.ui.config('committemplate', ref):
2453 if repo.ui.config('committemplate', ref):
2455 templatetext = committext = buildcommittemplate(
2454 templatetext = committext = buildcommittemplate(
2456 repo, ctx, subs, extramsg, ref)
2455 repo, ctx, subs, extramsg, ref)
2457 break
2456 break
2458 forms.pop()
2457 forms.pop()
2459 else:
2458 else:
2460 committext = buildcommittext(repo, ctx, subs, extramsg)
2459 committext = buildcommittext(repo, ctx, subs, extramsg)
2461
2460
2462 # run editor in the repository root
2461 # run editor in the repository root
2463 olddir = pycompat.getcwd()
2462 olddir = pycompat.getcwd()
2464 os.chdir(repo.root)
2463 os.chdir(repo.root)
2465
2464
2466 # make in-memory changes visible to external process
2465 # make in-memory changes visible to external process
2467 tr = repo.currenttransaction()
2466 tr = repo.currenttransaction()
2468 repo.dirstate.write(tr)
2467 repo.dirstate.write(tr)
2469 pending = tr and tr.writepending() and repo.root
2468 pending = tr and tr.writepending() and repo.root
2470
2469
2471 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2470 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2472 editform=editform, pending=pending,
2471 editform=editform, pending=pending,
2473 repopath=repo.path, action='commit')
2472 repopath=repo.path, action='commit')
2474 text = editortext
2473 text = editortext
2475
2474
2476 # strip away anything below this special string (used for editors that want
2475 # strip away anything below this special string (used for editors that want
2477 # to display the diff)
2476 # to display the diff)
2478 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2477 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2479 if stripbelow:
2478 if stripbelow:
2480 text = text[:stripbelow.start()]
2479 text = text[:stripbelow.start()]
2481
2480
2482 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2481 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2483 os.chdir(olddir)
2482 os.chdir(olddir)
2484
2483
2485 if finishdesc:
2484 if finishdesc:
2486 text = finishdesc(text)
2485 text = finishdesc(text)
2487 if not text.strip():
2486 if not text.strip():
2488 raise error.Abort(_("empty commit message"))
2487 raise error.Abort(_("empty commit message"))
2489 if unchangedmessagedetection and editortext == templatetext:
2488 if unchangedmessagedetection and editortext == templatetext:
2490 raise error.Abort(_("commit message unchanged"))
2489 raise error.Abort(_("commit message unchanged"))
2491
2490
2492 return text
2491 return text
2493
2492
2494 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2493 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2495 ui = repo.ui
2494 ui = repo.ui
2496 spec = formatter.templatespec(ref, None, None)
2495 spec = formatter.templatespec(ref, None, None)
2497 t = logcmdutil.changesettemplater(ui, repo, spec)
2496 t = logcmdutil.changesettemplater(ui, repo, spec)
2498 t.t.cache.update((k, templater.unquotestring(v))
2497 t.t.cache.update((k, templater.unquotestring(v))
2499 for k, v in repo.ui.configitems('committemplate'))
2498 for k, v in repo.ui.configitems('committemplate'))
2500
2499
2501 if not extramsg:
2500 if not extramsg:
2502 extramsg = '' # ensure that extramsg is string
2501 extramsg = '' # ensure that extramsg is string
2503
2502
2504 ui.pushbuffer()
2503 ui.pushbuffer()
2505 t.show(ctx, extramsg=extramsg)
2504 t.show(ctx, extramsg=extramsg)
2506 return ui.popbuffer()
2505 return ui.popbuffer()
2507
2506
2508 def hgprefix(msg):
2507 def hgprefix(msg):
2509 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2508 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2510
2509
2511 def buildcommittext(repo, ctx, subs, extramsg):
2510 def buildcommittext(repo, ctx, subs, extramsg):
2512 edittext = []
2511 edittext = []
2513 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2512 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2514 if ctx.description():
2513 if ctx.description():
2515 edittext.append(ctx.description())
2514 edittext.append(ctx.description())
2516 edittext.append("")
2515 edittext.append("")
2517 edittext.append("") # Empty line between message and comments.
2516 edittext.append("") # Empty line between message and comments.
2518 edittext.append(hgprefix(_("Enter commit message."
2517 edittext.append(hgprefix(_("Enter commit message."
2519 " Lines beginning with 'HG:' are removed.")))
2518 " Lines beginning with 'HG:' are removed.")))
2520 edittext.append(hgprefix(extramsg))
2519 edittext.append(hgprefix(extramsg))
2521 edittext.append("HG: --")
2520 edittext.append("HG: --")
2522 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2521 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2523 if ctx.p2():
2522 if ctx.p2():
2524 edittext.append(hgprefix(_("branch merge")))
2523 edittext.append(hgprefix(_("branch merge")))
2525 if ctx.branch():
2524 if ctx.branch():
2526 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2525 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2527 if bookmarks.isactivewdirparent(repo):
2526 if bookmarks.isactivewdirparent(repo):
2528 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2527 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2529 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2528 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2530 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2529 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2531 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2530 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2532 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2531 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2533 if not added and not modified and not removed:
2532 if not added and not modified and not removed:
2534 edittext.append(hgprefix(_("no files changed")))
2533 edittext.append(hgprefix(_("no files changed")))
2535 edittext.append("")
2534 edittext.append("")
2536
2535
2537 return "\n".join(edittext)
2536 return "\n".join(edittext)
2538
2537
2539 def commitstatus(repo, node, branch, bheads=None, opts=None):
2538 def commitstatus(repo, node, branch, bheads=None, opts=None):
2540 if opts is None:
2539 if opts is None:
2541 opts = {}
2540 opts = {}
2542 ctx = repo[node]
2541 ctx = repo[node]
2543 parents = ctx.parents()
2542 parents = ctx.parents()
2544
2543
2545 if (not opts.get('amend') and bheads and node not in bheads and not
2544 if (not opts.get('amend') and bheads and node not in bheads and not
2546 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2545 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2547 repo.ui.status(_('created new head\n'))
2546 repo.ui.status(_('created new head\n'))
2548 # The message is not printed for initial roots. For the other
2547 # The message is not printed for initial roots. For the other
2549 # changesets, it is printed in the following situations:
2548 # changesets, it is printed in the following situations:
2550 #
2549 #
2551 # Par column: for the 2 parents with ...
2550 # Par column: for the 2 parents with ...
2552 # N: null or no parent
2551 # N: null or no parent
2553 # B: parent is on another named branch
2552 # B: parent is on another named branch
2554 # C: parent is a regular non head changeset
2553 # C: parent is a regular non head changeset
2555 # H: parent was a branch head of the current branch
2554 # H: parent was a branch head of the current branch
2556 # Msg column: whether we print "created new head" message
2555 # Msg column: whether we print "created new head" message
2557 # In the following, it is assumed that there already exists some
2556 # In the following, it is assumed that there already exists some
2558 # initial branch heads of the current branch, otherwise nothing is
2557 # initial branch heads of the current branch, otherwise nothing is
2559 # printed anyway.
2558 # printed anyway.
2560 #
2559 #
2561 # Par Msg Comment
2560 # Par Msg Comment
2562 # N N y additional topo root
2561 # N N y additional topo root
2563 #
2562 #
2564 # B N y additional branch root
2563 # B N y additional branch root
2565 # C N y additional topo head
2564 # C N y additional topo head
2566 # H N n usual case
2565 # H N n usual case
2567 #
2566 #
2568 # B B y weird additional branch root
2567 # B B y weird additional branch root
2569 # C B y branch merge
2568 # C B y branch merge
2570 # H B n merge with named branch
2569 # H B n merge with named branch
2571 #
2570 #
2572 # C C y additional head from merge
2571 # C C y additional head from merge
2573 # C H n merge with a head
2572 # C H n merge with a head
2574 #
2573 #
2575 # H H n head merge: head count decreases
2574 # H H n head merge: head count decreases
2576
2575
2577 if not opts.get('close_branch'):
2576 if not opts.get('close_branch'):
2578 for r in parents:
2577 for r in parents:
2579 if r.closesbranch() and r.branch() == branch:
2578 if r.closesbranch() and r.branch() == branch:
2580 repo.ui.status(_('reopening closed branch head %d\n') % r)
2579 repo.ui.status(_('reopening closed branch head %d\n') % r)
2581
2580
2582 if repo.ui.debugflag:
2581 if repo.ui.debugflag:
2583 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2582 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2584 elif repo.ui.verbose:
2583 elif repo.ui.verbose:
2585 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2584 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2586
2585
2587 def postcommitstatus(repo, pats, opts):
2586 def postcommitstatus(repo, pats, opts):
2588 return repo.status(match=scmutil.match(repo[None], pats, opts))
2587 return repo.status(match=scmutil.match(repo[None], pats, opts))
2589
2588
2590 def revert(ui, repo, ctx, parents, *pats, **opts):
2589 def revert(ui, repo, ctx, parents, *pats, **opts):
2591 opts = pycompat.byteskwargs(opts)
2590 opts = pycompat.byteskwargs(opts)
2592 parent, p2 = parents
2591 parent, p2 = parents
2593 node = ctx.node()
2592 node = ctx.node()
2594
2593
2595 mf = ctx.manifest()
2594 mf = ctx.manifest()
2596 if node == p2:
2595 if node == p2:
2597 parent = p2
2596 parent = p2
2598
2597
2599 # need all matching names in dirstate and manifest of target rev,
2598 # need all matching names in dirstate and manifest of target rev,
2600 # so have to walk both. do not print errors if files exist in one
2599 # so have to walk both. do not print errors if files exist in one
2601 # but not other. in both cases, filesets should be evaluated against
2600 # but not other. in both cases, filesets should be evaluated against
2602 # workingctx to get consistent result (issue4497). this means 'set:**'
2601 # workingctx to get consistent result (issue4497). this means 'set:**'
2603 # cannot be used to select missing files from target rev.
2602 # cannot be used to select missing files from target rev.
2604
2603
2605 # `names` is a mapping for all elements in working copy and target revision
2604 # `names` is a mapping for all elements in working copy and target revision
2606 # The mapping is in the form:
2605 # The mapping is in the form:
2607 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2606 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2608 names = {}
2607 names = {}
2609
2608
2610 with repo.wlock():
2609 with repo.wlock():
2611 ## filling of the `names` mapping
2610 ## filling of the `names` mapping
2612 # walk dirstate to fill `names`
2611 # walk dirstate to fill `names`
2613
2612
2614 interactive = opts.get('interactive', False)
2613 interactive = opts.get('interactive', False)
2615 wctx = repo[None]
2614 wctx = repo[None]
2616 m = scmutil.match(wctx, pats, opts)
2615 m = scmutil.match(wctx, pats, opts)
2617
2616
2618 # we'll need this later
2617 # we'll need this later
2619 targetsubs = sorted(s for s in wctx.substate if m(s))
2618 targetsubs = sorted(s for s in wctx.substate if m(s))
2620
2619
2621 if not m.always():
2620 if not m.always():
2622 matcher = matchmod.badmatch(m, lambda x, y: False)
2621 matcher = matchmod.badmatch(m, lambda x, y: False)
2623 for abs in wctx.walk(matcher):
2622 for abs in wctx.walk(matcher):
2624 names[abs] = m.rel(abs), m.exact(abs)
2623 names[abs] = m.rel(abs), m.exact(abs)
2625
2624
2626 # walk target manifest to fill `names`
2625 # walk target manifest to fill `names`
2627
2626
2628 def badfn(path, msg):
2627 def badfn(path, msg):
2629 if path in names:
2628 if path in names:
2630 return
2629 return
2631 if path in ctx.substate:
2630 if path in ctx.substate:
2632 return
2631 return
2633 path_ = path + '/'
2632 path_ = path + '/'
2634 for f in names:
2633 for f in names:
2635 if f.startswith(path_):
2634 if f.startswith(path_):
2636 return
2635 return
2637 ui.warn("%s: %s\n" % (m.rel(path), msg))
2636 ui.warn("%s: %s\n" % (m.rel(path), msg))
2638
2637
2639 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2638 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2640 if abs not in names:
2639 if abs not in names:
2641 names[abs] = m.rel(abs), m.exact(abs)
2640 names[abs] = m.rel(abs), m.exact(abs)
2642
2641
2643 # Find status of all file in `names`.
2642 # Find status of all file in `names`.
2644 m = scmutil.matchfiles(repo, names)
2643 m = scmutil.matchfiles(repo, names)
2645
2644
2646 changes = repo.status(node1=node, match=m,
2645 changes = repo.status(node1=node, match=m,
2647 unknown=True, ignored=True, clean=True)
2646 unknown=True, ignored=True, clean=True)
2648 else:
2647 else:
2649 changes = repo.status(node1=node, match=m)
2648 changes = repo.status(node1=node, match=m)
2650 for kind in changes:
2649 for kind in changes:
2651 for abs in kind:
2650 for abs in kind:
2652 names[abs] = m.rel(abs), m.exact(abs)
2651 names[abs] = m.rel(abs), m.exact(abs)
2653
2652
2654 m = scmutil.matchfiles(repo, names)
2653 m = scmutil.matchfiles(repo, names)
2655
2654
2656 modified = set(changes.modified)
2655 modified = set(changes.modified)
2657 added = set(changes.added)
2656 added = set(changes.added)
2658 removed = set(changes.removed)
2657 removed = set(changes.removed)
2659 _deleted = set(changes.deleted)
2658 _deleted = set(changes.deleted)
2660 unknown = set(changes.unknown)
2659 unknown = set(changes.unknown)
2661 unknown.update(changes.ignored)
2660 unknown.update(changes.ignored)
2662 clean = set(changes.clean)
2661 clean = set(changes.clean)
2663 modadded = set()
2662 modadded = set()
2664
2663
2665 # We need to account for the state of the file in the dirstate,
2664 # We need to account for the state of the file in the dirstate,
2666 # even when we revert against something else than parent. This will
2665 # even when we revert against something else than parent. This will
2667 # slightly alter the behavior of revert (doing back up or not, delete
2666 # slightly alter the behavior of revert (doing back up or not, delete
2668 # or just forget etc).
2667 # or just forget etc).
2669 if parent == node:
2668 if parent == node:
2670 dsmodified = modified
2669 dsmodified = modified
2671 dsadded = added
2670 dsadded = added
2672 dsremoved = removed
2671 dsremoved = removed
2673 # store all local modifications, useful later for rename detection
2672 # store all local modifications, useful later for rename detection
2674 localchanges = dsmodified | dsadded
2673 localchanges = dsmodified | dsadded
2675 modified, added, removed = set(), set(), set()
2674 modified, added, removed = set(), set(), set()
2676 else:
2675 else:
2677 changes = repo.status(node1=parent, match=m)
2676 changes = repo.status(node1=parent, match=m)
2678 dsmodified = set(changes.modified)
2677 dsmodified = set(changes.modified)
2679 dsadded = set(changes.added)
2678 dsadded = set(changes.added)
2680 dsremoved = set(changes.removed)
2679 dsremoved = set(changes.removed)
2681 # store all local modifications, useful later for rename detection
2680 # store all local modifications, useful later for rename detection
2682 localchanges = dsmodified | dsadded
2681 localchanges = dsmodified | dsadded
2683
2682
2684 # only take into account for removes between wc and target
2683 # only take into account for removes between wc and target
2685 clean |= dsremoved - removed
2684 clean |= dsremoved - removed
2686 dsremoved &= removed
2685 dsremoved &= removed
2687 # distinct between dirstate remove and other
2686 # distinct between dirstate remove and other
2688 removed -= dsremoved
2687 removed -= dsremoved
2689
2688
2690 modadded = added & dsmodified
2689 modadded = added & dsmodified
2691 added -= modadded
2690 added -= modadded
2692
2691
2693 # tell newly modified apart.
2692 # tell newly modified apart.
2694 dsmodified &= modified
2693 dsmodified &= modified
2695 dsmodified |= modified & dsadded # dirstate added may need backup
2694 dsmodified |= modified & dsadded # dirstate added may need backup
2696 modified -= dsmodified
2695 modified -= dsmodified
2697
2696
2698 # We need to wait for some post-processing to update this set
2697 # We need to wait for some post-processing to update this set
2699 # before making the distinction. The dirstate will be used for
2698 # before making the distinction. The dirstate will be used for
2700 # that purpose.
2699 # that purpose.
2701 dsadded = added
2700 dsadded = added
2702
2701
2703 # in case of merge, files that are actually added can be reported as
2702 # in case of merge, files that are actually added can be reported as
2704 # modified, we need to post process the result
2703 # modified, we need to post process the result
2705 if p2 != nullid:
2704 if p2 != nullid:
2706 mergeadd = set(dsmodified)
2705 mergeadd = set(dsmodified)
2707 for path in dsmodified:
2706 for path in dsmodified:
2708 if path in mf:
2707 if path in mf:
2709 mergeadd.remove(path)
2708 mergeadd.remove(path)
2710 dsadded |= mergeadd
2709 dsadded |= mergeadd
2711 dsmodified -= mergeadd
2710 dsmodified -= mergeadd
2712
2711
2713 # if f is a rename, update `names` to also revert the source
2712 # if f is a rename, update `names` to also revert the source
2714 cwd = repo.getcwd()
2713 cwd = repo.getcwd()
2715 for f in localchanges:
2714 for f in localchanges:
2716 src = repo.dirstate.copied(f)
2715 src = repo.dirstate.copied(f)
2717 # XXX should we check for rename down to target node?
2716 # XXX should we check for rename down to target node?
2718 if src and src not in names and repo.dirstate[src] == 'r':
2717 if src and src not in names and repo.dirstate[src] == 'r':
2719 dsremoved.add(src)
2718 dsremoved.add(src)
2720 names[src] = (repo.pathto(src, cwd), True)
2719 names[src] = (repo.pathto(src, cwd), True)
2721
2720
2722 # determine the exact nature of the deleted changesets
2721 # determine the exact nature of the deleted changesets
2723 deladded = set(_deleted)
2722 deladded = set(_deleted)
2724 for path in _deleted:
2723 for path in _deleted:
2725 if path in mf:
2724 if path in mf:
2726 deladded.remove(path)
2725 deladded.remove(path)
2727 deleted = _deleted - deladded
2726 deleted = _deleted - deladded
2728
2727
2729 # distinguish between file to forget and the other
2728 # distinguish between file to forget and the other
2730 added = set()
2729 added = set()
2731 for abs in dsadded:
2730 for abs in dsadded:
2732 if repo.dirstate[abs] != 'a':
2731 if repo.dirstate[abs] != 'a':
2733 added.add(abs)
2732 added.add(abs)
2734 dsadded -= added
2733 dsadded -= added
2735
2734
2736 for abs in deladded:
2735 for abs in deladded:
2737 if repo.dirstate[abs] == 'a':
2736 if repo.dirstate[abs] == 'a':
2738 dsadded.add(abs)
2737 dsadded.add(abs)
2739 deladded -= dsadded
2738 deladded -= dsadded
2740
2739
2741 # For files marked as removed, we check if an unknown file is present at
2740 # For files marked as removed, we check if an unknown file is present at
2742 # the same path. If a such file exists it may need to be backed up.
2741 # the same path. If a such file exists it may need to be backed up.
2743 # Making the distinction at this stage helps have simpler backup
2742 # Making the distinction at this stage helps have simpler backup
2744 # logic.
2743 # logic.
2745 removunk = set()
2744 removunk = set()
2746 for abs in removed:
2745 for abs in removed:
2747 target = repo.wjoin(abs)
2746 target = repo.wjoin(abs)
2748 if os.path.lexists(target):
2747 if os.path.lexists(target):
2749 removunk.add(abs)
2748 removunk.add(abs)
2750 removed -= removunk
2749 removed -= removunk
2751
2750
2752 dsremovunk = set()
2751 dsremovunk = set()
2753 for abs in dsremoved:
2752 for abs in dsremoved:
2754 target = repo.wjoin(abs)
2753 target = repo.wjoin(abs)
2755 if os.path.lexists(target):
2754 if os.path.lexists(target):
2756 dsremovunk.add(abs)
2755 dsremovunk.add(abs)
2757 dsremoved -= dsremovunk
2756 dsremoved -= dsremovunk
2758
2757
2759 # action to be actually performed by revert
2758 # action to be actually performed by revert
2760 # (<list of file>, message>) tuple
2759 # (<list of file>, message>) tuple
2761 actions = {'revert': ([], _('reverting %s\n')),
2760 actions = {'revert': ([], _('reverting %s\n')),
2762 'add': ([], _('adding %s\n')),
2761 'add': ([], _('adding %s\n')),
2763 'remove': ([], _('removing %s\n')),
2762 'remove': ([], _('removing %s\n')),
2764 'drop': ([], _('removing %s\n')),
2763 'drop': ([], _('removing %s\n')),
2765 'forget': ([], _('forgetting %s\n')),
2764 'forget': ([], _('forgetting %s\n')),
2766 'undelete': ([], _('undeleting %s\n')),
2765 'undelete': ([], _('undeleting %s\n')),
2767 'noop': (None, _('no changes needed to %s\n')),
2766 'noop': (None, _('no changes needed to %s\n')),
2768 'unknown': (None, _('file not managed: %s\n')),
2767 'unknown': (None, _('file not managed: %s\n')),
2769 }
2768 }
2770
2769
2771 # "constant" that convey the backup strategy.
2770 # "constant" that convey the backup strategy.
2772 # All set to `discard` if `no-backup` is set do avoid checking
2771 # All set to `discard` if `no-backup` is set do avoid checking
2773 # no_backup lower in the code.
2772 # no_backup lower in the code.
2774 # These values are ordered for comparison purposes
2773 # These values are ordered for comparison purposes
2775 backupinteractive = 3 # do backup if interactively modified
2774 backupinteractive = 3 # do backup if interactively modified
2776 backup = 2 # unconditionally do backup
2775 backup = 2 # unconditionally do backup
2777 check = 1 # check if the existing file differs from target
2776 check = 1 # check if the existing file differs from target
2778 discard = 0 # never do backup
2777 discard = 0 # never do backup
2779 if opts.get('no_backup'):
2778 if opts.get('no_backup'):
2780 backupinteractive = backup = check = discard
2779 backupinteractive = backup = check = discard
2781 if interactive:
2780 if interactive:
2782 dsmodifiedbackup = backupinteractive
2781 dsmodifiedbackup = backupinteractive
2783 else:
2782 else:
2784 dsmodifiedbackup = backup
2783 dsmodifiedbackup = backup
2785 tobackup = set()
2784 tobackup = set()
2786
2785
2787 backupanddel = actions['remove']
2786 backupanddel = actions['remove']
2788 if not opts.get('no_backup'):
2787 if not opts.get('no_backup'):
2789 backupanddel = actions['drop']
2788 backupanddel = actions['drop']
2790
2789
2791 disptable = (
2790 disptable = (
2792 # dispatch table:
2791 # dispatch table:
2793 # file state
2792 # file state
2794 # action
2793 # action
2795 # make backup
2794 # make backup
2796
2795
2797 ## Sets that results that will change file on disk
2796 ## Sets that results that will change file on disk
2798 # Modified compared to target, no local change
2797 # Modified compared to target, no local change
2799 (modified, actions['revert'], discard),
2798 (modified, actions['revert'], discard),
2800 # Modified compared to target, but local file is deleted
2799 # Modified compared to target, but local file is deleted
2801 (deleted, actions['revert'], discard),
2800 (deleted, actions['revert'], discard),
2802 # Modified compared to target, local change
2801 # Modified compared to target, local change
2803 (dsmodified, actions['revert'], dsmodifiedbackup),
2802 (dsmodified, actions['revert'], dsmodifiedbackup),
2804 # Added since target
2803 # Added since target
2805 (added, actions['remove'], discard),
2804 (added, actions['remove'], discard),
2806 # Added in working directory
2805 # Added in working directory
2807 (dsadded, actions['forget'], discard),
2806 (dsadded, actions['forget'], discard),
2808 # Added since target, have local modification
2807 # Added since target, have local modification
2809 (modadded, backupanddel, backup),
2808 (modadded, backupanddel, backup),
2810 # Added since target but file is missing in working directory
2809 # Added since target but file is missing in working directory
2811 (deladded, actions['drop'], discard),
2810 (deladded, actions['drop'], discard),
2812 # Removed since target, before working copy parent
2811 # Removed since target, before working copy parent
2813 (removed, actions['add'], discard),
2812 (removed, actions['add'], discard),
2814 # Same as `removed` but an unknown file exists at the same path
2813 # Same as `removed` but an unknown file exists at the same path
2815 (removunk, actions['add'], check),
2814 (removunk, actions['add'], check),
2816 # Removed since targe, marked as such in working copy parent
2815 # Removed since targe, marked as such in working copy parent
2817 (dsremoved, actions['undelete'], discard),
2816 (dsremoved, actions['undelete'], discard),
2818 # Same as `dsremoved` but an unknown file exists at the same path
2817 # Same as `dsremoved` but an unknown file exists at the same path
2819 (dsremovunk, actions['undelete'], check),
2818 (dsremovunk, actions['undelete'], check),
2820 ## the following sets does not result in any file changes
2819 ## the following sets does not result in any file changes
2821 # File with no modification
2820 # File with no modification
2822 (clean, actions['noop'], discard),
2821 (clean, actions['noop'], discard),
2823 # Existing file, not tracked anywhere
2822 # Existing file, not tracked anywhere
2824 (unknown, actions['unknown'], discard),
2823 (unknown, actions['unknown'], discard),
2825 )
2824 )
2826
2825
2827 for abs, (rel, exact) in sorted(names.items()):
2826 for abs, (rel, exact) in sorted(names.items()):
2828 # target file to be touch on disk (relative to cwd)
2827 # target file to be touch on disk (relative to cwd)
2829 target = repo.wjoin(abs)
2828 target = repo.wjoin(abs)
2830 # search the entry in the dispatch table.
2829 # search the entry in the dispatch table.
2831 # if the file is in any of these sets, it was touched in the working
2830 # if the file is in any of these sets, it was touched in the working
2832 # directory parent and we are sure it needs to be reverted.
2831 # directory parent and we are sure it needs to be reverted.
2833 for table, (xlist, msg), dobackup in disptable:
2832 for table, (xlist, msg), dobackup in disptable:
2834 if abs not in table:
2833 if abs not in table:
2835 continue
2834 continue
2836 if xlist is not None:
2835 if xlist is not None:
2837 xlist.append(abs)
2836 xlist.append(abs)
2838 if dobackup:
2837 if dobackup:
2839 # If in interactive mode, don't automatically create
2838 # If in interactive mode, don't automatically create
2840 # .orig files (issue4793)
2839 # .orig files (issue4793)
2841 if dobackup == backupinteractive:
2840 if dobackup == backupinteractive:
2842 tobackup.add(abs)
2841 tobackup.add(abs)
2843 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2842 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2844 bakname = scmutil.origpath(ui, repo, rel)
2843 bakname = scmutil.origpath(ui, repo, rel)
2845 ui.note(_('saving current version of %s as %s\n') %
2844 ui.note(_('saving current version of %s as %s\n') %
2846 (rel, bakname))
2845 (rel, bakname))
2847 if not opts.get('dry_run'):
2846 if not opts.get('dry_run'):
2848 if interactive:
2847 if interactive:
2849 util.copyfile(target, bakname)
2848 util.copyfile(target, bakname)
2850 else:
2849 else:
2851 util.rename(target, bakname)
2850 util.rename(target, bakname)
2852 if ui.verbose or not exact:
2851 if ui.verbose or not exact:
2853 if not isinstance(msg, bytes):
2852 if not isinstance(msg, bytes):
2854 msg = msg(abs)
2853 msg = msg(abs)
2855 ui.status(msg % rel)
2854 ui.status(msg % rel)
2856 elif exact:
2855 elif exact:
2857 ui.warn(msg % rel)
2856 ui.warn(msg % rel)
2858 break
2857 break
2859
2858
2860 if not opts.get('dry_run'):
2859 if not opts.get('dry_run'):
2861 needdata = ('revert', 'add', 'undelete')
2860 needdata = ('revert', 'add', 'undelete')
2862 if _revertprefetch is not _revertprefetchstub:
2861 if _revertprefetch is not _revertprefetchstub:
2863 ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
2862 ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
2864 "add a callback to 'scmutil.fileprefetchhooks'",
2863 "add a callback to 'scmutil.fileprefetchhooks'",
2865 '4.6', stacklevel=1)
2864 '4.6', stacklevel=1)
2866 _revertprefetch(repo, ctx,
2865 _revertprefetch(repo, ctx,
2867 *[actions[name][0] for name in needdata])
2866 *[actions[name][0] for name in needdata])
2868 oplist = [actions[name][0] for name in needdata]
2867 oplist = [actions[name][0] for name in needdata]
2869 prefetch = scmutil.fileprefetchhooks
2868 prefetch = scmutil.fileprefetchhooks
2870 prefetch(repo, ctx, [f for sublist in oplist for f in sublist])
2869 prefetch(repo, ctx, [f for sublist in oplist for f in sublist])
2871 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2870 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2872
2871
2873 if targetsubs:
2872 if targetsubs:
2874 # Revert the subrepos on the revert list
2873 # Revert the subrepos on the revert list
2875 for sub in targetsubs:
2874 for sub in targetsubs:
2876 try:
2875 try:
2877 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2876 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2878 **pycompat.strkwargs(opts))
2877 **pycompat.strkwargs(opts))
2879 except KeyError:
2878 except KeyError:
2880 raise error.Abort("subrepository '%s' does not exist in %s!"
2879 raise error.Abort("subrepository '%s' does not exist in %s!"
2881 % (sub, short(ctx.node())))
2880 % (sub, short(ctx.node())))
2882
2881
2883 def _revertprefetchstub(repo, ctx, *files):
2882 def _revertprefetchstub(repo, ctx, *files):
2884 """Stub method for detecting extension wrapping of _revertprefetch(), to
2883 """Stub method for detecting extension wrapping of _revertprefetch(), to
2885 issue a deprecation warning."""
2884 issue a deprecation warning."""
2886
2885
2887 _revertprefetch = _revertprefetchstub
2886 _revertprefetch = _revertprefetchstub
2888
2887
2889 def _performrevert(repo, parents, ctx, actions, interactive=False,
2888 def _performrevert(repo, parents, ctx, actions, interactive=False,
2890 tobackup=None):
2889 tobackup=None):
2891 """function that actually perform all the actions computed for revert
2890 """function that actually perform all the actions computed for revert
2892
2891
2893 This is an independent function to let extension to plug in and react to
2892 This is an independent function to let extension to plug in and react to
2894 the imminent revert.
2893 the imminent revert.
2895
2894
2896 Make sure you have the working directory locked when calling this function.
2895 Make sure you have the working directory locked when calling this function.
2897 """
2896 """
2898 parent, p2 = parents
2897 parent, p2 = parents
2899 node = ctx.node()
2898 node = ctx.node()
2900 excluded_files = []
2899 excluded_files = []
2901
2900
2902 def checkout(f):
2901 def checkout(f):
2903 fc = ctx[f]
2902 fc = ctx[f]
2904 repo.wwrite(f, fc.data(), fc.flags())
2903 repo.wwrite(f, fc.data(), fc.flags())
2905
2904
2906 def doremove(f):
2905 def doremove(f):
2907 try:
2906 try:
2908 repo.wvfs.unlinkpath(f)
2907 repo.wvfs.unlinkpath(f)
2909 except OSError:
2908 except OSError:
2910 pass
2909 pass
2911 repo.dirstate.remove(f)
2910 repo.dirstate.remove(f)
2912
2911
2913 audit_path = pathutil.pathauditor(repo.root, cached=True)
2912 audit_path = pathutil.pathauditor(repo.root, cached=True)
2914 for f in actions['forget'][0]:
2913 for f in actions['forget'][0]:
2915 if interactive:
2914 if interactive:
2916 choice = repo.ui.promptchoice(
2915 choice = repo.ui.promptchoice(
2917 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2916 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2918 if choice == 0:
2917 if choice == 0:
2919 repo.dirstate.drop(f)
2918 repo.dirstate.drop(f)
2920 else:
2919 else:
2921 excluded_files.append(f)
2920 excluded_files.append(f)
2922 else:
2921 else:
2923 repo.dirstate.drop(f)
2922 repo.dirstate.drop(f)
2924 for f in actions['remove'][0]:
2923 for f in actions['remove'][0]:
2925 audit_path(f)
2924 audit_path(f)
2926 if interactive:
2925 if interactive:
2927 choice = repo.ui.promptchoice(
2926 choice = repo.ui.promptchoice(
2928 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
2927 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
2929 if choice == 0:
2928 if choice == 0:
2930 doremove(f)
2929 doremove(f)
2931 else:
2930 else:
2932 excluded_files.append(f)
2931 excluded_files.append(f)
2933 else:
2932 else:
2934 doremove(f)
2933 doremove(f)
2935 for f in actions['drop'][0]:
2934 for f in actions['drop'][0]:
2936 audit_path(f)
2935 audit_path(f)
2937 repo.dirstate.remove(f)
2936 repo.dirstate.remove(f)
2938
2937
2939 normal = None
2938 normal = None
2940 if node == parent:
2939 if node == parent:
2941 # We're reverting to our parent. If possible, we'd like status
2940 # We're reverting to our parent. If possible, we'd like status
2942 # to report the file as clean. We have to use normallookup for
2941 # to report the file as clean. We have to use normallookup for
2943 # merges to avoid losing information about merged/dirty files.
2942 # merges to avoid losing information about merged/dirty files.
2944 if p2 != nullid:
2943 if p2 != nullid:
2945 normal = repo.dirstate.normallookup
2944 normal = repo.dirstate.normallookup
2946 else:
2945 else:
2947 normal = repo.dirstate.normal
2946 normal = repo.dirstate.normal
2948
2947
2949 newlyaddedandmodifiedfiles = set()
2948 newlyaddedandmodifiedfiles = set()
2950 if interactive:
2949 if interactive:
2951 # Prompt the user for changes to revert
2950 # Prompt the user for changes to revert
2952 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
2951 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
2953 m = scmutil.matchfiles(repo, torevert)
2952 m = scmutil.matchfiles(repo, torevert)
2954 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
2953 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
2955 diffopts.nodates = True
2954 diffopts.nodates = True
2956 diffopts.git = True
2955 diffopts.git = True
2957 operation = 'discard'
2956 operation = 'discard'
2958 reversehunks = True
2957 reversehunks = True
2959 if node != parent:
2958 if node != parent:
2960 operation = 'apply'
2959 operation = 'apply'
2961 reversehunks = False
2960 reversehunks = False
2962 if reversehunks:
2961 if reversehunks:
2963 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
2962 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
2964 else:
2963 else:
2965 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
2964 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
2966 originalchunks = patch.parsepatch(diff)
2965 originalchunks = patch.parsepatch(diff)
2967
2966
2968 try:
2967 try:
2969
2968
2970 chunks, opts = recordfilter(repo.ui, originalchunks,
2969 chunks, opts = recordfilter(repo.ui, originalchunks,
2971 operation=operation)
2970 operation=operation)
2972 if reversehunks:
2971 if reversehunks:
2973 chunks = patch.reversehunks(chunks)
2972 chunks = patch.reversehunks(chunks)
2974
2973
2975 except error.PatchError as err:
2974 except error.PatchError as err:
2976 raise error.Abort(_('error parsing patch: %s') % err)
2975 raise error.Abort(_('error parsing patch: %s') % err)
2977
2976
2978 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
2977 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
2979 if tobackup is None:
2978 if tobackup is None:
2980 tobackup = set()
2979 tobackup = set()
2981 # Apply changes
2980 # Apply changes
2982 fp = stringio()
2981 fp = stringio()
2983 for c in chunks:
2982 for c in chunks:
2984 # Create a backup file only if this hunk should be backed up
2983 # Create a backup file only if this hunk should be backed up
2985 if ishunk(c) and c.header.filename() in tobackup:
2984 if ishunk(c) and c.header.filename() in tobackup:
2986 abs = c.header.filename()
2985 abs = c.header.filename()
2987 target = repo.wjoin(abs)
2986 target = repo.wjoin(abs)
2988 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
2987 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
2989 util.copyfile(target, bakname)
2988 util.copyfile(target, bakname)
2990 tobackup.remove(abs)
2989 tobackup.remove(abs)
2991 c.write(fp)
2990 c.write(fp)
2992 dopatch = fp.tell()
2991 dopatch = fp.tell()
2993 fp.seek(0)
2992 fp.seek(0)
2994 if dopatch:
2993 if dopatch:
2995 try:
2994 try:
2996 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
2995 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
2997 except error.PatchError as err:
2996 except error.PatchError as err:
2998 raise error.Abort(str(err))
2997 raise error.Abort(str(err))
2999 del fp
2998 del fp
3000 else:
2999 else:
3001 for f in actions['revert'][0]:
3000 for f in actions['revert'][0]:
3002 checkout(f)
3001 checkout(f)
3003 if normal:
3002 if normal:
3004 normal(f)
3003 normal(f)
3005
3004
3006 for f in actions['add'][0]:
3005 for f in actions['add'][0]:
3007 # Don't checkout modified files, they are already created by the diff
3006 # Don't checkout modified files, they are already created by the diff
3008 if f not in newlyaddedandmodifiedfiles:
3007 if f not in newlyaddedandmodifiedfiles:
3009 checkout(f)
3008 checkout(f)
3010 repo.dirstate.add(f)
3009 repo.dirstate.add(f)
3011
3010
3012 normal = repo.dirstate.normallookup
3011 normal = repo.dirstate.normallookup
3013 if node == parent and p2 == nullid:
3012 if node == parent and p2 == nullid:
3014 normal = repo.dirstate.normal
3013 normal = repo.dirstate.normal
3015 for f in actions['undelete'][0]:
3014 for f in actions['undelete'][0]:
3016 checkout(f)
3015 checkout(f)
3017 normal(f)
3016 normal(f)
3018
3017
3019 copied = copies.pathcopies(repo[parent], ctx)
3018 copied = copies.pathcopies(repo[parent], ctx)
3020
3019
3021 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3020 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3022 if f in copied:
3021 if f in copied:
3023 repo.dirstate.copy(copied[f], f)
3022 repo.dirstate.copy(copied[f], f)
3024
3023
3025 class command(registrar.command):
3024 class command(registrar.command):
3026 """deprecated: used registrar.command instead"""
3025 """deprecated: used registrar.command instead"""
3027 def _doregister(self, func, name, *args, **kwargs):
3026 def _doregister(self, func, name, *args, **kwargs):
3028 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3027 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3029 return super(command, self)._doregister(func, name, *args, **kwargs)
3028 return super(command, self)._doregister(func, name, *args, **kwargs)
3030
3029
3031 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3030 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3032 # commands.outgoing. "missing" is "missing" of the result of
3031 # commands.outgoing. "missing" is "missing" of the result of
3033 # "findcommonoutgoing()"
3032 # "findcommonoutgoing()"
3034 outgoinghooks = util.hooks()
3033 outgoinghooks = util.hooks()
3035
3034
3036 # a list of (ui, repo) functions called by commands.summary
3035 # a list of (ui, repo) functions called by commands.summary
3037 summaryhooks = util.hooks()
3036 summaryhooks = util.hooks()
3038
3037
3039 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3038 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3040 #
3039 #
3041 # functions should return tuple of booleans below, if 'changes' is None:
3040 # functions should return tuple of booleans below, if 'changes' is None:
3042 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3041 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3043 #
3042 #
3044 # otherwise, 'changes' is a tuple of tuples below:
3043 # otherwise, 'changes' is a tuple of tuples below:
3045 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3044 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3046 # - (desturl, destbranch, destpeer, outgoing)
3045 # - (desturl, destbranch, destpeer, outgoing)
3047 summaryremotehooks = util.hooks()
3046 summaryremotehooks = util.hooks()
3048
3047
3049 # A list of state files kept by multistep operations like graft.
3048 # A list of state files kept by multistep operations like graft.
3050 # Since graft cannot be aborted, it is considered 'clearable' by update.
3049 # Since graft cannot be aborted, it is considered 'clearable' by update.
3051 # note: bisect is intentionally excluded
3050 # note: bisect is intentionally excluded
3052 # (state file, clearable, allowcommit, error, hint)
3051 # (state file, clearable, allowcommit, error, hint)
3053 unfinishedstates = [
3052 unfinishedstates = [
3054 ('graftstate', True, False, _('graft in progress'),
3053 ('graftstate', True, False, _('graft in progress'),
3055 _("use 'hg graft --continue' or 'hg update' to abort")),
3054 _("use 'hg graft --continue' or 'hg update' to abort")),
3056 ('updatestate', True, False, _('last update was interrupted'),
3055 ('updatestate', True, False, _('last update was interrupted'),
3057 _("use 'hg update' to get a consistent checkout"))
3056 _("use 'hg update' to get a consistent checkout"))
3058 ]
3057 ]
3059
3058
3060 def checkunfinished(repo, commit=False):
3059 def checkunfinished(repo, commit=False):
3061 '''Look for an unfinished multistep operation, like graft, and abort
3060 '''Look for an unfinished multistep operation, like graft, and abort
3062 if found. It's probably good to check this right before
3061 if found. It's probably good to check this right before
3063 bailifchanged().
3062 bailifchanged().
3064 '''
3063 '''
3065 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3064 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3066 if commit and allowcommit:
3065 if commit and allowcommit:
3067 continue
3066 continue
3068 if repo.vfs.exists(f):
3067 if repo.vfs.exists(f):
3069 raise error.Abort(msg, hint=hint)
3068 raise error.Abort(msg, hint=hint)
3070
3069
3071 def clearunfinished(repo):
3070 def clearunfinished(repo):
3072 '''Check for unfinished operations (as above), and clear the ones
3071 '''Check for unfinished operations (as above), and clear the ones
3073 that are clearable.
3072 that are clearable.
3074 '''
3073 '''
3075 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3074 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3076 if not clearable and repo.vfs.exists(f):
3075 if not clearable and repo.vfs.exists(f):
3077 raise error.Abort(msg, hint=hint)
3076 raise error.Abort(msg, hint=hint)
3078 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3077 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3079 if clearable and repo.vfs.exists(f):
3078 if clearable and repo.vfs.exists(f):
3080 util.unlink(repo.vfs.join(f))
3079 util.unlink(repo.vfs.join(f))
3081
3080
3082 afterresolvedstates = [
3081 afterresolvedstates = [
3083 ('graftstate',
3082 ('graftstate',
3084 _('hg graft --continue')),
3083 _('hg graft --continue')),
3085 ]
3084 ]
3086
3085
3087 def howtocontinue(repo):
3086 def howtocontinue(repo):
3088 '''Check for an unfinished operation and return the command to finish
3087 '''Check for an unfinished operation and return the command to finish
3089 it.
3088 it.
3090
3089
3091 afterresolvedstates tuples define a .hg/{file} and the corresponding
3090 afterresolvedstates tuples define a .hg/{file} and the corresponding
3092 command needed to finish it.
3091 command needed to finish it.
3093
3092
3094 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3093 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3095 a boolean.
3094 a boolean.
3096 '''
3095 '''
3097 contmsg = _("continue: %s")
3096 contmsg = _("continue: %s")
3098 for f, msg in afterresolvedstates:
3097 for f, msg in afterresolvedstates:
3099 if repo.vfs.exists(f):
3098 if repo.vfs.exists(f):
3100 return contmsg % msg, True
3099 return contmsg % msg, True
3101 if repo[None].dirty(missing=True, merge=False, branch=False):
3100 if repo[None].dirty(missing=True, merge=False, branch=False):
3102 return contmsg % _("hg commit"), False
3101 return contmsg % _("hg commit"), False
3103 return None, None
3102 return None, None
3104
3103
3105 def checkafterresolved(repo):
3104 def checkafterresolved(repo):
3106 '''Inform the user about the next action after completing hg resolve
3105 '''Inform the user about the next action after completing hg resolve
3107
3106
3108 If there's a matching afterresolvedstates, howtocontinue will yield
3107 If there's a matching afterresolvedstates, howtocontinue will yield
3109 repo.ui.warn as the reporter.
3108 repo.ui.warn as the reporter.
3110
3109
3111 Otherwise, it will yield repo.ui.note.
3110 Otherwise, it will yield repo.ui.note.
3112 '''
3111 '''
3113 msg, warning = howtocontinue(repo)
3112 msg, warning = howtocontinue(repo)
3114 if msg is not None:
3113 if msg is not None:
3115 if warning:
3114 if warning:
3116 repo.ui.warn("%s\n" % msg)
3115 repo.ui.warn("%s\n" % msg)
3117 else:
3116 else:
3118 repo.ui.note("%s\n" % msg)
3117 repo.ui.note("%s\n" % msg)
3119
3118
3120 def wrongtooltocontinue(repo, task):
3119 def wrongtooltocontinue(repo, task):
3121 '''Raise an abort suggesting how to properly continue if there is an
3120 '''Raise an abort suggesting how to properly continue if there is an
3122 active task.
3121 active task.
3123
3122
3124 Uses howtocontinue() to find the active task.
3123 Uses howtocontinue() to find the active task.
3125
3124
3126 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3125 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3127 a hint.
3126 a hint.
3128 '''
3127 '''
3129 after = howtocontinue(repo)
3128 after = howtocontinue(repo)
3130 hint = None
3129 hint = None
3131 if after[1]:
3130 if after[1]:
3132 hint = after[0]
3131 hint = after[0]
3133 raise error.Abort(_('no %s in progress') % task, hint=hint)
3132 raise error.Abort(_('no %s in progress') % task, hint=hint)
3134
3133
3135 class changeset_printer(logcmdutil.changesetprinter):
3134 class changeset_printer(logcmdutil.changesetprinter):
3136
3135
3137 def __init__(self, ui, *args, **kwargs):
3136 def __init__(self, ui, *args, **kwargs):
3138 msg = ("'cmdutil.changeset_printer' is deprecated, "
3137 msg = ("'cmdutil.changeset_printer' is deprecated, "
3139 "use 'logcmdutil.logcmdutil'")
3138 "use 'logcmdutil.logcmdutil'")
3140 ui.deprecwarn(msg, "4.6")
3139 ui.deprecwarn(msg, "4.6")
3141 super(changeset_printer, self).__init__(ui, *args, **kwargs)
3140 super(changeset_printer, self).__init__(ui, *args, **kwargs)
3142
3141
3143 def displaygraph(ui, *args, **kwargs):
3142 def displaygraph(ui, *args, **kwargs):
3144 msg = ("'cmdutil.displaygraph' is deprecated, "
3143 msg = ("'cmdutil.displaygraph' is deprecated, "
3145 "use 'logcmdutil.displaygraph'")
3144 "use 'logcmdutil.displaygraph'")
3146 ui.deprecwarn(msg, "4.6")
3145 ui.deprecwarn(msg, "4.6")
3147 return logcmdutil.displaygraph(ui, *args, **kwargs)
3146 return logcmdutil.displaygraph(ui, *args, **kwargs)
3148
3147
3149 def show_changeset(ui, *args, **kwargs):
3148 def show_changeset(ui, *args, **kwargs):
3150 msg = ("'cmdutil.show_changeset' is deprecated, "
3149 msg = ("'cmdutil.show_changeset' is deprecated, "
3151 "use 'logcmdutil.changesetdisplayer'")
3150 "use 'logcmdutil.changesetdisplayer'")
3152 ui.deprecwarn(msg, "4.6")
3151 ui.deprecwarn(msg, "4.6")
3153 return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
3152 return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
General Comments 0
You need to be logged in to leave comments. Login now