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