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