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