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