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