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