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