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