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