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