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