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