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