##// END OF EJS Templates
cmdutil: fix amend when passing a date...
Boris Feld -
r34123:ae95853c default
parent child Browse files
Show More
@@ -1,3885 +1,3888 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 itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14 import tempfile
14 import tempfile
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23
23
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 changelog,
26 changelog,
27 copies,
27 copies,
28 crecord as crecordmod,
28 crecord as crecordmod,
29 dirstateguard,
29 dirstateguard,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 graphmod,
33 graphmod,
34 match as matchmod,
34 match as matchmod,
35 obsolete,
35 obsolete,
36 patch,
36 patch,
37 pathutil,
37 pathutil,
38 pycompat,
38 pycompat,
39 registrar,
39 registrar,
40 revlog,
40 revlog,
41 revset,
41 revset,
42 scmutil,
42 scmutil,
43 smartset,
43 smartset,
44 templatekw,
44 templatekw,
45 templater,
45 templater,
46 util,
46 util,
47 vfs as vfsmod,
47 vfs as vfsmod,
48 )
48 )
49 stringio = util.stringio
49 stringio = util.stringio
50
50
51 # templates of common command options
51 # templates of common command options
52
52
53 dryrunopts = [
53 dryrunopts = [
54 ('n', 'dry-run', None,
54 ('n', 'dry-run', None,
55 _('do not perform actions, just print output')),
55 _('do not perform actions, just print output')),
56 ]
56 ]
57
57
58 remoteopts = [
58 remoteopts = [
59 ('e', 'ssh', '',
59 ('e', 'ssh', '',
60 _('specify ssh command to use'), _('CMD')),
60 _('specify ssh command to use'), _('CMD')),
61 ('', 'remotecmd', '',
61 ('', 'remotecmd', '',
62 _('specify hg command to run on the remote side'), _('CMD')),
62 _('specify hg command to run on the remote side'), _('CMD')),
63 ('', 'insecure', None,
63 ('', 'insecure', None,
64 _('do not verify server certificate (ignoring web.cacerts config)')),
64 _('do not verify server certificate (ignoring web.cacerts config)')),
65 ]
65 ]
66
66
67 walkopts = [
67 walkopts = [
68 ('I', 'include', [],
68 ('I', 'include', [],
69 _('include names matching the given patterns'), _('PATTERN')),
69 _('include names matching the given patterns'), _('PATTERN')),
70 ('X', 'exclude', [],
70 ('X', 'exclude', [],
71 _('exclude names matching the given patterns'), _('PATTERN')),
71 _('exclude names matching the given patterns'), _('PATTERN')),
72 ]
72 ]
73
73
74 commitopts = [
74 commitopts = [
75 ('m', 'message', '',
75 ('m', 'message', '',
76 _('use text as commit message'), _('TEXT')),
76 _('use text as commit message'), _('TEXT')),
77 ('l', 'logfile', '',
77 ('l', 'logfile', '',
78 _('read commit message from file'), _('FILE')),
78 _('read commit message from file'), _('FILE')),
79 ]
79 ]
80
80
81 commitopts2 = [
81 commitopts2 = [
82 ('d', 'date', '',
82 ('d', 'date', '',
83 _('record the specified date as commit date'), _('DATE')),
83 _('record the specified date as commit date'), _('DATE')),
84 ('u', 'user', '',
84 ('u', 'user', '',
85 _('record the specified user as committer'), _('USER')),
85 _('record the specified user as committer'), _('USER')),
86 ]
86 ]
87
87
88 # hidden for now
88 # hidden for now
89 formatteropts = [
89 formatteropts = [
90 ('T', 'template', '',
90 ('T', 'template', '',
91 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
91 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
92 ]
92 ]
93
93
94 templateopts = [
94 templateopts = [
95 ('', 'style', '',
95 ('', 'style', '',
96 _('display using template map file (DEPRECATED)'), _('STYLE')),
96 _('display using template map file (DEPRECATED)'), _('STYLE')),
97 ('T', 'template', '',
97 ('T', 'template', '',
98 _('display with template'), _('TEMPLATE')),
98 _('display with template'), _('TEMPLATE')),
99 ]
99 ]
100
100
101 logopts = [
101 logopts = [
102 ('p', 'patch', None, _('show patch')),
102 ('p', 'patch', None, _('show patch')),
103 ('g', 'git', None, _('use git extended diff format')),
103 ('g', 'git', None, _('use git extended diff format')),
104 ('l', 'limit', '',
104 ('l', 'limit', '',
105 _('limit number of changes displayed'), _('NUM')),
105 _('limit number of changes displayed'), _('NUM')),
106 ('M', 'no-merges', None, _('do not show merges')),
106 ('M', 'no-merges', None, _('do not show merges')),
107 ('', 'stat', None, _('output diffstat-style summary of changes')),
107 ('', 'stat', None, _('output diffstat-style summary of changes')),
108 ('G', 'graph', None, _("show the revision DAG")),
108 ('G', 'graph', None, _("show the revision DAG")),
109 ] + templateopts
109 ] + templateopts
110
110
111 diffopts = [
111 diffopts = [
112 ('a', 'text', None, _('treat all files as text')),
112 ('a', 'text', None, _('treat all files as text')),
113 ('g', 'git', None, _('use git extended diff format')),
113 ('g', 'git', None, _('use git extended diff format')),
114 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
114 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
115 ('', 'nodates', None, _('omit dates from diff headers'))
115 ('', 'nodates', None, _('omit dates from diff headers'))
116 ]
116 ]
117
117
118 diffwsopts = [
118 diffwsopts = [
119 ('w', 'ignore-all-space', None,
119 ('w', 'ignore-all-space', None,
120 _('ignore white space when comparing lines')),
120 _('ignore white space when comparing lines')),
121 ('b', 'ignore-space-change', None,
121 ('b', 'ignore-space-change', None,
122 _('ignore changes in the amount of white space')),
122 _('ignore changes in the amount of white space')),
123 ('B', 'ignore-blank-lines', None,
123 ('B', 'ignore-blank-lines', None,
124 _('ignore changes whose lines are all blank')),
124 _('ignore changes whose lines are all blank')),
125 ('Z', 'ignore-space-at-eol', None,
125 ('Z', 'ignore-space-at-eol', None,
126 _('ignore changes in whitespace at EOL')),
126 _('ignore changes in whitespace at EOL')),
127 ]
127 ]
128
128
129 diffopts2 = [
129 diffopts2 = [
130 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
130 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
131 ('p', 'show-function', None, _('show which function each change is in')),
131 ('p', 'show-function', None, _('show which function each change is in')),
132 ('', 'reverse', None, _('produce a diff that undoes the changes')),
132 ('', 'reverse', None, _('produce a diff that undoes the changes')),
133 ] + diffwsopts + [
133 ] + diffwsopts + [
134 ('U', 'unified', '',
134 ('U', 'unified', '',
135 _('number of lines of context to show'), _('NUM')),
135 _('number of lines of context to show'), _('NUM')),
136 ('', 'stat', None, _('output diffstat-style summary of changes')),
136 ('', 'stat', None, _('output diffstat-style summary of changes')),
137 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
137 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
138 ]
138 ]
139
139
140 mergetoolopts = [
140 mergetoolopts = [
141 ('t', 'tool', '', _('specify merge tool')),
141 ('t', 'tool', '', _('specify merge tool')),
142 ]
142 ]
143
143
144 similarityopts = [
144 similarityopts = [
145 ('s', 'similarity', '',
145 ('s', 'similarity', '',
146 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
146 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
147 ]
147 ]
148
148
149 subrepoopts = [
149 subrepoopts = [
150 ('S', 'subrepos', None,
150 ('S', 'subrepos', None,
151 _('recurse into subrepositories'))
151 _('recurse into subrepositories'))
152 ]
152 ]
153
153
154 debugrevlogopts = [
154 debugrevlogopts = [
155 ('c', 'changelog', False, _('open changelog')),
155 ('c', 'changelog', False, _('open changelog')),
156 ('m', 'manifest', False, _('open manifest')),
156 ('m', 'manifest', False, _('open manifest')),
157 ('', 'dir', '', _('open directory manifest')),
157 ('', 'dir', '', _('open directory manifest')),
158 ]
158 ]
159
159
160 # special string such that everything below this line will be ingored in the
160 # special string such that everything below this line will be ingored in the
161 # editor text
161 # editor text
162 _linebelow = "^HG: ------------------------ >8 ------------------------$"
162 _linebelow = "^HG: ------------------------ >8 ------------------------$"
163
163
164 def ishunk(x):
164 def ishunk(x):
165 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
165 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
166 return isinstance(x, hunkclasses)
166 return isinstance(x, hunkclasses)
167
167
168 def newandmodified(chunks, originalchunks):
168 def newandmodified(chunks, originalchunks):
169 newlyaddedandmodifiedfiles = set()
169 newlyaddedandmodifiedfiles = set()
170 for chunk in chunks:
170 for chunk in chunks:
171 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
171 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
172 originalchunks:
172 originalchunks:
173 newlyaddedandmodifiedfiles.add(chunk.header.filename())
173 newlyaddedandmodifiedfiles.add(chunk.header.filename())
174 return newlyaddedandmodifiedfiles
174 return newlyaddedandmodifiedfiles
175
175
176 def parsealiases(cmd):
176 def parsealiases(cmd):
177 return cmd.lstrip("^").split("|")
177 return cmd.lstrip("^").split("|")
178
178
179 def setupwrapcolorwrite(ui):
179 def setupwrapcolorwrite(ui):
180 # wrap ui.write so diff output can be labeled/colorized
180 # wrap ui.write so diff output can be labeled/colorized
181 def wrapwrite(orig, *args, **kw):
181 def wrapwrite(orig, *args, **kw):
182 label = kw.pop('label', '')
182 label = kw.pop('label', '')
183 for chunk, l in patch.difflabel(lambda: args):
183 for chunk, l in patch.difflabel(lambda: args):
184 orig(chunk, label=label + l)
184 orig(chunk, label=label + l)
185
185
186 oldwrite = ui.write
186 oldwrite = ui.write
187 def wrap(*args, **kwargs):
187 def wrap(*args, **kwargs):
188 return wrapwrite(oldwrite, *args, **kwargs)
188 return wrapwrite(oldwrite, *args, **kwargs)
189 setattr(ui, 'write', wrap)
189 setattr(ui, 'write', wrap)
190 return oldwrite
190 return oldwrite
191
191
192 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
192 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
193 if usecurses:
193 if usecurses:
194 if testfile:
194 if testfile:
195 recordfn = crecordmod.testdecorator(testfile,
195 recordfn = crecordmod.testdecorator(testfile,
196 crecordmod.testchunkselector)
196 crecordmod.testchunkselector)
197 else:
197 else:
198 recordfn = crecordmod.chunkselector
198 recordfn = crecordmod.chunkselector
199
199
200 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
200 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
201
201
202 else:
202 else:
203 return patch.filterpatch(ui, originalhunks, operation)
203 return patch.filterpatch(ui, originalhunks, operation)
204
204
205 def recordfilter(ui, originalhunks, operation=None):
205 def recordfilter(ui, originalhunks, operation=None):
206 """ Prompts the user to filter the originalhunks and return a list of
206 """ Prompts the user to filter the originalhunks and return a list of
207 selected hunks.
207 selected hunks.
208 *operation* is used for to build ui messages to indicate the user what
208 *operation* is used for to build ui messages to indicate the user what
209 kind of filtering they are doing: reverting, committing, shelving, etc.
209 kind of filtering they are doing: reverting, committing, shelving, etc.
210 (see patch.filterpatch).
210 (see patch.filterpatch).
211 """
211 """
212 usecurses = crecordmod.checkcurses(ui)
212 usecurses = crecordmod.checkcurses(ui)
213 testfile = ui.config('experimental', 'crecordtest')
213 testfile = ui.config('experimental', 'crecordtest')
214 oldwrite = setupwrapcolorwrite(ui)
214 oldwrite = setupwrapcolorwrite(ui)
215 try:
215 try:
216 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
216 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
217 testfile, operation)
217 testfile, operation)
218 finally:
218 finally:
219 ui.write = oldwrite
219 ui.write = oldwrite
220 return newchunks, newopts
220 return newchunks, newopts
221
221
222 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
222 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
223 filterfn, *pats, **opts):
223 filterfn, *pats, **opts):
224 from . import merge as mergemod
224 from . import merge as mergemod
225 opts = pycompat.byteskwargs(opts)
225 opts = pycompat.byteskwargs(opts)
226 if not ui.interactive():
226 if not ui.interactive():
227 if cmdsuggest:
227 if cmdsuggest:
228 msg = _('running non-interactively, use %s instead') % cmdsuggest
228 msg = _('running non-interactively, use %s instead') % cmdsuggest
229 else:
229 else:
230 msg = _('running non-interactively')
230 msg = _('running non-interactively')
231 raise error.Abort(msg)
231 raise error.Abort(msg)
232
232
233 # make sure username is set before going interactive
233 # make sure username is set before going interactive
234 if not opts.get('user'):
234 if not opts.get('user'):
235 ui.username() # raise exception, username not provided
235 ui.username() # raise exception, username not provided
236
236
237 def recordfunc(ui, repo, message, match, opts):
237 def recordfunc(ui, repo, message, match, opts):
238 """This is generic record driver.
238 """This is generic record driver.
239
239
240 Its job is to interactively filter local changes, and
240 Its job is to interactively filter local changes, and
241 accordingly prepare working directory into a state in which the
241 accordingly prepare working directory into a state in which the
242 job can be delegated to a non-interactive commit command such as
242 job can be delegated to a non-interactive commit command such as
243 'commit' or 'qrefresh'.
243 'commit' or 'qrefresh'.
244
244
245 After the actual job is done by non-interactive command, the
245 After the actual job is done by non-interactive command, the
246 working directory is restored to its original state.
246 working directory is restored to its original state.
247
247
248 In the end we'll record interesting changes, and everything else
248 In the end we'll record interesting changes, and everything else
249 will be left in place, so the user can continue working.
249 will be left in place, so the user can continue working.
250 """
250 """
251
251
252 checkunfinished(repo, commit=True)
252 checkunfinished(repo, commit=True)
253 wctx = repo[None]
253 wctx = repo[None]
254 merge = len(wctx.parents()) > 1
254 merge = len(wctx.parents()) > 1
255 if merge:
255 if merge:
256 raise error.Abort(_('cannot partially commit a merge '
256 raise error.Abort(_('cannot partially commit a merge '
257 '(use "hg commit" instead)'))
257 '(use "hg commit" instead)'))
258
258
259 def fail(f, msg):
259 def fail(f, msg):
260 raise error.Abort('%s: %s' % (f, msg))
260 raise error.Abort('%s: %s' % (f, msg))
261
261
262 force = opts.get('force')
262 force = opts.get('force')
263 if not force:
263 if not force:
264 vdirs = []
264 vdirs = []
265 match.explicitdir = vdirs.append
265 match.explicitdir = vdirs.append
266 match.bad = fail
266 match.bad = fail
267
267
268 status = repo.status(match=match)
268 status = repo.status(match=match)
269 if not force:
269 if not force:
270 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
270 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
271 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
271 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
272 diffopts.nodates = True
272 diffopts.nodates = True
273 diffopts.git = True
273 diffopts.git = True
274 diffopts.showfunc = True
274 diffopts.showfunc = True
275 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
275 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
276 originalchunks = patch.parsepatch(originaldiff)
276 originalchunks = patch.parsepatch(originaldiff)
277
277
278 # 1. filter patch, since we are intending to apply subset of it
278 # 1. filter patch, since we are intending to apply subset of it
279 try:
279 try:
280 chunks, newopts = filterfn(ui, originalchunks)
280 chunks, newopts = filterfn(ui, originalchunks)
281 except patch.PatchError as err:
281 except patch.PatchError as err:
282 raise error.Abort(_('error parsing patch: %s') % err)
282 raise error.Abort(_('error parsing patch: %s') % err)
283 opts.update(newopts)
283 opts.update(newopts)
284
284
285 # We need to keep a backup of files that have been newly added and
285 # We need to keep a backup of files that have been newly added and
286 # modified during the recording process because there is a previous
286 # modified during the recording process because there is a previous
287 # version without the edit in the workdir
287 # version without the edit in the workdir
288 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
288 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
289 contenders = set()
289 contenders = set()
290 for h in chunks:
290 for h in chunks:
291 try:
291 try:
292 contenders.update(set(h.files()))
292 contenders.update(set(h.files()))
293 except AttributeError:
293 except AttributeError:
294 pass
294 pass
295
295
296 changed = status.modified + status.added + status.removed
296 changed = status.modified + status.added + status.removed
297 newfiles = [f for f in changed if f in contenders]
297 newfiles = [f for f in changed if f in contenders]
298 if not newfiles:
298 if not newfiles:
299 ui.status(_('no changes to record\n'))
299 ui.status(_('no changes to record\n'))
300 return 0
300 return 0
301
301
302 modified = set(status.modified)
302 modified = set(status.modified)
303
303
304 # 2. backup changed files, so we can restore them in the end
304 # 2. backup changed files, so we can restore them in the end
305
305
306 if backupall:
306 if backupall:
307 tobackup = changed
307 tobackup = changed
308 else:
308 else:
309 tobackup = [f for f in newfiles if f in modified or f in \
309 tobackup = [f for f in newfiles if f in modified or f in \
310 newlyaddedandmodifiedfiles]
310 newlyaddedandmodifiedfiles]
311 backups = {}
311 backups = {}
312 if tobackup:
312 if tobackup:
313 backupdir = repo.vfs.join('record-backups')
313 backupdir = repo.vfs.join('record-backups')
314 try:
314 try:
315 os.mkdir(backupdir)
315 os.mkdir(backupdir)
316 except OSError as err:
316 except OSError as err:
317 if err.errno != errno.EEXIST:
317 if err.errno != errno.EEXIST:
318 raise
318 raise
319 try:
319 try:
320 # backup continues
320 # backup continues
321 for f in tobackup:
321 for f in tobackup:
322 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
322 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
323 dir=backupdir)
323 dir=backupdir)
324 os.close(fd)
324 os.close(fd)
325 ui.debug('backup %r as %r\n' % (f, tmpname))
325 ui.debug('backup %r as %r\n' % (f, tmpname))
326 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
326 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
327 backups[f] = tmpname
327 backups[f] = tmpname
328
328
329 fp = stringio()
329 fp = stringio()
330 for c in chunks:
330 for c in chunks:
331 fname = c.filename()
331 fname = c.filename()
332 if fname in backups:
332 if fname in backups:
333 c.write(fp)
333 c.write(fp)
334 dopatch = fp.tell()
334 dopatch = fp.tell()
335 fp.seek(0)
335 fp.seek(0)
336
336
337 # 2.5 optionally review / modify patch in text editor
337 # 2.5 optionally review / modify patch in text editor
338 if opts.get('review', False):
338 if opts.get('review', False):
339 patchtext = (crecordmod.diffhelptext
339 patchtext = (crecordmod.diffhelptext
340 + crecordmod.patchhelptext
340 + crecordmod.patchhelptext
341 + fp.read())
341 + fp.read())
342 reviewedpatch = ui.edit(patchtext, "",
342 reviewedpatch = ui.edit(patchtext, "",
343 action="diff",
343 action="diff",
344 repopath=repo.path)
344 repopath=repo.path)
345 fp.truncate(0)
345 fp.truncate(0)
346 fp.write(reviewedpatch)
346 fp.write(reviewedpatch)
347 fp.seek(0)
347 fp.seek(0)
348
348
349 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
349 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
350 # 3a. apply filtered patch to clean repo (clean)
350 # 3a. apply filtered patch to clean repo (clean)
351 if backups:
351 if backups:
352 # Equivalent to hg.revert
352 # Equivalent to hg.revert
353 m = scmutil.matchfiles(repo, backups.keys())
353 m = scmutil.matchfiles(repo, backups.keys())
354 mergemod.update(repo, repo.dirstate.p1(),
354 mergemod.update(repo, repo.dirstate.p1(),
355 False, True, matcher=m)
355 False, True, matcher=m)
356
356
357 # 3b. (apply)
357 # 3b. (apply)
358 if dopatch:
358 if dopatch:
359 try:
359 try:
360 ui.debug('applying patch\n')
360 ui.debug('applying patch\n')
361 ui.debug(fp.getvalue())
361 ui.debug(fp.getvalue())
362 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
362 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
363 except patch.PatchError as err:
363 except patch.PatchError as err:
364 raise error.Abort(str(err))
364 raise error.Abort(str(err))
365 del fp
365 del fp
366
366
367 # 4. We prepared working directory according to filtered
367 # 4. We prepared working directory according to filtered
368 # patch. Now is the time to delegate the job to
368 # patch. Now is the time to delegate the job to
369 # commit/qrefresh or the like!
369 # commit/qrefresh or the like!
370
370
371 # Make all of the pathnames absolute.
371 # Make all of the pathnames absolute.
372 newfiles = [repo.wjoin(nf) for nf in newfiles]
372 newfiles = [repo.wjoin(nf) for nf in newfiles]
373 return commitfunc(ui, repo, *newfiles, **opts)
373 return commitfunc(ui, repo, *newfiles, **opts)
374 finally:
374 finally:
375 # 5. finally restore backed-up files
375 # 5. finally restore backed-up files
376 try:
376 try:
377 dirstate = repo.dirstate
377 dirstate = repo.dirstate
378 for realname, tmpname in backups.iteritems():
378 for realname, tmpname in backups.iteritems():
379 ui.debug('restoring %r to %r\n' % (tmpname, realname))
379 ui.debug('restoring %r to %r\n' % (tmpname, realname))
380
380
381 if dirstate[realname] == 'n':
381 if dirstate[realname] == 'n':
382 # without normallookup, restoring timestamp
382 # without normallookup, restoring timestamp
383 # may cause partially committed files
383 # may cause partially committed files
384 # to be treated as unmodified
384 # to be treated as unmodified
385 dirstate.normallookup(realname)
385 dirstate.normallookup(realname)
386
386
387 # copystat=True here and above are a hack to trick any
387 # copystat=True here and above are a hack to trick any
388 # editors that have f open that we haven't modified them.
388 # editors that have f open that we haven't modified them.
389 #
389 #
390 # Also note that this racy as an editor could notice the
390 # Also note that this racy as an editor could notice the
391 # file's mtime before we've finished writing it.
391 # file's mtime before we've finished writing it.
392 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
392 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
393 os.unlink(tmpname)
393 os.unlink(tmpname)
394 if tobackup:
394 if tobackup:
395 os.rmdir(backupdir)
395 os.rmdir(backupdir)
396 except OSError:
396 except OSError:
397 pass
397 pass
398
398
399 def recordinwlock(ui, repo, message, match, opts):
399 def recordinwlock(ui, repo, message, match, opts):
400 with repo.wlock():
400 with repo.wlock():
401 return recordfunc(ui, repo, message, match, opts)
401 return recordfunc(ui, repo, message, match, opts)
402
402
403 return commit(ui, repo, recordinwlock, pats, opts)
403 return commit(ui, repo, recordinwlock, pats, opts)
404
404
405 def tersestatus(root, statlist, status, ignorefn, ignore):
405 def tersestatus(root, statlist, status, ignorefn, ignore):
406 """
406 """
407 Returns a list of statuses with directory collapsed if all the files in the
407 Returns a list of statuses with directory collapsed if all the files in the
408 directory has the same status.
408 directory has the same status.
409 """
409 """
410
410
411 def numfiles(dirname):
411 def numfiles(dirname):
412 """
412 """
413 Calculates the number of tracked files in a given directory which also
413 Calculates the number of tracked files in a given directory which also
414 includes files which were removed or deleted. Considers ignored files
414 includes files which were removed or deleted. Considers ignored files
415 if ignore argument is True or 'i' is present in status argument.
415 if ignore argument is True or 'i' is present in status argument.
416 """
416 """
417 if lencache.get(dirname):
417 if lencache.get(dirname):
418 return lencache[dirname]
418 return lencache[dirname]
419 if 'i' in status or ignore:
419 if 'i' in status or ignore:
420 def match(localpath):
420 def match(localpath):
421 absolutepath = os.path.join(root, localpath)
421 absolutepath = os.path.join(root, localpath)
422 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
422 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
423 return True
423 return True
424 return False
424 return False
425 else:
425 else:
426 def match(localpath):
426 def match(localpath):
427 # there can be directory whose all the files are ignored and
427 # there can be directory whose all the files are ignored and
428 # hence the drectory should also be ignored while counting
428 # hence the drectory should also be ignored while counting
429 # number of files or subdirs in it's parent directory. This
429 # number of files or subdirs in it's parent directory. This
430 # checks the same.
430 # checks the same.
431 # XXX: We need a better logic here.
431 # XXX: We need a better logic here.
432 if os.path.isdir(os.path.join(root, localpath)):
432 if os.path.isdir(os.path.join(root, localpath)):
433 return isignoreddir(localpath)
433 return isignoreddir(localpath)
434 else:
434 else:
435 # XXX: there can be files which have the ignored pattern but
435 # XXX: there can be files which have the ignored pattern but
436 # are not ignored. That leads to bug in counting number of
436 # are not ignored. That leads to bug in counting number of
437 # tracked files in the directory.
437 # tracked files in the directory.
438 return ignorefn(localpath)
438 return ignorefn(localpath)
439 lendir = 0
439 lendir = 0
440 abspath = os.path.join(root, dirname)
440 abspath = os.path.join(root, dirname)
441 # There might be cases when a directory does not exists as the whole
441 # There might be cases when a directory does not exists as the whole
442 # directory can be removed and/or deleted.
442 # directory can be removed and/or deleted.
443 try:
443 try:
444 for f in os.listdir(abspath):
444 for f in os.listdir(abspath):
445 localpath = os.path.join(dirname, f)
445 localpath = os.path.join(dirname, f)
446 if not match(localpath):
446 if not match(localpath):
447 lendir += 1
447 lendir += 1
448 except OSError:
448 except OSError:
449 pass
449 pass
450 lendir += len(absentdir.get(dirname, []))
450 lendir += len(absentdir.get(dirname, []))
451 lencache[dirname] = lendir
451 lencache[dirname] = lendir
452 return lendir
452 return lendir
453
453
454 def isemptydir(abspath):
454 def isemptydir(abspath):
455 """
455 """
456 Check whether a directory is empty or not, i.e. there is no files in the
456 Check whether a directory is empty or not, i.e. there is no files in the
457 directory and all its subdirectories.
457 directory and all its subdirectories.
458 """
458 """
459 for f in os.listdir(abspath):
459 for f in os.listdir(abspath):
460 fullpath = os.path.join(abspath, f)
460 fullpath = os.path.join(abspath, f)
461 if os.path.isdir(fullpath):
461 if os.path.isdir(fullpath):
462 # recursion here
462 # recursion here
463 ret = isemptydir(fullpath)
463 ret = isemptydir(fullpath)
464 if not ret:
464 if not ret:
465 return False
465 return False
466 else:
466 else:
467 return False
467 return False
468 return True
468 return True
469
469
470 def isignoreddir(localpath):
470 def isignoreddir(localpath):
471 """Return True if `localpath` directory is ignored or contains only
471 """Return True if `localpath` directory is ignored or contains only
472 ignored files and should hence be considered ignored.
472 ignored files and should hence be considered ignored.
473 """
473 """
474 dirpath = os.path.join(root, localpath)
474 dirpath = os.path.join(root, localpath)
475 if ignorefn(dirpath):
475 if ignorefn(dirpath):
476 return True
476 return True
477 for f in os.listdir(dirpath):
477 for f in os.listdir(dirpath):
478 filepath = os.path.join(dirpath, f)
478 filepath = os.path.join(dirpath, f)
479 if os.path.isdir(filepath):
479 if os.path.isdir(filepath):
480 # recursion here
480 # recursion here
481 ret = isignoreddir(os.path.join(localpath, f))
481 ret = isignoreddir(os.path.join(localpath, f))
482 if not ret:
482 if not ret:
483 return False
483 return False
484 else:
484 else:
485 if not ignorefn(os.path.join(localpath, f)):
485 if not ignorefn(os.path.join(localpath, f)):
486 return False
486 return False
487 return True
487 return True
488
488
489 def absentones(removedfiles, missingfiles):
489 def absentones(removedfiles, missingfiles):
490 """
490 """
491 Returns a dictionary of directories with files in it which are either
491 Returns a dictionary of directories with files in it which are either
492 removed or missing (deleted) in them.
492 removed or missing (deleted) in them.
493 """
493 """
494 absentdir = {}
494 absentdir = {}
495 absentfiles = removedfiles + missingfiles
495 absentfiles = removedfiles + missingfiles
496 while absentfiles:
496 while absentfiles:
497 f = absentfiles.pop()
497 f = absentfiles.pop()
498 par = os.path.dirname(f)
498 par = os.path.dirname(f)
499 if par == '':
499 if par == '':
500 continue
500 continue
501 # we need to store files rather than number of files as some files
501 # we need to store files rather than number of files as some files
502 # or subdirectories in a directory can be counted twice. This is
502 # or subdirectories in a directory can be counted twice. This is
503 # also we have used sets here.
503 # also we have used sets here.
504 try:
504 try:
505 absentdir[par].add(f)
505 absentdir[par].add(f)
506 except KeyError:
506 except KeyError:
507 absentdir[par] = set([f])
507 absentdir[par] = set([f])
508 absentfiles.append(par)
508 absentfiles.append(par)
509 return absentdir
509 return absentdir
510
510
511 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
511 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
512 # get a dictonary of directories and files which are missing as os.listdir()
512 # get a dictonary of directories and files which are missing as os.listdir()
513 # won't be able to list them.
513 # won't be able to list them.
514 absentdir = absentones(statlist[2], statlist[3])
514 absentdir = absentones(statlist[2], statlist[3])
515 finalrs = [[]] * len(indexes)
515 finalrs = [[]] * len(indexes)
516 didsomethingchanged = False
516 didsomethingchanged = False
517 # dictionary to store number of files and subdir in a directory so that we
517 # dictionary to store number of files and subdir in a directory so that we
518 # don't compute that again.
518 # don't compute that again.
519 lencache = {}
519 lencache = {}
520
520
521 for st in pycompat.bytestr(status):
521 for st in pycompat.bytestr(status):
522
522
523 try:
523 try:
524 ind = indexes[st]
524 ind = indexes[st]
525 except KeyError:
525 except KeyError:
526 # TODO: Need a better error message here
526 # TODO: Need a better error message here
527 raise error.Abort("'%s' not recognized" % st)
527 raise error.Abort("'%s' not recognized" % st)
528
528
529 sfiles = statlist[ind]
529 sfiles = statlist[ind]
530 if not sfiles:
530 if not sfiles:
531 continue
531 continue
532 pardict = {}
532 pardict = {}
533 for a in sfiles:
533 for a in sfiles:
534 par = os.path.dirname(a)
534 par = os.path.dirname(a)
535 pardict.setdefault(par, []).append(a)
535 pardict.setdefault(par, []).append(a)
536
536
537 rs = []
537 rs = []
538 newls = []
538 newls = []
539 for par, files in pardict.iteritems():
539 for par, files in pardict.iteritems():
540 lenpar = numfiles(par)
540 lenpar = numfiles(par)
541 if lenpar == len(files):
541 if lenpar == len(files):
542 newls.append(par)
542 newls.append(par)
543
543
544 if not newls:
544 if not newls:
545 continue
545 continue
546
546
547 while newls:
547 while newls:
548 newel = newls.pop()
548 newel = newls.pop()
549 if newel == '':
549 if newel == '':
550 continue
550 continue
551 parn = os.path.dirname(newel)
551 parn = os.path.dirname(newel)
552 pardict[newel] = []
552 pardict[newel] = []
553 # Adding pycompat.ossep as newel is a directory.
553 # Adding pycompat.ossep as newel is a directory.
554 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
554 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
555 lenpar = numfiles(parn)
555 lenpar = numfiles(parn)
556 if lenpar == len(pardict[parn]):
556 if lenpar == len(pardict[parn]):
557 newls.append(parn)
557 newls.append(parn)
558
558
559 # dict.values() for Py3 compatibility
559 # dict.values() for Py3 compatibility
560 for files in pardict.values():
560 for files in pardict.values():
561 rs.extend(files)
561 rs.extend(files)
562
562
563 rs.sort()
563 rs.sort()
564 finalrs[ind] = rs
564 finalrs[ind] = rs
565 didsomethingchanged = True
565 didsomethingchanged = True
566
566
567 # If nothing is changed, make sure the order of files is preserved.
567 # If nothing is changed, make sure the order of files is preserved.
568 if not didsomethingchanged:
568 if not didsomethingchanged:
569 return statlist
569 return statlist
570
570
571 for x in xrange(len(indexes)):
571 for x in xrange(len(indexes)):
572 if not finalrs[x]:
572 if not finalrs[x]:
573 finalrs[x] = statlist[x]
573 finalrs[x] = statlist[x]
574
574
575 return finalrs
575 return finalrs
576
576
577 def _commentlines(raw):
577 def _commentlines(raw):
578 '''Surround lineswith a comment char and a new line'''
578 '''Surround lineswith a comment char and a new line'''
579 lines = raw.splitlines()
579 lines = raw.splitlines()
580 commentedlines = ['# %s' % line for line in lines]
580 commentedlines = ['# %s' % line for line in lines]
581 return '\n'.join(commentedlines) + '\n'
581 return '\n'.join(commentedlines) + '\n'
582
582
583 def _conflictsmsg(repo):
583 def _conflictsmsg(repo):
584 # avoid merge cycle
584 # avoid merge cycle
585 from . import merge as mergemod
585 from . import merge as mergemod
586 mergestate = mergemod.mergestate.read(repo)
586 mergestate = mergemod.mergestate.read(repo)
587 if not mergestate.active():
587 if not mergestate.active():
588 return
588 return
589
589
590 m = scmutil.match(repo[None])
590 m = scmutil.match(repo[None])
591 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
591 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
592 if unresolvedlist:
592 if unresolvedlist:
593 mergeliststr = '\n'.join(
593 mergeliststr = '\n'.join(
594 [' %s' % os.path.relpath(
594 [' %s' % os.path.relpath(
595 os.path.join(repo.root, path),
595 os.path.join(repo.root, path),
596 pycompat.getcwd()) for path in unresolvedlist])
596 pycompat.getcwd()) for path in unresolvedlist])
597 msg = _('''Unresolved merge conflicts:
597 msg = _('''Unresolved merge conflicts:
598
598
599 %s
599 %s
600
600
601 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
601 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
602 else:
602 else:
603 msg = _('No unresolved merge conflicts.')
603 msg = _('No unresolved merge conflicts.')
604
604
605 return _commentlines(msg)
605 return _commentlines(msg)
606
606
607 def _helpmessage(continuecmd, abortcmd):
607 def _helpmessage(continuecmd, abortcmd):
608 msg = _('To continue: %s\n'
608 msg = _('To continue: %s\n'
609 'To abort: %s') % (continuecmd, abortcmd)
609 'To abort: %s') % (continuecmd, abortcmd)
610 return _commentlines(msg)
610 return _commentlines(msg)
611
611
612 def _rebasemsg():
612 def _rebasemsg():
613 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
613 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
614
614
615 def _histeditmsg():
615 def _histeditmsg():
616 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
616 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
617
617
618 def _unshelvemsg():
618 def _unshelvemsg():
619 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
619 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
620
620
621 def _updatecleanmsg(dest=None):
621 def _updatecleanmsg(dest=None):
622 warning = _('warning: this will discard uncommitted changes')
622 warning = _('warning: this will discard uncommitted changes')
623 return 'hg update --clean %s (%s)' % (dest or '.', warning)
623 return 'hg update --clean %s (%s)' % (dest or '.', warning)
624
624
625 def _graftmsg():
625 def _graftmsg():
626 # tweakdefaults requires `update` to have a rev hence the `.`
626 # tweakdefaults requires `update` to have a rev hence the `.`
627 return _helpmessage('hg graft --continue', _updatecleanmsg())
627 return _helpmessage('hg graft --continue', _updatecleanmsg())
628
628
629 def _mergemsg():
629 def _mergemsg():
630 # tweakdefaults requires `update` to have a rev hence the `.`
630 # tweakdefaults requires `update` to have a rev hence the `.`
631 return _helpmessage('hg commit', _updatecleanmsg())
631 return _helpmessage('hg commit', _updatecleanmsg())
632
632
633 def _bisectmsg():
633 def _bisectmsg():
634 msg = _('To mark the changeset good: hg bisect --good\n'
634 msg = _('To mark the changeset good: hg bisect --good\n'
635 'To mark the changeset bad: hg bisect --bad\n'
635 'To mark the changeset bad: hg bisect --bad\n'
636 'To abort: hg bisect --reset\n')
636 'To abort: hg bisect --reset\n')
637 return _commentlines(msg)
637 return _commentlines(msg)
638
638
639 def fileexistspredicate(filename):
639 def fileexistspredicate(filename):
640 return lambda repo: repo.vfs.exists(filename)
640 return lambda repo: repo.vfs.exists(filename)
641
641
642 def _mergepredicate(repo):
642 def _mergepredicate(repo):
643 return len(repo[None].parents()) > 1
643 return len(repo[None].parents()) > 1
644
644
645 STATES = (
645 STATES = (
646 # (state, predicate to detect states, helpful message function)
646 # (state, predicate to detect states, helpful message function)
647 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
647 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
648 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
648 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
649 ('graft', fileexistspredicate('graftstate'), _graftmsg),
649 ('graft', fileexistspredicate('graftstate'), _graftmsg),
650 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
650 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
651 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
651 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
652 # The merge state is part of a list that will be iterated over.
652 # The merge state is part of a list that will be iterated over.
653 # They need to be last because some of the other unfinished states may also
653 # They need to be last because some of the other unfinished states may also
654 # be in a merge or update state (eg. rebase, histedit, graft, etc).
654 # be in a merge or update state (eg. rebase, histedit, graft, etc).
655 # We want those to have priority.
655 # We want those to have priority.
656 ('merge', _mergepredicate, _mergemsg),
656 ('merge', _mergepredicate, _mergemsg),
657 )
657 )
658
658
659 def _getrepostate(repo):
659 def _getrepostate(repo):
660 # experimental config: commands.status.skipstates
660 # experimental config: commands.status.skipstates
661 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
661 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
662 for state, statedetectionpredicate, msgfn in STATES:
662 for state, statedetectionpredicate, msgfn in STATES:
663 if state in skip:
663 if state in skip:
664 continue
664 continue
665 if statedetectionpredicate(repo):
665 if statedetectionpredicate(repo):
666 return (state, statedetectionpredicate, msgfn)
666 return (state, statedetectionpredicate, msgfn)
667
667
668 def morestatus(repo, fm):
668 def morestatus(repo, fm):
669 statetuple = _getrepostate(repo)
669 statetuple = _getrepostate(repo)
670 label = 'status.morestatus'
670 label = 'status.morestatus'
671 if statetuple:
671 if statetuple:
672 fm.startitem()
672 fm.startitem()
673 state, statedetectionpredicate, helpfulmsg = statetuple
673 state, statedetectionpredicate, helpfulmsg = statetuple
674 statemsg = _('The repository is in an unfinished *%s* state.') % state
674 statemsg = _('The repository is in an unfinished *%s* state.') % state
675 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
675 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
676 conmsg = _conflictsmsg(repo)
676 conmsg = _conflictsmsg(repo)
677 if conmsg:
677 if conmsg:
678 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
678 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
679 if helpfulmsg:
679 if helpfulmsg:
680 helpmsg = helpfulmsg()
680 helpmsg = helpfulmsg()
681 fm.write('helpmsg', '%s\n', helpmsg, label=label)
681 fm.write('helpmsg', '%s\n', helpmsg, label=label)
682
682
683 def findpossible(cmd, table, strict=False):
683 def findpossible(cmd, table, strict=False):
684 """
684 """
685 Return cmd -> (aliases, command table entry)
685 Return cmd -> (aliases, command table entry)
686 for each matching command.
686 for each matching command.
687 Return debug commands (or their aliases) only if no normal command matches.
687 Return debug commands (or their aliases) only if no normal command matches.
688 """
688 """
689 choice = {}
689 choice = {}
690 debugchoice = {}
690 debugchoice = {}
691
691
692 if cmd in table:
692 if cmd in table:
693 # short-circuit exact matches, "log" alias beats "^log|history"
693 # short-circuit exact matches, "log" alias beats "^log|history"
694 keys = [cmd]
694 keys = [cmd]
695 else:
695 else:
696 keys = table.keys()
696 keys = table.keys()
697
697
698 allcmds = []
698 allcmds = []
699 for e in keys:
699 for e in keys:
700 aliases = parsealiases(e)
700 aliases = parsealiases(e)
701 allcmds.extend(aliases)
701 allcmds.extend(aliases)
702 found = None
702 found = None
703 if cmd in aliases:
703 if cmd in aliases:
704 found = cmd
704 found = cmd
705 elif not strict:
705 elif not strict:
706 for a in aliases:
706 for a in aliases:
707 if a.startswith(cmd):
707 if a.startswith(cmd):
708 found = a
708 found = a
709 break
709 break
710 if found is not None:
710 if found is not None:
711 if aliases[0].startswith("debug") or found.startswith("debug"):
711 if aliases[0].startswith("debug") or found.startswith("debug"):
712 debugchoice[found] = (aliases, table[e])
712 debugchoice[found] = (aliases, table[e])
713 else:
713 else:
714 choice[found] = (aliases, table[e])
714 choice[found] = (aliases, table[e])
715
715
716 if not choice and debugchoice:
716 if not choice and debugchoice:
717 choice = debugchoice
717 choice = debugchoice
718
718
719 return choice, allcmds
719 return choice, allcmds
720
720
721 def findcmd(cmd, table, strict=True):
721 def findcmd(cmd, table, strict=True):
722 """Return (aliases, command table entry) for command string."""
722 """Return (aliases, command table entry) for command string."""
723 choice, allcmds = findpossible(cmd, table, strict)
723 choice, allcmds = findpossible(cmd, table, strict)
724
724
725 if cmd in choice:
725 if cmd in choice:
726 return choice[cmd]
726 return choice[cmd]
727
727
728 if len(choice) > 1:
728 if len(choice) > 1:
729 clist = sorted(choice)
729 clist = sorted(choice)
730 raise error.AmbiguousCommand(cmd, clist)
730 raise error.AmbiguousCommand(cmd, clist)
731
731
732 if choice:
732 if choice:
733 return list(choice.values())[0]
733 return list(choice.values())[0]
734
734
735 raise error.UnknownCommand(cmd, allcmds)
735 raise error.UnknownCommand(cmd, allcmds)
736
736
737 def findrepo(p):
737 def findrepo(p):
738 while not os.path.isdir(os.path.join(p, ".hg")):
738 while not os.path.isdir(os.path.join(p, ".hg")):
739 oldp, p = p, os.path.dirname(p)
739 oldp, p = p, os.path.dirname(p)
740 if p == oldp:
740 if p == oldp:
741 return None
741 return None
742
742
743 return p
743 return p
744
744
745 def bailifchanged(repo, merge=True, hint=None):
745 def bailifchanged(repo, merge=True, hint=None):
746 """ enforce the precondition that working directory must be clean.
746 """ enforce the precondition that working directory must be clean.
747
747
748 'merge' can be set to false if a pending uncommitted merge should be
748 'merge' can be set to false if a pending uncommitted merge should be
749 ignored (such as when 'update --check' runs).
749 ignored (such as when 'update --check' runs).
750
750
751 'hint' is the usual hint given to Abort exception.
751 'hint' is the usual hint given to Abort exception.
752 """
752 """
753
753
754 if merge and repo.dirstate.p2() != nullid:
754 if merge and repo.dirstate.p2() != nullid:
755 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
755 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
756 modified, added, removed, deleted = repo.status()[:4]
756 modified, added, removed, deleted = repo.status()[:4]
757 if modified or added or removed or deleted:
757 if modified or added or removed or deleted:
758 raise error.Abort(_('uncommitted changes'), hint=hint)
758 raise error.Abort(_('uncommitted changes'), hint=hint)
759 ctx = repo[None]
759 ctx = repo[None]
760 for s in sorted(ctx.substate):
760 for s in sorted(ctx.substate):
761 ctx.sub(s).bailifchanged(hint=hint)
761 ctx.sub(s).bailifchanged(hint=hint)
762
762
763 def logmessage(ui, opts):
763 def logmessage(ui, opts):
764 """ get the log message according to -m and -l option """
764 """ get the log message according to -m and -l option """
765 message = opts.get('message')
765 message = opts.get('message')
766 logfile = opts.get('logfile')
766 logfile = opts.get('logfile')
767
767
768 if message and logfile:
768 if message and logfile:
769 raise error.Abort(_('options --message and --logfile are mutually '
769 raise error.Abort(_('options --message and --logfile are mutually '
770 'exclusive'))
770 'exclusive'))
771 if not message and logfile:
771 if not message and logfile:
772 try:
772 try:
773 if isstdiofilename(logfile):
773 if isstdiofilename(logfile):
774 message = ui.fin.read()
774 message = ui.fin.read()
775 else:
775 else:
776 message = '\n'.join(util.readfile(logfile).splitlines())
776 message = '\n'.join(util.readfile(logfile).splitlines())
777 except IOError as inst:
777 except IOError as inst:
778 raise error.Abort(_("can't read commit message '%s': %s") %
778 raise error.Abort(_("can't read commit message '%s': %s") %
779 (logfile, encoding.strtolocal(inst.strerror)))
779 (logfile, encoding.strtolocal(inst.strerror)))
780 return message
780 return message
781
781
782 def mergeeditform(ctxorbool, baseformname):
782 def mergeeditform(ctxorbool, baseformname):
783 """return appropriate editform name (referencing a committemplate)
783 """return appropriate editform name (referencing a committemplate)
784
784
785 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
785 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
786 merging is committed.
786 merging is committed.
787
787
788 This returns baseformname with '.merge' appended if it is a merge,
788 This returns baseformname with '.merge' appended if it is a merge,
789 otherwise '.normal' is appended.
789 otherwise '.normal' is appended.
790 """
790 """
791 if isinstance(ctxorbool, bool):
791 if isinstance(ctxorbool, bool):
792 if ctxorbool:
792 if ctxorbool:
793 return baseformname + ".merge"
793 return baseformname + ".merge"
794 elif 1 < len(ctxorbool.parents()):
794 elif 1 < len(ctxorbool.parents()):
795 return baseformname + ".merge"
795 return baseformname + ".merge"
796
796
797 return baseformname + ".normal"
797 return baseformname + ".normal"
798
798
799 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
799 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
800 editform='', **opts):
800 editform='', **opts):
801 """get appropriate commit message editor according to '--edit' option
801 """get appropriate commit message editor according to '--edit' option
802
802
803 'finishdesc' is a function to be called with edited commit message
803 'finishdesc' is a function to be called with edited commit message
804 (= 'description' of the new changeset) just after editing, but
804 (= 'description' of the new changeset) just after editing, but
805 before checking empty-ness. It should return actual text to be
805 before checking empty-ness. It should return actual text to be
806 stored into history. This allows to change description before
806 stored into history. This allows to change description before
807 storing.
807 storing.
808
808
809 'extramsg' is a extra message to be shown in the editor instead of
809 'extramsg' is a extra message to be shown in the editor instead of
810 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
810 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
811 is automatically added.
811 is automatically added.
812
812
813 'editform' is a dot-separated list of names, to distinguish
813 'editform' is a dot-separated list of names, to distinguish
814 the purpose of commit text editing.
814 the purpose of commit text editing.
815
815
816 'getcommiteditor' returns 'commitforceeditor' regardless of
816 'getcommiteditor' returns 'commitforceeditor' regardless of
817 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
817 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
818 they are specific for usage in MQ.
818 they are specific for usage in MQ.
819 """
819 """
820 if edit or finishdesc or extramsg:
820 if edit or finishdesc or extramsg:
821 return lambda r, c, s: commitforceeditor(r, c, s,
821 return lambda r, c, s: commitforceeditor(r, c, s,
822 finishdesc=finishdesc,
822 finishdesc=finishdesc,
823 extramsg=extramsg,
823 extramsg=extramsg,
824 editform=editform)
824 editform=editform)
825 elif editform:
825 elif editform:
826 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
826 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
827 else:
827 else:
828 return commiteditor
828 return commiteditor
829
829
830 def loglimit(opts):
830 def loglimit(opts):
831 """get the log limit according to option -l/--limit"""
831 """get the log limit according to option -l/--limit"""
832 limit = opts.get('limit')
832 limit = opts.get('limit')
833 if limit:
833 if limit:
834 try:
834 try:
835 limit = int(limit)
835 limit = int(limit)
836 except ValueError:
836 except ValueError:
837 raise error.Abort(_('limit must be a positive integer'))
837 raise error.Abort(_('limit must be a positive integer'))
838 if limit <= 0:
838 if limit <= 0:
839 raise error.Abort(_('limit must be positive'))
839 raise error.Abort(_('limit must be positive'))
840 else:
840 else:
841 limit = None
841 limit = None
842 return limit
842 return limit
843
843
844 def makefilename(repo, pat, node, desc=None,
844 def makefilename(repo, pat, node, desc=None,
845 total=None, seqno=None, revwidth=None, pathname=None):
845 total=None, seqno=None, revwidth=None, pathname=None):
846 node_expander = {
846 node_expander = {
847 'H': lambda: hex(node),
847 'H': lambda: hex(node),
848 'R': lambda: str(repo.changelog.rev(node)),
848 'R': lambda: str(repo.changelog.rev(node)),
849 'h': lambda: short(node),
849 'h': lambda: short(node),
850 'm': lambda: re.sub('[^\w]', '_', str(desc))
850 'm': lambda: re.sub('[^\w]', '_', str(desc))
851 }
851 }
852 expander = {
852 expander = {
853 '%': lambda: '%',
853 '%': lambda: '%',
854 'b': lambda: os.path.basename(repo.root),
854 'b': lambda: os.path.basename(repo.root),
855 }
855 }
856
856
857 try:
857 try:
858 if node:
858 if node:
859 expander.update(node_expander)
859 expander.update(node_expander)
860 if node:
860 if node:
861 expander['r'] = (lambda:
861 expander['r'] = (lambda:
862 str(repo.changelog.rev(node)).zfill(revwidth or 0))
862 str(repo.changelog.rev(node)).zfill(revwidth or 0))
863 if total is not None:
863 if total is not None:
864 expander['N'] = lambda: str(total)
864 expander['N'] = lambda: str(total)
865 if seqno is not None:
865 if seqno is not None:
866 expander['n'] = lambda: str(seqno)
866 expander['n'] = lambda: str(seqno)
867 if total is not None and seqno is not None:
867 if total is not None and seqno is not None:
868 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
868 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
869 if pathname is not None:
869 if pathname is not None:
870 expander['s'] = lambda: os.path.basename(pathname)
870 expander['s'] = lambda: os.path.basename(pathname)
871 expander['d'] = lambda: os.path.dirname(pathname) or '.'
871 expander['d'] = lambda: os.path.dirname(pathname) or '.'
872 expander['p'] = lambda: pathname
872 expander['p'] = lambda: pathname
873
873
874 newname = []
874 newname = []
875 patlen = len(pat)
875 patlen = len(pat)
876 i = 0
876 i = 0
877 while i < patlen:
877 while i < patlen:
878 c = pat[i:i + 1]
878 c = pat[i:i + 1]
879 if c == '%':
879 if c == '%':
880 i += 1
880 i += 1
881 c = pat[i:i + 1]
881 c = pat[i:i + 1]
882 c = expander[c]()
882 c = expander[c]()
883 newname.append(c)
883 newname.append(c)
884 i += 1
884 i += 1
885 return ''.join(newname)
885 return ''.join(newname)
886 except KeyError as inst:
886 except KeyError as inst:
887 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
887 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
888 inst.args[0])
888 inst.args[0])
889
889
890 def isstdiofilename(pat):
890 def isstdiofilename(pat):
891 """True if the given pat looks like a filename denoting stdin/stdout"""
891 """True if the given pat looks like a filename denoting stdin/stdout"""
892 return not pat or pat == '-'
892 return not pat or pat == '-'
893
893
894 class _unclosablefile(object):
894 class _unclosablefile(object):
895 def __init__(self, fp):
895 def __init__(self, fp):
896 self._fp = fp
896 self._fp = fp
897
897
898 def close(self):
898 def close(self):
899 pass
899 pass
900
900
901 def __iter__(self):
901 def __iter__(self):
902 return iter(self._fp)
902 return iter(self._fp)
903
903
904 def __getattr__(self, attr):
904 def __getattr__(self, attr):
905 return getattr(self._fp, attr)
905 return getattr(self._fp, attr)
906
906
907 def __enter__(self):
907 def __enter__(self):
908 return self
908 return self
909
909
910 def __exit__(self, exc_type, exc_value, exc_tb):
910 def __exit__(self, exc_type, exc_value, exc_tb):
911 pass
911 pass
912
912
913 def makefileobj(repo, pat, node=None, desc=None, total=None,
913 def makefileobj(repo, pat, node=None, desc=None, total=None,
914 seqno=None, revwidth=None, mode='wb', modemap=None,
914 seqno=None, revwidth=None, mode='wb', modemap=None,
915 pathname=None):
915 pathname=None):
916
916
917 writable = mode not in ('r', 'rb')
917 writable = mode not in ('r', 'rb')
918
918
919 if isstdiofilename(pat):
919 if isstdiofilename(pat):
920 if writable:
920 if writable:
921 fp = repo.ui.fout
921 fp = repo.ui.fout
922 else:
922 else:
923 fp = repo.ui.fin
923 fp = repo.ui.fin
924 return _unclosablefile(fp)
924 return _unclosablefile(fp)
925 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
925 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
926 if modemap is not None:
926 if modemap is not None:
927 mode = modemap.get(fn, mode)
927 mode = modemap.get(fn, mode)
928 if mode == 'wb':
928 if mode == 'wb':
929 modemap[fn] = 'ab'
929 modemap[fn] = 'ab'
930 return open(fn, mode)
930 return open(fn, mode)
931
931
932 def openrevlog(repo, cmd, file_, opts):
932 def openrevlog(repo, cmd, file_, opts):
933 """opens the changelog, manifest, a filelog or a given revlog"""
933 """opens the changelog, manifest, a filelog or a given revlog"""
934 cl = opts['changelog']
934 cl = opts['changelog']
935 mf = opts['manifest']
935 mf = opts['manifest']
936 dir = opts['dir']
936 dir = opts['dir']
937 msg = None
937 msg = None
938 if cl and mf:
938 if cl and mf:
939 msg = _('cannot specify --changelog and --manifest at the same time')
939 msg = _('cannot specify --changelog and --manifest at the same time')
940 elif cl and dir:
940 elif cl and dir:
941 msg = _('cannot specify --changelog and --dir at the same time')
941 msg = _('cannot specify --changelog and --dir at the same time')
942 elif cl or mf or dir:
942 elif cl or mf or dir:
943 if file_:
943 if file_:
944 msg = _('cannot specify filename with --changelog or --manifest')
944 msg = _('cannot specify filename with --changelog or --manifest')
945 elif not repo:
945 elif not repo:
946 msg = _('cannot specify --changelog or --manifest or --dir '
946 msg = _('cannot specify --changelog or --manifest or --dir '
947 'without a repository')
947 'without a repository')
948 if msg:
948 if msg:
949 raise error.Abort(msg)
949 raise error.Abort(msg)
950
950
951 r = None
951 r = None
952 if repo:
952 if repo:
953 if cl:
953 if cl:
954 r = repo.unfiltered().changelog
954 r = repo.unfiltered().changelog
955 elif dir:
955 elif dir:
956 if 'treemanifest' not in repo.requirements:
956 if 'treemanifest' not in repo.requirements:
957 raise error.Abort(_("--dir can only be used on repos with "
957 raise error.Abort(_("--dir can only be used on repos with "
958 "treemanifest enabled"))
958 "treemanifest enabled"))
959 dirlog = repo.manifestlog._revlog.dirlog(dir)
959 dirlog = repo.manifestlog._revlog.dirlog(dir)
960 if len(dirlog):
960 if len(dirlog):
961 r = dirlog
961 r = dirlog
962 elif mf:
962 elif mf:
963 r = repo.manifestlog._revlog
963 r = repo.manifestlog._revlog
964 elif file_:
964 elif file_:
965 filelog = repo.file(file_)
965 filelog = repo.file(file_)
966 if len(filelog):
966 if len(filelog):
967 r = filelog
967 r = filelog
968 if not r:
968 if not r:
969 if not file_:
969 if not file_:
970 raise error.CommandError(cmd, _('invalid arguments'))
970 raise error.CommandError(cmd, _('invalid arguments'))
971 if not os.path.isfile(file_):
971 if not os.path.isfile(file_):
972 raise error.Abort(_("revlog '%s' not found") % file_)
972 raise error.Abort(_("revlog '%s' not found") % file_)
973 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
973 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
974 file_[:-2] + ".i")
974 file_[:-2] + ".i")
975 return r
975 return r
976
976
977 def copy(ui, repo, pats, opts, rename=False):
977 def copy(ui, repo, pats, opts, rename=False):
978 # called with the repo lock held
978 # called with the repo lock held
979 #
979 #
980 # hgsep => pathname that uses "/" to separate directories
980 # hgsep => pathname that uses "/" to separate directories
981 # ossep => pathname that uses os.sep to separate directories
981 # ossep => pathname that uses os.sep to separate directories
982 cwd = repo.getcwd()
982 cwd = repo.getcwd()
983 targets = {}
983 targets = {}
984 after = opts.get("after")
984 after = opts.get("after")
985 dryrun = opts.get("dry_run")
985 dryrun = opts.get("dry_run")
986 wctx = repo[None]
986 wctx = repo[None]
987
987
988 def walkpat(pat):
988 def walkpat(pat):
989 srcs = []
989 srcs = []
990 if after:
990 if after:
991 badstates = '?'
991 badstates = '?'
992 else:
992 else:
993 badstates = '?r'
993 badstates = '?r'
994 m = scmutil.match(wctx, [pat], opts, globbed=True)
994 m = scmutil.match(wctx, [pat], opts, globbed=True)
995 for abs in wctx.walk(m):
995 for abs in wctx.walk(m):
996 state = repo.dirstate[abs]
996 state = repo.dirstate[abs]
997 rel = m.rel(abs)
997 rel = m.rel(abs)
998 exact = m.exact(abs)
998 exact = m.exact(abs)
999 if state in badstates:
999 if state in badstates:
1000 if exact and state == '?':
1000 if exact and state == '?':
1001 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1001 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1002 if exact and state == 'r':
1002 if exact and state == 'r':
1003 ui.warn(_('%s: not copying - file has been marked for'
1003 ui.warn(_('%s: not copying - file has been marked for'
1004 ' remove\n') % rel)
1004 ' remove\n') % rel)
1005 continue
1005 continue
1006 # abs: hgsep
1006 # abs: hgsep
1007 # rel: ossep
1007 # rel: ossep
1008 srcs.append((abs, rel, exact))
1008 srcs.append((abs, rel, exact))
1009 return srcs
1009 return srcs
1010
1010
1011 # abssrc: hgsep
1011 # abssrc: hgsep
1012 # relsrc: ossep
1012 # relsrc: ossep
1013 # otarget: ossep
1013 # otarget: ossep
1014 def copyfile(abssrc, relsrc, otarget, exact):
1014 def copyfile(abssrc, relsrc, otarget, exact):
1015 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1015 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1016 if '/' in abstarget:
1016 if '/' in abstarget:
1017 # We cannot normalize abstarget itself, this would prevent
1017 # We cannot normalize abstarget itself, this would prevent
1018 # case only renames, like a => A.
1018 # case only renames, like a => A.
1019 abspath, absname = abstarget.rsplit('/', 1)
1019 abspath, absname = abstarget.rsplit('/', 1)
1020 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1020 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1021 reltarget = repo.pathto(abstarget, cwd)
1021 reltarget = repo.pathto(abstarget, cwd)
1022 target = repo.wjoin(abstarget)
1022 target = repo.wjoin(abstarget)
1023 src = repo.wjoin(abssrc)
1023 src = repo.wjoin(abssrc)
1024 state = repo.dirstate[abstarget]
1024 state = repo.dirstate[abstarget]
1025
1025
1026 scmutil.checkportable(ui, abstarget)
1026 scmutil.checkportable(ui, abstarget)
1027
1027
1028 # check for collisions
1028 # check for collisions
1029 prevsrc = targets.get(abstarget)
1029 prevsrc = targets.get(abstarget)
1030 if prevsrc is not None:
1030 if prevsrc is not None:
1031 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1031 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1032 (reltarget, repo.pathto(abssrc, cwd),
1032 (reltarget, repo.pathto(abssrc, cwd),
1033 repo.pathto(prevsrc, cwd)))
1033 repo.pathto(prevsrc, cwd)))
1034 return
1034 return
1035
1035
1036 # check for overwrites
1036 # check for overwrites
1037 exists = os.path.lexists(target)
1037 exists = os.path.lexists(target)
1038 samefile = False
1038 samefile = False
1039 if exists and abssrc != abstarget:
1039 if exists and abssrc != abstarget:
1040 if (repo.dirstate.normalize(abssrc) ==
1040 if (repo.dirstate.normalize(abssrc) ==
1041 repo.dirstate.normalize(abstarget)):
1041 repo.dirstate.normalize(abstarget)):
1042 if not rename:
1042 if not rename:
1043 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1043 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1044 return
1044 return
1045 exists = False
1045 exists = False
1046 samefile = True
1046 samefile = True
1047
1047
1048 if not after and exists or after and state in 'mn':
1048 if not after and exists or after and state in 'mn':
1049 if not opts['force']:
1049 if not opts['force']:
1050 if state in 'mn':
1050 if state in 'mn':
1051 msg = _('%s: not overwriting - file already committed\n')
1051 msg = _('%s: not overwriting - file already committed\n')
1052 if after:
1052 if after:
1053 flags = '--after --force'
1053 flags = '--after --force'
1054 else:
1054 else:
1055 flags = '--force'
1055 flags = '--force'
1056 if rename:
1056 if rename:
1057 hint = _('(hg rename %s to replace the file by '
1057 hint = _('(hg rename %s to replace the file by '
1058 'recording a rename)\n') % flags
1058 'recording a rename)\n') % flags
1059 else:
1059 else:
1060 hint = _('(hg copy %s to replace the file by '
1060 hint = _('(hg copy %s to replace the file by '
1061 'recording a copy)\n') % flags
1061 'recording a copy)\n') % flags
1062 else:
1062 else:
1063 msg = _('%s: not overwriting - file exists\n')
1063 msg = _('%s: not overwriting - file exists\n')
1064 if rename:
1064 if rename:
1065 hint = _('(hg rename --after to record the rename)\n')
1065 hint = _('(hg rename --after to record the rename)\n')
1066 else:
1066 else:
1067 hint = _('(hg copy --after to record the copy)\n')
1067 hint = _('(hg copy --after to record the copy)\n')
1068 ui.warn(msg % reltarget)
1068 ui.warn(msg % reltarget)
1069 ui.warn(hint)
1069 ui.warn(hint)
1070 return
1070 return
1071
1071
1072 if after:
1072 if after:
1073 if not exists:
1073 if not exists:
1074 if rename:
1074 if rename:
1075 ui.warn(_('%s: not recording move - %s does not exist\n') %
1075 ui.warn(_('%s: not recording move - %s does not exist\n') %
1076 (relsrc, reltarget))
1076 (relsrc, reltarget))
1077 else:
1077 else:
1078 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1078 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1079 (relsrc, reltarget))
1079 (relsrc, reltarget))
1080 return
1080 return
1081 elif not dryrun:
1081 elif not dryrun:
1082 try:
1082 try:
1083 if exists:
1083 if exists:
1084 os.unlink(target)
1084 os.unlink(target)
1085 targetdir = os.path.dirname(target) or '.'
1085 targetdir = os.path.dirname(target) or '.'
1086 if not os.path.isdir(targetdir):
1086 if not os.path.isdir(targetdir):
1087 os.makedirs(targetdir)
1087 os.makedirs(targetdir)
1088 if samefile:
1088 if samefile:
1089 tmp = target + "~hgrename"
1089 tmp = target + "~hgrename"
1090 os.rename(src, tmp)
1090 os.rename(src, tmp)
1091 os.rename(tmp, target)
1091 os.rename(tmp, target)
1092 else:
1092 else:
1093 util.copyfile(src, target)
1093 util.copyfile(src, target)
1094 srcexists = True
1094 srcexists = True
1095 except IOError as inst:
1095 except IOError as inst:
1096 if inst.errno == errno.ENOENT:
1096 if inst.errno == errno.ENOENT:
1097 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1097 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1098 srcexists = False
1098 srcexists = False
1099 else:
1099 else:
1100 ui.warn(_('%s: cannot copy - %s\n') %
1100 ui.warn(_('%s: cannot copy - %s\n') %
1101 (relsrc, encoding.strtolocal(inst.strerror)))
1101 (relsrc, encoding.strtolocal(inst.strerror)))
1102 return True # report a failure
1102 return True # report a failure
1103
1103
1104 if ui.verbose or not exact:
1104 if ui.verbose or not exact:
1105 if rename:
1105 if rename:
1106 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1106 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1107 else:
1107 else:
1108 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1108 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1109
1109
1110 targets[abstarget] = abssrc
1110 targets[abstarget] = abssrc
1111
1111
1112 # fix up dirstate
1112 # fix up dirstate
1113 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1113 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1114 dryrun=dryrun, cwd=cwd)
1114 dryrun=dryrun, cwd=cwd)
1115 if rename and not dryrun:
1115 if rename and not dryrun:
1116 if not after and srcexists and not samefile:
1116 if not after and srcexists and not samefile:
1117 repo.wvfs.unlinkpath(abssrc)
1117 repo.wvfs.unlinkpath(abssrc)
1118 wctx.forget([abssrc])
1118 wctx.forget([abssrc])
1119
1119
1120 # pat: ossep
1120 # pat: ossep
1121 # dest ossep
1121 # dest ossep
1122 # srcs: list of (hgsep, hgsep, ossep, bool)
1122 # srcs: list of (hgsep, hgsep, ossep, bool)
1123 # return: function that takes hgsep and returns ossep
1123 # return: function that takes hgsep and returns ossep
1124 def targetpathfn(pat, dest, srcs):
1124 def targetpathfn(pat, dest, srcs):
1125 if os.path.isdir(pat):
1125 if os.path.isdir(pat):
1126 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1126 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1127 abspfx = util.localpath(abspfx)
1127 abspfx = util.localpath(abspfx)
1128 if destdirexists:
1128 if destdirexists:
1129 striplen = len(os.path.split(abspfx)[0])
1129 striplen = len(os.path.split(abspfx)[0])
1130 else:
1130 else:
1131 striplen = len(abspfx)
1131 striplen = len(abspfx)
1132 if striplen:
1132 if striplen:
1133 striplen += len(pycompat.ossep)
1133 striplen += len(pycompat.ossep)
1134 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1134 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1135 elif destdirexists:
1135 elif destdirexists:
1136 res = lambda p: os.path.join(dest,
1136 res = lambda p: os.path.join(dest,
1137 os.path.basename(util.localpath(p)))
1137 os.path.basename(util.localpath(p)))
1138 else:
1138 else:
1139 res = lambda p: dest
1139 res = lambda p: dest
1140 return res
1140 return res
1141
1141
1142 # pat: ossep
1142 # pat: ossep
1143 # dest ossep
1143 # dest ossep
1144 # srcs: list of (hgsep, hgsep, ossep, bool)
1144 # srcs: list of (hgsep, hgsep, ossep, bool)
1145 # return: function that takes hgsep and returns ossep
1145 # return: function that takes hgsep and returns ossep
1146 def targetpathafterfn(pat, dest, srcs):
1146 def targetpathafterfn(pat, dest, srcs):
1147 if matchmod.patkind(pat):
1147 if matchmod.patkind(pat):
1148 # a mercurial pattern
1148 # a mercurial pattern
1149 res = lambda p: os.path.join(dest,
1149 res = lambda p: os.path.join(dest,
1150 os.path.basename(util.localpath(p)))
1150 os.path.basename(util.localpath(p)))
1151 else:
1151 else:
1152 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1152 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1153 if len(abspfx) < len(srcs[0][0]):
1153 if len(abspfx) < len(srcs[0][0]):
1154 # A directory. Either the target path contains the last
1154 # A directory. Either the target path contains the last
1155 # component of the source path or it does not.
1155 # component of the source path or it does not.
1156 def evalpath(striplen):
1156 def evalpath(striplen):
1157 score = 0
1157 score = 0
1158 for s in srcs:
1158 for s in srcs:
1159 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1159 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1160 if os.path.lexists(t):
1160 if os.path.lexists(t):
1161 score += 1
1161 score += 1
1162 return score
1162 return score
1163
1163
1164 abspfx = util.localpath(abspfx)
1164 abspfx = util.localpath(abspfx)
1165 striplen = len(abspfx)
1165 striplen = len(abspfx)
1166 if striplen:
1166 if striplen:
1167 striplen += len(pycompat.ossep)
1167 striplen += len(pycompat.ossep)
1168 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1168 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1169 score = evalpath(striplen)
1169 score = evalpath(striplen)
1170 striplen1 = len(os.path.split(abspfx)[0])
1170 striplen1 = len(os.path.split(abspfx)[0])
1171 if striplen1:
1171 if striplen1:
1172 striplen1 += len(pycompat.ossep)
1172 striplen1 += len(pycompat.ossep)
1173 if evalpath(striplen1) > score:
1173 if evalpath(striplen1) > score:
1174 striplen = striplen1
1174 striplen = striplen1
1175 res = lambda p: os.path.join(dest,
1175 res = lambda p: os.path.join(dest,
1176 util.localpath(p)[striplen:])
1176 util.localpath(p)[striplen:])
1177 else:
1177 else:
1178 # a file
1178 # a file
1179 if destdirexists:
1179 if destdirexists:
1180 res = lambda p: os.path.join(dest,
1180 res = lambda p: os.path.join(dest,
1181 os.path.basename(util.localpath(p)))
1181 os.path.basename(util.localpath(p)))
1182 else:
1182 else:
1183 res = lambda p: dest
1183 res = lambda p: dest
1184 return res
1184 return res
1185
1185
1186 pats = scmutil.expandpats(pats)
1186 pats = scmutil.expandpats(pats)
1187 if not pats:
1187 if not pats:
1188 raise error.Abort(_('no source or destination specified'))
1188 raise error.Abort(_('no source or destination specified'))
1189 if len(pats) == 1:
1189 if len(pats) == 1:
1190 raise error.Abort(_('no destination specified'))
1190 raise error.Abort(_('no destination specified'))
1191 dest = pats.pop()
1191 dest = pats.pop()
1192 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1192 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1193 if not destdirexists:
1193 if not destdirexists:
1194 if len(pats) > 1 or matchmod.patkind(pats[0]):
1194 if len(pats) > 1 or matchmod.patkind(pats[0]):
1195 raise error.Abort(_('with multiple sources, destination must be an '
1195 raise error.Abort(_('with multiple sources, destination must be an '
1196 'existing directory'))
1196 'existing directory'))
1197 if util.endswithsep(dest):
1197 if util.endswithsep(dest):
1198 raise error.Abort(_('destination %s is not a directory') % dest)
1198 raise error.Abort(_('destination %s is not a directory') % dest)
1199
1199
1200 tfn = targetpathfn
1200 tfn = targetpathfn
1201 if after:
1201 if after:
1202 tfn = targetpathafterfn
1202 tfn = targetpathafterfn
1203 copylist = []
1203 copylist = []
1204 for pat in pats:
1204 for pat in pats:
1205 srcs = walkpat(pat)
1205 srcs = walkpat(pat)
1206 if not srcs:
1206 if not srcs:
1207 continue
1207 continue
1208 copylist.append((tfn(pat, dest, srcs), srcs))
1208 copylist.append((tfn(pat, dest, srcs), srcs))
1209 if not copylist:
1209 if not copylist:
1210 raise error.Abort(_('no files to copy'))
1210 raise error.Abort(_('no files to copy'))
1211
1211
1212 errors = 0
1212 errors = 0
1213 for targetpath, srcs in copylist:
1213 for targetpath, srcs in copylist:
1214 for abssrc, relsrc, exact in srcs:
1214 for abssrc, relsrc, exact in srcs:
1215 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1215 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1216 errors += 1
1216 errors += 1
1217
1217
1218 if errors:
1218 if errors:
1219 ui.warn(_('(consider using --after)\n'))
1219 ui.warn(_('(consider using --after)\n'))
1220
1220
1221 return errors != 0
1221 return errors != 0
1222
1222
1223 ## facility to let extension process additional data into an import patch
1223 ## facility to let extension process additional data into an import patch
1224 # list of identifier to be executed in order
1224 # list of identifier to be executed in order
1225 extrapreimport = [] # run before commit
1225 extrapreimport = [] # run before commit
1226 extrapostimport = [] # run after commit
1226 extrapostimport = [] # run after commit
1227 # mapping from identifier to actual import function
1227 # mapping from identifier to actual import function
1228 #
1228 #
1229 # 'preimport' are run before the commit is made and are provided the following
1229 # 'preimport' are run before the commit is made and are provided the following
1230 # arguments:
1230 # arguments:
1231 # - repo: the localrepository instance,
1231 # - repo: the localrepository instance,
1232 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1232 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1233 # - extra: the future extra dictionary of the changeset, please mutate it,
1233 # - extra: the future extra dictionary of the changeset, please mutate it,
1234 # - opts: the import options.
1234 # - opts: the import options.
1235 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1235 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1236 # mutation of in memory commit and more. Feel free to rework the code to get
1236 # mutation of in memory commit and more. Feel free to rework the code to get
1237 # there.
1237 # there.
1238 extrapreimportmap = {}
1238 extrapreimportmap = {}
1239 # 'postimport' are run after the commit is made and are provided the following
1239 # 'postimport' are run after the commit is made and are provided the following
1240 # argument:
1240 # argument:
1241 # - ctx: the changectx created by import.
1241 # - ctx: the changectx created by import.
1242 extrapostimportmap = {}
1242 extrapostimportmap = {}
1243
1243
1244 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1244 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1245 """Utility function used by commands.import to import a single patch
1245 """Utility function used by commands.import to import a single patch
1246
1246
1247 This function is explicitly defined here to help the evolve extension to
1247 This function is explicitly defined here to help the evolve extension to
1248 wrap this part of the import logic.
1248 wrap this part of the import logic.
1249
1249
1250 The API is currently a bit ugly because it a simple code translation from
1250 The API is currently a bit ugly because it a simple code translation from
1251 the import command. Feel free to make it better.
1251 the import command. Feel free to make it better.
1252
1252
1253 :hunk: a patch (as a binary string)
1253 :hunk: a patch (as a binary string)
1254 :parents: nodes that will be parent of the created commit
1254 :parents: nodes that will be parent of the created commit
1255 :opts: the full dict of option passed to the import command
1255 :opts: the full dict of option passed to the import command
1256 :msgs: list to save commit message to.
1256 :msgs: list to save commit message to.
1257 (used in case we need to save it when failing)
1257 (used in case we need to save it when failing)
1258 :updatefunc: a function that update a repo to a given node
1258 :updatefunc: a function that update a repo to a given node
1259 updatefunc(<repo>, <node>)
1259 updatefunc(<repo>, <node>)
1260 """
1260 """
1261 # avoid cycle context -> subrepo -> cmdutil
1261 # avoid cycle context -> subrepo -> cmdutil
1262 from . import context
1262 from . import context
1263 extractdata = patch.extract(ui, hunk)
1263 extractdata = patch.extract(ui, hunk)
1264 tmpname = extractdata.get('filename')
1264 tmpname = extractdata.get('filename')
1265 message = extractdata.get('message')
1265 message = extractdata.get('message')
1266 user = opts.get('user') or extractdata.get('user')
1266 user = opts.get('user') or extractdata.get('user')
1267 date = opts.get('date') or extractdata.get('date')
1267 date = opts.get('date') or extractdata.get('date')
1268 branch = extractdata.get('branch')
1268 branch = extractdata.get('branch')
1269 nodeid = extractdata.get('nodeid')
1269 nodeid = extractdata.get('nodeid')
1270 p1 = extractdata.get('p1')
1270 p1 = extractdata.get('p1')
1271 p2 = extractdata.get('p2')
1271 p2 = extractdata.get('p2')
1272
1272
1273 nocommit = opts.get('no_commit')
1273 nocommit = opts.get('no_commit')
1274 importbranch = opts.get('import_branch')
1274 importbranch = opts.get('import_branch')
1275 update = not opts.get('bypass')
1275 update = not opts.get('bypass')
1276 strip = opts["strip"]
1276 strip = opts["strip"]
1277 prefix = opts["prefix"]
1277 prefix = opts["prefix"]
1278 sim = float(opts.get('similarity') or 0)
1278 sim = float(opts.get('similarity') or 0)
1279 if not tmpname:
1279 if not tmpname:
1280 return (None, None, False)
1280 return (None, None, False)
1281
1281
1282 rejects = False
1282 rejects = False
1283
1283
1284 try:
1284 try:
1285 cmdline_message = logmessage(ui, opts)
1285 cmdline_message = logmessage(ui, opts)
1286 if cmdline_message:
1286 if cmdline_message:
1287 # pickup the cmdline msg
1287 # pickup the cmdline msg
1288 message = cmdline_message
1288 message = cmdline_message
1289 elif message:
1289 elif message:
1290 # pickup the patch msg
1290 # pickup the patch msg
1291 message = message.strip()
1291 message = message.strip()
1292 else:
1292 else:
1293 # launch the editor
1293 # launch the editor
1294 message = None
1294 message = None
1295 ui.debug('message:\n%s\n' % message)
1295 ui.debug('message:\n%s\n' % message)
1296
1296
1297 if len(parents) == 1:
1297 if len(parents) == 1:
1298 parents.append(repo[nullid])
1298 parents.append(repo[nullid])
1299 if opts.get('exact'):
1299 if opts.get('exact'):
1300 if not nodeid or not p1:
1300 if not nodeid or not p1:
1301 raise error.Abort(_('not a Mercurial patch'))
1301 raise error.Abort(_('not a Mercurial patch'))
1302 p1 = repo[p1]
1302 p1 = repo[p1]
1303 p2 = repo[p2 or nullid]
1303 p2 = repo[p2 or nullid]
1304 elif p2:
1304 elif p2:
1305 try:
1305 try:
1306 p1 = repo[p1]
1306 p1 = repo[p1]
1307 p2 = repo[p2]
1307 p2 = repo[p2]
1308 # Without any options, consider p2 only if the
1308 # Without any options, consider p2 only if the
1309 # patch is being applied on top of the recorded
1309 # patch is being applied on top of the recorded
1310 # first parent.
1310 # first parent.
1311 if p1 != parents[0]:
1311 if p1 != parents[0]:
1312 p1 = parents[0]
1312 p1 = parents[0]
1313 p2 = repo[nullid]
1313 p2 = repo[nullid]
1314 except error.RepoError:
1314 except error.RepoError:
1315 p1, p2 = parents
1315 p1, p2 = parents
1316 if p2.node() == nullid:
1316 if p2.node() == nullid:
1317 ui.warn(_("warning: import the patch as a normal revision\n"
1317 ui.warn(_("warning: import the patch as a normal revision\n"
1318 "(use --exact to import the patch as a merge)\n"))
1318 "(use --exact to import the patch as a merge)\n"))
1319 else:
1319 else:
1320 p1, p2 = parents
1320 p1, p2 = parents
1321
1321
1322 n = None
1322 n = None
1323 if update:
1323 if update:
1324 if p1 != parents[0]:
1324 if p1 != parents[0]:
1325 updatefunc(repo, p1.node())
1325 updatefunc(repo, p1.node())
1326 if p2 != parents[1]:
1326 if p2 != parents[1]:
1327 repo.setparents(p1.node(), p2.node())
1327 repo.setparents(p1.node(), p2.node())
1328
1328
1329 if opts.get('exact') or importbranch:
1329 if opts.get('exact') or importbranch:
1330 repo.dirstate.setbranch(branch or 'default')
1330 repo.dirstate.setbranch(branch or 'default')
1331
1331
1332 partial = opts.get('partial', False)
1332 partial = opts.get('partial', False)
1333 files = set()
1333 files = set()
1334 try:
1334 try:
1335 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1335 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1336 files=files, eolmode=None, similarity=sim / 100.0)
1336 files=files, eolmode=None, similarity=sim / 100.0)
1337 except patch.PatchError as e:
1337 except patch.PatchError as e:
1338 if not partial:
1338 if not partial:
1339 raise error.Abort(str(e))
1339 raise error.Abort(str(e))
1340 if partial:
1340 if partial:
1341 rejects = True
1341 rejects = True
1342
1342
1343 files = list(files)
1343 files = list(files)
1344 if nocommit:
1344 if nocommit:
1345 if message:
1345 if message:
1346 msgs.append(message)
1346 msgs.append(message)
1347 else:
1347 else:
1348 if opts.get('exact') or p2:
1348 if opts.get('exact') or p2:
1349 # If you got here, you either use --force and know what
1349 # If you got here, you either use --force and know what
1350 # you are doing or used --exact or a merge patch while
1350 # you are doing or used --exact or a merge patch while
1351 # being updated to its first parent.
1351 # being updated to its first parent.
1352 m = None
1352 m = None
1353 else:
1353 else:
1354 m = scmutil.matchfiles(repo, files or [])
1354 m = scmutil.matchfiles(repo, files or [])
1355 editform = mergeeditform(repo[None], 'import.normal')
1355 editform = mergeeditform(repo[None], 'import.normal')
1356 if opts.get('exact'):
1356 if opts.get('exact'):
1357 editor = None
1357 editor = None
1358 else:
1358 else:
1359 editor = getcommiteditor(editform=editform, **opts)
1359 editor = getcommiteditor(editform=editform, **opts)
1360 extra = {}
1360 extra = {}
1361 for idfunc in extrapreimport:
1361 for idfunc in extrapreimport:
1362 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1362 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1363 overrides = {}
1363 overrides = {}
1364 if partial:
1364 if partial:
1365 overrides[('ui', 'allowemptycommit')] = True
1365 overrides[('ui', 'allowemptycommit')] = True
1366 with repo.ui.configoverride(overrides, 'import'):
1366 with repo.ui.configoverride(overrides, 'import'):
1367 n = repo.commit(message, user,
1367 n = repo.commit(message, user,
1368 date, match=m,
1368 date, match=m,
1369 editor=editor, extra=extra)
1369 editor=editor, extra=extra)
1370 for idfunc in extrapostimport:
1370 for idfunc in extrapostimport:
1371 extrapostimportmap[idfunc](repo[n])
1371 extrapostimportmap[idfunc](repo[n])
1372 else:
1372 else:
1373 if opts.get('exact') or importbranch:
1373 if opts.get('exact') or importbranch:
1374 branch = branch or 'default'
1374 branch = branch or 'default'
1375 else:
1375 else:
1376 branch = p1.branch()
1376 branch = p1.branch()
1377 store = patch.filestore()
1377 store = patch.filestore()
1378 try:
1378 try:
1379 files = set()
1379 files = set()
1380 try:
1380 try:
1381 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1381 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1382 files, eolmode=None)
1382 files, eolmode=None)
1383 except patch.PatchError as e:
1383 except patch.PatchError as e:
1384 raise error.Abort(str(e))
1384 raise error.Abort(str(e))
1385 if opts.get('exact'):
1385 if opts.get('exact'):
1386 editor = None
1386 editor = None
1387 else:
1387 else:
1388 editor = getcommiteditor(editform='import.bypass')
1388 editor = getcommiteditor(editform='import.bypass')
1389 memctx = context.memctx(repo, (p1.node(), p2.node()),
1389 memctx = context.memctx(repo, (p1.node(), p2.node()),
1390 message,
1390 message,
1391 files=files,
1391 files=files,
1392 filectxfn=store,
1392 filectxfn=store,
1393 user=user,
1393 user=user,
1394 date=date,
1394 date=date,
1395 branch=branch,
1395 branch=branch,
1396 editor=editor)
1396 editor=editor)
1397 n = memctx.commit()
1397 n = memctx.commit()
1398 finally:
1398 finally:
1399 store.close()
1399 store.close()
1400 if opts.get('exact') and nocommit:
1400 if opts.get('exact') and nocommit:
1401 # --exact with --no-commit is still useful in that it does merge
1401 # --exact with --no-commit is still useful in that it does merge
1402 # and branch bits
1402 # and branch bits
1403 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1403 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1404 elif opts.get('exact') and hex(n) != nodeid:
1404 elif opts.get('exact') and hex(n) != nodeid:
1405 raise error.Abort(_('patch is damaged or loses information'))
1405 raise error.Abort(_('patch is damaged or loses information'))
1406 msg = _('applied to working directory')
1406 msg = _('applied to working directory')
1407 if n:
1407 if n:
1408 # i18n: refers to a short changeset id
1408 # i18n: refers to a short changeset id
1409 msg = _('created %s') % short(n)
1409 msg = _('created %s') % short(n)
1410 return (msg, n, rejects)
1410 return (msg, n, rejects)
1411 finally:
1411 finally:
1412 os.unlink(tmpname)
1412 os.unlink(tmpname)
1413
1413
1414 # facility to let extensions include additional data in an exported patch
1414 # facility to let extensions include additional data in an exported patch
1415 # list of identifiers to be executed in order
1415 # list of identifiers to be executed in order
1416 extraexport = []
1416 extraexport = []
1417 # mapping from identifier to actual export function
1417 # mapping from identifier to actual export function
1418 # function as to return a string to be added to the header or None
1418 # function as to return a string to be added to the header or None
1419 # it is given two arguments (sequencenumber, changectx)
1419 # it is given two arguments (sequencenumber, changectx)
1420 extraexportmap = {}
1420 extraexportmap = {}
1421
1421
1422 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1422 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1423 node = scmutil.binnode(ctx)
1423 node = scmutil.binnode(ctx)
1424 parents = [p.node() for p in ctx.parents() if p]
1424 parents = [p.node() for p in ctx.parents() if p]
1425 branch = ctx.branch()
1425 branch = ctx.branch()
1426 if switch_parent:
1426 if switch_parent:
1427 parents.reverse()
1427 parents.reverse()
1428
1428
1429 if parents:
1429 if parents:
1430 prev = parents[0]
1430 prev = parents[0]
1431 else:
1431 else:
1432 prev = nullid
1432 prev = nullid
1433
1433
1434 write("# HG changeset patch\n")
1434 write("# HG changeset patch\n")
1435 write("# User %s\n" % ctx.user())
1435 write("# User %s\n" % ctx.user())
1436 write("# Date %d %d\n" % ctx.date())
1436 write("# Date %d %d\n" % ctx.date())
1437 write("# %s\n" % util.datestr(ctx.date()))
1437 write("# %s\n" % util.datestr(ctx.date()))
1438 if branch and branch != 'default':
1438 if branch and branch != 'default':
1439 write("# Branch %s\n" % branch)
1439 write("# Branch %s\n" % branch)
1440 write("# Node ID %s\n" % hex(node))
1440 write("# Node ID %s\n" % hex(node))
1441 write("# Parent %s\n" % hex(prev))
1441 write("# Parent %s\n" % hex(prev))
1442 if len(parents) > 1:
1442 if len(parents) > 1:
1443 write("# Parent %s\n" % hex(parents[1]))
1443 write("# Parent %s\n" % hex(parents[1]))
1444
1444
1445 for headerid in extraexport:
1445 for headerid in extraexport:
1446 header = extraexportmap[headerid](seqno, ctx)
1446 header = extraexportmap[headerid](seqno, ctx)
1447 if header is not None:
1447 if header is not None:
1448 write('# %s\n' % header)
1448 write('# %s\n' % header)
1449 write(ctx.description().rstrip())
1449 write(ctx.description().rstrip())
1450 write("\n\n")
1450 write("\n\n")
1451
1451
1452 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1452 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1453 write(chunk, label=label)
1453 write(chunk, label=label)
1454
1454
1455 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1455 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1456 opts=None, match=None):
1456 opts=None, match=None):
1457 '''export changesets as hg patches
1457 '''export changesets as hg patches
1458
1458
1459 Args:
1459 Args:
1460 repo: The repository from which we're exporting revisions.
1460 repo: The repository from which we're exporting revisions.
1461 revs: A list of revisions to export as revision numbers.
1461 revs: A list of revisions to export as revision numbers.
1462 fntemplate: An optional string to use for generating patch file names.
1462 fntemplate: An optional string to use for generating patch file names.
1463 fp: An optional file-like object to which patches should be written.
1463 fp: An optional file-like object to which patches should be written.
1464 switch_parent: If True, show diffs against second parent when not nullid.
1464 switch_parent: If True, show diffs against second parent when not nullid.
1465 Default is false, which always shows diff against p1.
1465 Default is false, which always shows diff against p1.
1466 opts: diff options to use for generating the patch.
1466 opts: diff options to use for generating the patch.
1467 match: If specified, only export changes to files matching this matcher.
1467 match: If specified, only export changes to files matching this matcher.
1468
1468
1469 Returns:
1469 Returns:
1470 Nothing.
1470 Nothing.
1471
1471
1472 Side Effect:
1472 Side Effect:
1473 "HG Changeset Patch" data is emitted to one of the following
1473 "HG Changeset Patch" data is emitted to one of the following
1474 destinations:
1474 destinations:
1475 fp is specified: All revs are written to the specified
1475 fp is specified: All revs are written to the specified
1476 file-like object.
1476 file-like object.
1477 fntemplate specified: Each rev is written to a unique file named using
1477 fntemplate specified: Each rev is written to a unique file named using
1478 the given template.
1478 the given template.
1479 Neither fp nor template specified: All revs written to repo.ui.write()
1479 Neither fp nor template specified: All revs written to repo.ui.write()
1480 '''
1480 '''
1481
1481
1482 total = len(revs)
1482 total = len(revs)
1483 revwidth = max(len(str(rev)) for rev in revs)
1483 revwidth = max(len(str(rev)) for rev in revs)
1484 filemode = {}
1484 filemode = {}
1485
1485
1486 write = None
1486 write = None
1487 dest = '<unnamed>'
1487 dest = '<unnamed>'
1488 if fp:
1488 if fp:
1489 dest = getattr(fp, 'name', dest)
1489 dest = getattr(fp, 'name', dest)
1490 def write(s, **kw):
1490 def write(s, **kw):
1491 fp.write(s)
1491 fp.write(s)
1492 elif not fntemplate:
1492 elif not fntemplate:
1493 write = repo.ui.write
1493 write = repo.ui.write
1494
1494
1495 for seqno, rev in enumerate(revs, 1):
1495 for seqno, rev in enumerate(revs, 1):
1496 ctx = repo[rev]
1496 ctx = repo[rev]
1497 fo = None
1497 fo = None
1498 if not fp and fntemplate:
1498 if not fp and fntemplate:
1499 desc_lines = ctx.description().rstrip().split('\n')
1499 desc_lines = ctx.description().rstrip().split('\n')
1500 desc = desc_lines[0] #Commit always has a first line.
1500 desc = desc_lines[0] #Commit always has a first line.
1501 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1501 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1502 total=total, seqno=seqno, revwidth=revwidth,
1502 total=total, seqno=seqno, revwidth=revwidth,
1503 mode='wb', modemap=filemode)
1503 mode='wb', modemap=filemode)
1504 dest = fo.name
1504 dest = fo.name
1505 def write(s, **kw):
1505 def write(s, **kw):
1506 fo.write(s)
1506 fo.write(s)
1507 if not dest.startswith('<'):
1507 if not dest.startswith('<'):
1508 repo.ui.note("%s\n" % dest)
1508 repo.ui.note("%s\n" % dest)
1509 _exportsingle(
1509 _exportsingle(
1510 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1510 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1511 if fo is not None:
1511 if fo is not None:
1512 fo.close()
1512 fo.close()
1513
1513
1514 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1514 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1515 changes=None, stat=False, fp=None, prefix='',
1515 changes=None, stat=False, fp=None, prefix='',
1516 root='', listsubrepos=False):
1516 root='', listsubrepos=False):
1517 '''show diff or diffstat.'''
1517 '''show diff or diffstat.'''
1518 if fp is None:
1518 if fp is None:
1519 write = ui.write
1519 write = ui.write
1520 else:
1520 else:
1521 def write(s, **kw):
1521 def write(s, **kw):
1522 fp.write(s)
1522 fp.write(s)
1523
1523
1524 if root:
1524 if root:
1525 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1525 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1526 else:
1526 else:
1527 relroot = ''
1527 relroot = ''
1528 if relroot != '':
1528 if relroot != '':
1529 # XXX relative roots currently don't work if the root is within a
1529 # XXX relative roots currently don't work if the root is within a
1530 # subrepo
1530 # subrepo
1531 uirelroot = match.uipath(relroot)
1531 uirelroot = match.uipath(relroot)
1532 relroot += '/'
1532 relroot += '/'
1533 for matchroot in match.files():
1533 for matchroot in match.files():
1534 if not matchroot.startswith(relroot):
1534 if not matchroot.startswith(relroot):
1535 ui.warn(_('warning: %s not inside relative root %s\n') % (
1535 ui.warn(_('warning: %s not inside relative root %s\n') % (
1536 match.uipath(matchroot), uirelroot))
1536 match.uipath(matchroot), uirelroot))
1537
1537
1538 if stat:
1538 if stat:
1539 diffopts = diffopts.copy(context=0)
1539 diffopts = diffopts.copy(context=0)
1540 width = 80
1540 width = 80
1541 if not ui.plain():
1541 if not ui.plain():
1542 width = ui.termwidth()
1542 width = ui.termwidth()
1543 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1543 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1544 prefix=prefix, relroot=relroot)
1544 prefix=prefix, relroot=relroot)
1545 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1545 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1546 width=width):
1546 width=width):
1547 write(chunk, label=label)
1547 write(chunk, label=label)
1548 else:
1548 else:
1549 for chunk, label in patch.diffui(repo, node1, node2, match,
1549 for chunk, label in patch.diffui(repo, node1, node2, match,
1550 changes, diffopts, prefix=prefix,
1550 changes, diffopts, prefix=prefix,
1551 relroot=relroot):
1551 relroot=relroot):
1552 write(chunk, label=label)
1552 write(chunk, label=label)
1553
1553
1554 if listsubrepos:
1554 if listsubrepos:
1555 ctx1 = repo[node1]
1555 ctx1 = repo[node1]
1556 ctx2 = repo[node2]
1556 ctx2 = repo[node2]
1557 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1557 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1558 tempnode2 = node2
1558 tempnode2 = node2
1559 try:
1559 try:
1560 if node2 is not None:
1560 if node2 is not None:
1561 tempnode2 = ctx2.substate[subpath][1]
1561 tempnode2 = ctx2.substate[subpath][1]
1562 except KeyError:
1562 except KeyError:
1563 # A subrepo that existed in node1 was deleted between node1 and
1563 # A subrepo that existed in node1 was deleted between node1 and
1564 # node2 (inclusive). Thus, ctx2's substate won't contain that
1564 # node2 (inclusive). Thus, ctx2's substate won't contain that
1565 # subpath. The best we can do is to ignore it.
1565 # subpath. The best we can do is to ignore it.
1566 tempnode2 = None
1566 tempnode2 = None
1567 submatch = matchmod.subdirmatcher(subpath, match)
1567 submatch = matchmod.subdirmatcher(subpath, match)
1568 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1568 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1569 stat=stat, fp=fp, prefix=prefix)
1569 stat=stat, fp=fp, prefix=prefix)
1570
1570
1571 def _changesetlabels(ctx):
1571 def _changesetlabels(ctx):
1572 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1572 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1573 if ctx.obsolete():
1573 if ctx.obsolete():
1574 labels.append('changeset.obsolete')
1574 labels.append('changeset.obsolete')
1575 if ctx.isunstable():
1575 if ctx.isunstable():
1576 labels.append('changeset.unstable')
1576 labels.append('changeset.unstable')
1577 for instability in ctx.instabilities():
1577 for instability in ctx.instabilities():
1578 labels.append('instability.%s' % instability)
1578 labels.append('instability.%s' % instability)
1579 return ' '.join(labels)
1579 return ' '.join(labels)
1580
1580
1581 class changeset_printer(object):
1581 class changeset_printer(object):
1582 '''show changeset information when templating not requested.'''
1582 '''show changeset information when templating not requested.'''
1583
1583
1584 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1584 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1585 self.ui = ui
1585 self.ui = ui
1586 self.repo = repo
1586 self.repo = repo
1587 self.buffered = buffered
1587 self.buffered = buffered
1588 self.matchfn = matchfn
1588 self.matchfn = matchfn
1589 self.diffopts = diffopts
1589 self.diffopts = diffopts
1590 self.header = {}
1590 self.header = {}
1591 self.hunk = {}
1591 self.hunk = {}
1592 self.lastheader = None
1592 self.lastheader = None
1593 self.footer = None
1593 self.footer = None
1594
1594
1595 def flush(self, ctx):
1595 def flush(self, ctx):
1596 rev = ctx.rev()
1596 rev = ctx.rev()
1597 if rev in self.header:
1597 if rev in self.header:
1598 h = self.header[rev]
1598 h = self.header[rev]
1599 if h != self.lastheader:
1599 if h != self.lastheader:
1600 self.lastheader = h
1600 self.lastheader = h
1601 self.ui.write(h)
1601 self.ui.write(h)
1602 del self.header[rev]
1602 del self.header[rev]
1603 if rev in self.hunk:
1603 if rev in self.hunk:
1604 self.ui.write(self.hunk[rev])
1604 self.ui.write(self.hunk[rev])
1605 del self.hunk[rev]
1605 del self.hunk[rev]
1606 return 1
1606 return 1
1607 return 0
1607 return 0
1608
1608
1609 def close(self):
1609 def close(self):
1610 if self.footer:
1610 if self.footer:
1611 self.ui.write(self.footer)
1611 self.ui.write(self.footer)
1612
1612
1613 def show(self, ctx, copies=None, matchfn=None, **props):
1613 def show(self, ctx, copies=None, matchfn=None, **props):
1614 props = pycompat.byteskwargs(props)
1614 props = pycompat.byteskwargs(props)
1615 if self.buffered:
1615 if self.buffered:
1616 self.ui.pushbuffer(labeled=True)
1616 self.ui.pushbuffer(labeled=True)
1617 self._show(ctx, copies, matchfn, props)
1617 self._show(ctx, copies, matchfn, props)
1618 self.hunk[ctx.rev()] = self.ui.popbuffer()
1618 self.hunk[ctx.rev()] = self.ui.popbuffer()
1619 else:
1619 else:
1620 self._show(ctx, copies, matchfn, props)
1620 self._show(ctx, copies, matchfn, props)
1621
1621
1622 def _show(self, ctx, copies, matchfn, props):
1622 def _show(self, ctx, copies, matchfn, props):
1623 '''show a single changeset or file revision'''
1623 '''show a single changeset or file revision'''
1624 changenode = ctx.node()
1624 changenode = ctx.node()
1625 rev = ctx.rev()
1625 rev = ctx.rev()
1626 if self.ui.debugflag:
1626 if self.ui.debugflag:
1627 hexfunc = hex
1627 hexfunc = hex
1628 else:
1628 else:
1629 hexfunc = short
1629 hexfunc = short
1630 # as of now, wctx.node() and wctx.rev() return None, but we want to
1630 # as of now, wctx.node() and wctx.rev() return None, but we want to
1631 # show the same values as {node} and {rev} templatekw
1631 # show the same values as {node} and {rev} templatekw
1632 revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
1632 revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
1633
1633
1634 if self.ui.quiet:
1634 if self.ui.quiet:
1635 self.ui.write("%d:%s\n" % revnode, label='log.node')
1635 self.ui.write("%d:%s\n" % revnode, label='log.node')
1636 return
1636 return
1637
1637
1638 date = util.datestr(ctx.date())
1638 date = util.datestr(ctx.date())
1639
1639
1640 # i18n: column positioning for "hg log"
1640 # i18n: column positioning for "hg log"
1641 self.ui.write(_("changeset: %d:%s\n") % revnode,
1641 self.ui.write(_("changeset: %d:%s\n") % revnode,
1642 label=_changesetlabels(ctx))
1642 label=_changesetlabels(ctx))
1643
1643
1644 # branches are shown first before any other names due to backwards
1644 # branches are shown first before any other names due to backwards
1645 # compatibility
1645 # compatibility
1646 branch = ctx.branch()
1646 branch = ctx.branch()
1647 # don't show the default branch name
1647 # don't show the default branch name
1648 if branch != 'default':
1648 if branch != 'default':
1649 # i18n: column positioning for "hg log"
1649 # i18n: column positioning for "hg log"
1650 self.ui.write(_("branch: %s\n") % branch,
1650 self.ui.write(_("branch: %s\n") % branch,
1651 label='log.branch')
1651 label='log.branch')
1652
1652
1653 for nsname, ns in self.repo.names.iteritems():
1653 for nsname, ns in self.repo.names.iteritems():
1654 # branches has special logic already handled above, so here we just
1654 # branches has special logic already handled above, so here we just
1655 # skip it
1655 # skip it
1656 if nsname == 'branches':
1656 if nsname == 'branches':
1657 continue
1657 continue
1658 # we will use the templatename as the color name since those two
1658 # we will use the templatename as the color name since those two
1659 # should be the same
1659 # should be the same
1660 for name in ns.names(self.repo, changenode):
1660 for name in ns.names(self.repo, changenode):
1661 self.ui.write(ns.logfmt % name,
1661 self.ui.write(ns.logfmt % name,
1662 label='log.%s' % ns.colorname)
1662 label='log.%s' % ns.colorname)
1663 if self.ui.debugflag:
1663 if self.ui.debugflag:
1664 # i18n: column positioning for "hg log"
1664 # i18n: column positioning for "hg log"
1665 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1665 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1666 label='log.phase')
1666 label='log.phase')
1667 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1667 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1668 label = 'log.parent changeset.%s' % pctx.phasestr()
1668 label = 'log.parent changeset.%s' % pctx.phasestr()
1669 # i18n: column positioning for "hg log"
1669 # i18n: column positioning for "hg log"
1670 self.ui.write(_("parent: %d:%s\n")
1670 self.ui.write(_("parent: %d:%s\n")
1671 % (pctx.rev(), hexfunc(pctx.node())),
1671 % (pctx.rev(), hexfunc(pctx.node())),
1672 label=label)
1672 label=label)
1673
1673
1674 if self.ui.debugflag and rev is not None:
1674 if self.ui.debugflag and rev is not None:
1675 mnode = ctx.manifestnode()
1675 mnode = ctx.manifestnode()
1676 # i18n: column positioning for "hg log"
1676 # i18n: column positioning for "hg log"
1677 self.ui.write(_("manifest: %d:%s\n") %
1677 self.ui.write(_("manifest: %d:%s\n") %
1678 (self.repo.manifestlog._revlog.rev(mnode),
1678 (self.repo.manifestlog._revlog.rev(mnode),
1679 hex(mnode)),
1679 hex(mnode)),
1680 label='ui.debug log.manifest')
1680 label='ui.debug log.manifest')
1681 # i18n: column positioning for "hg log"
1681 # i18n: column positioning for "hg log"
1682 self.ui.write(_("user: %s\n") % ctx.user(),
1682 self.ui.write(_("user: %s\n") % ctx.user(),
1683 label='log.user')
1683 label='log.user')
1684 # i18n: column positioning for "hg log"
1684 # i18n: column positioning for "hg log"
1685 self.ui.write(_("date: %s\n") % date,
1685 self.ui.write(_("date: %s\n") % date,
1686 label='log.date')
1686 label='log.date')
1687
1687
1688 if ctx.isunstable():
1688 if ctx.isunstable():
1689 # i18n: column positioning for "hg log"
1689 # i18n: column positioning for "hg log"
1690 instabilities = ctx.instabilities()
1690 instabilities = ctx.instabilities()
1691 self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
1691 self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
1692 label='log.instability')
1692 label='log.instability')
1693
1693
1694 self._exthook(ctx)
1694 self._exthook(ctx)
1695
1695
1696 if self.ui.debugflag:
1696 if self.ui.debugflag:
1697 files = ctx.p1().status(ctx)[:3]
1697 files = ctx.p1().status(ctx)[:3]
1698 for key, value in zip([# i18n: column positioning for "hg log"
1698 for key, value in zip([# i18n: column positioning for "hg log"
1699 _("files:"),
1699 _("files:"),
1700 # i18n: column positioning for "hg log"
1700 # i18n: column positioning for "hg log"
1701 _("files+:"),
1701 _("files+:"),
1702 # i18n: column positioning for "hg log"
1702 # i18n: column positioning for "hg log"
1703 _("files-:")], files):
1703 _("files-:")], files):
1704 if value:
1704 if value:
1705 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1705 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1706 label='ui.debug log.files')
1706 label='ui.debug log.files')
1707 elif ctx.files() and self.ui.verbose:
1707 elif ctx.files() and self.ui.verbose:
1708 # i18n: column positioning for "hg log"
1708 # i18n: column positioning for "hg log"
1709 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1709 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1710 label='ui.note log.files')
1710 label='ui.note log.files')
1711 if copies and self.ui.verbose:
1711 if copies and self.ui.verbose:
1712 copies = ['%s (%s)' % c for c in copies]
1712 copies = ['%s (%s)' % c for c in copies]
1713 # i18n: column positioning for "hg log"
1713 # i18n: column positioning for "hg log"
1714 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1714 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1715 label='ui.note log.copies')
1715 label='ui.note log.copies')
1716
1716
1717 extra = ctx.extra()
1717 extra = ctx.extra()
1718 if extra and self.ui.debugflag:
1718 if extra and self.ui.debugflag:
1719 for key, value in sorted(extra.items()):
1719 for key, value in sorted(extra.items()):
1720 # i18n: column positioning for "hg log"
1720 # i18n: column positioning for "hg log"
1721 self.ui.write(_("extra: %s=%s\n")
1721 self.ui.write(_("extra: %s=%s\n")
1722 % (key, util.escapestr(value)),
1722 % (key, util.escapestr(value)),
1723 label='ui.debug log.extra')
1723 label='ui.debug log.extra')
1724
1724
1725 description = ctx.description().strip()
1725 description = ctx.description().strip()
1726 if description:
1726 if description:
1727 if self.ui.verbose:
1727 if self.ui.verbose:
1728 self.ui.write(_("description:\n"),
1728 self.ui.write(_("description:\n"),
1729 label='ui.note log.description')
1729 label='ui.note log.description')
1730 self.ui.write(description,
1730 self.ui.write(description,
1731 label='ui.note log.description')
1731 label='ui.note log.description')
1732 self.ui.write("\n\n")
1732 self.ui.write("\n\n")
1733 else:
1733 else:
1734 # i18n: column positioning for "hg log"
1734 # i18n: column positioning for "hg log"
1735 self.ui.write(_("summary: %s\n") %
1735 self.ui.write(_("summary: %s\n") %
1736 description.splitlines()[0],
1736 description.splitlines()[0],
1737 label='log.summary')
1737 label='log.summary')
1738 self.ui.write("\n")
1738 self.ui.write("\n")
1739
1739
1740 self.showpatch(ctx, matchfn)
1740 self.showpatch(ctx, matchfn)
1741
1741
1742 def _exthook(self, ctx):
1742 def _exthook(self, ctx):
1743 '''empty method used by extension as a hook point
1743 '''empty method used by extension as a hook point
1744 '''
1744 '''
1745 pass
1745 pass
1746
1746
1747 def showpatch(self, ctx, matchfn):
1747 def showpatch(self, ctx, matchfn):
1748 if not matchfn:
1748 if not matchfn:
1749 matchfn = self.matchfn
1749 matchfn = self.matchfn
1750 if matchfn:
1750 if matchfn:
1751 stat = self.diffopts.get('stat')
1751 stat = self.diffopts.get('stat')
1752 diff = self.diffopts.get('patch')
1752 diff = self.diffopts.get('patch')
1753 diffopts = patch.diffallopts(self.ui, self.diffopts)
1753 diffopts = patch.diffallopts(self.ui, self.diffopts)
1754 node = ctx.node()
1754 node = ctx.node()
1755 prev = ctx.p1().node()
1755 prev = ctx.p1().node()
1756 if stat:
1756 if stat:
1757 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1757 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1758 match=matchfn, stat=True)
1758 match=matchfn, stat=True)
1759 if diff:
1759 if diff:
1760 if stat:
1760 if stat:
1761 self.ui.write("\n")
1761 self.ui.write("\n")
1762 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1762 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1763 match=matchfn, stat=False)
1763 match=matchfn, stat=False)
1764 self.ui.write("\n")
1764 self.ui.write("\n")
1765
1765
1766 class jsonchangeset(changeset_printer):
1766 class jsonchangeset(changeset_printer):
1767 '''format changeset information.'''
1767 '''format changeset information.'''
1768
1768
1769 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1769 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1770 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1770 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1771 self.cache = {}
1771 self.cache = {}
1772 self._first = True
1772 self._first = True
1773
1773
1774 def close(self):
1774 def close(self):
1775 if not self._first:
1775 if not self._first:
1776 self.ui.write("\n]\n")
1776 self.ui.write("\n]\n")
1777 else:
1777 else:
1778 self.ui.write("[]\n")
1778 self.ui.write("[]\n")
1779
1779
1780 def _show(self, ctx, copies, matchfn, props):
1780 def _show(self, ctx, copies, matchfn, props):
1781 '''show a single changeset or file revision'''
1781 '''show a single changeset or file revision'''
1782 rev = ctx.rev()
1782 rev = ctx.rev()
1783 if rev is None:
1783 if rev is None:
1784 jrev = jnode = 'null'
1784 jrev = jnode = 'null'
1785 else:
1785 else:
1786 jrev = '%d' % rev
1786 jrev = '%d' % rev
1787 jnode = '"%s"' % hex(ctx.node())
1787 jnode = '"%s"' % hex(ctx.node())
1788 j = encoding.jsonescape
1788 j = encoding.jsonescape
1789
1789
1790 if self._first:
1790 if self._first:
1791 self.ui.write("[\n {")
1791 self.ui.write("[\n {")
1792 self._first = False
1792 self._first = False
1793 else:
1793 else:
1794 self.ui.write(",\n {")
1794 self.ui.write(",\n {")
1795
1795
1796 if self.ui.quiet:
1796 if self.ui.quiet:
1797 self.ui.write(('\n "rev": %s') % jrev)
1797 self.ui.write(('\n "rev": %s') % jrev)
1798 self.ui.write((',\n "node": %s') % jnode)
1798 self.ui.write((',\n "node": %s') % jnode)
1799 self.ui.write('\n }')
1799 self.ui.write('\n }')
1800 return
1800 return
1801
1801
1802 self.ui.write(('\n "rev": %s') % jrev)
1802 self.ui.write(('\n "rev": %s') % jrev)
1803 self.ui.write((',\n "node": %s') % jnode)
1803 self.ui.write((',\n "node": %s') % jnode)
1804 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1804 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1805 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1805 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1806 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1806 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1807 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1807 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1808 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1808 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1809
1809
1810 self.ui.write((',\n "bookmarks": [%s]') %
1810 self.ui.write((',\n "bookmarks": [%s]') %
1811 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1811 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1812 self.ui.write((',\n "tags": [%s]') %
1812 self.ui.write((',\n "tags": [%s]') %
1813 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1813 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1814 self.ui.write((',\n "parents": [%s]') %
1814 self.ui.write((',\n "parents": [%s]') %
1815 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1815 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1816
1816
1817 if self.ui.debugflag:
1817 if self.ui.debugflag:
1818 if rev is None:
1818 if rev is None:
1819 jmanifestnode = 'null'
1819 jmanifestnode = 'null'
1820 else:
1820 else:
1821 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1821 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1822 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1822 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1823
1823
1824 self.ui.write((',\n "extra": {%s}') %
1824 self.ui.write((',\n "extra": {%s}') %
1825 ", ".join('"%s": "%s"' % (j(k), j(v))
1825 ", ".join('"%s": "%s"' % (j(k), j(v))
1826 for k, v in ctx.extra().items()))
1826 for k, v in ctx.extra().items()))
1827
1827
1828 files = ctx.p1().status(ctx)
1828 files = ctx.p1().status(ctx)
1829 self.ui.write((',\n "modified": [%s]') %
1829 self.ui.write((',\n "modified": [%s]') %
1830 ", ".join('"%s"' % j(f) for f in files[0]))
1830 ", ".join('"%s"' % j(f) for f in files[0]))
1831 self.ui.write((',\n "added": [%s]') %
1831 self.ui.write((',\n "added": [%s]') %
1832 ", ".join('"%s"' % j(f) for f in files[1]))
1832 ", ".join('"%s"' % j(f) for f in files[1]))
1833 self.ui.write((',\n "removed": [%s]') %
1833 self.ui.write((',\n "removed": [%s]') %
1834 ", ".join('"%s"' % j(f) for f in files[2]))
1834 ", ".join('"%s"' % j(f) for f in files[2]))
1835
1835
1836 elif self.ui.verbose:
1836 elif self.ui.verbose:
1837 self.ui.write((',\n "files": [%s]') %
1837 self.ui.write((',\n "files": [%s]') %
1838 ", ".join('"%s"' % j(f) for f in ctx.files()))
1838 ", ".join('"%s"' % j(f) for f in ctx.files()))
1839
1839
1840 if copies:
1840 if copies:
1841 self.ui.write((',\n "copies": {%s}') %
1841 self.ui.write((',\n "copies": {%s}') %
1842 ", ".join('"%s": "%s"' % (j(k), j(v))
1842 ", ".join('"%s": "%s"' % (j(k), j(v))
1843 for k, v in copies))
1843 for k, v in copies))
1844
1844
1845 matchfn = self.matchfn
1845 matchfn = self.matchfn
1846 if matchfn:
1846 if matchfn:
1847 stat = self.diffopts.get('stat')
1847 stat = self.diffopts.get('stat')
1848 diff = self.diffopts.get('patch')
1848 diff = self.diffopts.get('patch')
1849 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1849 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1850 node, prev = ctx.node(), ctx.p1().node()
1850 node, prev = ctx.node(), ctx.p1().node()
1851 if stat:
1851 if stat:
1852 self.ui.pushbuffer()
1852 self.ui.pushbuffer()
1853 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1853 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1854 match=matchfn, stat=True)
1854 match=matchfn, stat=True)
1855 self.ui.write((',\n "diffstat": "%s"')
1855 self.ui.write((',\n "diffstat": "%s"')
1856 % j(self.ui.popbuffer()))
1856 % j(self.ui.popbuffer()))
1857 if diff:
1857 if diff:
1858 self.ui.pushbuffer()
1858 self.ui.pushbuffer()
1859 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1859 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1860 match=matchfn, stat=False)
1860 match=matchfn, stat=False)
1861 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1861 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1862
1862
1863 self.ui.write("\n }")
1863 self.ui.write("\n }")
1864
1864
1865 class changeset_templater(changeset_printer):
1865 class changeset_templater(changeset_printer):
1866 '''format changeset information.'''
1866 '''format changeset information.'''
1867
1867
1868 # Arguments before "buffered" used to be positional. Consider not
1868 # Arguments before "buffered" used to be positional. Consider not
1869 # adding/removing arguments before "buffered" to not break callers.
1869 # adding/removing arguments before "buffered" to not break callers.
1870 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1870 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1871 buffered=False):
1871 buffered=False):
1872 diffopts = diffopts or {}
1872 diffopts = diffopts or {}
1873
1873
1874 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1874 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1875 self.t = formatter.loadtemplater(ui, tmplspec,
1875 self.t = formatter.loadtemplater(ui, tmplspec,
1876 cache=templatekw.defaulttempl)
1876 cache=templatekw.defaulttempl)
1877 self._counter = itertools.count()
1877 self._counter = itertools.count()
1878 self.cache = {}
1878 self.cache = {}
1879
1879
1880 self._tref = tmplspec.ref
1880 self._tref = tmplspec.ref
1881 self._parts = {'header': '', 'footer': '',
1881 self._parts = {'header': '', 'footer': '',
1882 tmplspec.ref: tmplspec.ref,
1882 tmplspec.ref: tmplspec.ref,
1883 'docheader': '', 'docfooter': '',
1883 'docheader': '', 'docfooter': '',
1884 'separator': ''}
1884 'separator': ''}
1885 if tmplspec.mapfile:
1885 if tmplspec.mapfile:
1886 # find correct templates for current mode, for backward
1886 # find correct templates for current mode, for backward
1887 # compatibility with 'log -v/-q/--debug' using a mapfile
1887 # compatibility with 'log -v/-q/--debug' using a mapfile
1888 tmplmodes = [
1888 tmplmodes = [
1889 (True, ''),
1889 (True, ''),
1890 (self.ui.verbose, '_verbose'),
1890 (self.ui.verbose, '_verbose'),
1891 (self.ui.quiet, '_quiet'),
1891 (self.ui.quiet, '_quiet'),
1892 (self.ui.debugflag, '_debug'),
1892 (self.ui.debugflag, '_debug'),
1893 ]
1893 ]
1894 for mode, postfix in tmplmodes:
1894 for mode, postfix in tmplmodes:
1895 for t in self._parts:
1895 for t in self._parts:
1896 cur = t + postfix
1896 cur = t + postfix
1897 if mode and cur in self.t:
1897 if mode and cur in self.t:
1898 self._parts[t] = cur
1898 self._parts[t] = cur
1899 else:
1899 else:
1900 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1900 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1901 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1901 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1902 self._parts.update(m)
1902 self._parts.update(m)
1903
1903
1904 if self._parts['docheader']:
1904 if self._parts['docheader']:
1905 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1905 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1906
1906
1907 def close(self):
1907 def close(self):
1908 if self._parts['docfooter']:
1908 if self._parts['docfooter']:
1909 if not self.footer:
1909 if not self.footer:
1910 self.footer = ""
1910 self.footer = ""
1911 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1911 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1912 return super(changeset_templater, self).close()
1912 return super(changeset_templater, self).close()
1913
1913
1914 def _show(self, ctx, copies, matchfn, props):
1914 def _show(self, ctx, copies, matchfn, props):
1915 '''show a single changeset or file revision'''
1915 '''show a single changeset or file revision'''
1916 props = props.copy()
1916 props = props.copy()
1917 props.update(templatekw.keywords)
1917 props.update(templatekw.keywords)
1918 props['templ'] = self.t
1918 props['templ'] = self.t
1919 props['ctx'] = ctx
1919 props['ctx'] = ctx
1920 props['repo'] = self.repo
1920 props['repo'] = self.repo
1921 props['ui'] = self.repo.ui
1921 props['ui'] = self.repo.ui
1922 props['index'] = index = next(self._counter)
1922 props['index'] = index = next(self._counter)
1923 props['revcache'] = {'copies': copies}
1923 props['revcache'] = {'copies': copies}
1924 props['cache'] = self.cache
1924 props['cache'] = self.cache
1925 props = pycompat.strkwargs(props)
1925 props = pycompat.strkwargs(props)
1926
1926
1927 # write separator, which wouldn't work well with the header part below
1927 # write separator, which wouldn't work well with the header part below
1928 # since there's inherently a conflict between header (across items) and
1928 # since there's inherently a conflict between header (across items) and
1929 # separator (per item)
1929 # separator (per item)
1930 if self._parts['separator'] and index > 0:
1930 if self._parts['separator'] and index > 0:
1931 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1931 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1932
1932
1933 # write header
1933 # write header
1934 if self._parts['header']:
1934 if self._parts['header']:
1935 h = templater.stringify(self.t(self._parts['header'], **props))
1935 h = templater.stringify(self.t(self._parts['header'], **props))
1936 if self.buffered:
1936 if self.buffered:
1937 self.header[ctx.rev()] = h
1937 self.header[ctx.rev()] = h
1938 else:
1938 else:
1939 if self.lastheader != h:
1939 if self.lastheader != h:
1940 self.lastheader = h
1940 self.lastheader = h
1941 self.ui.write(h)
1941 self.ui.write(h)
1942
1942
1943 # write changeset metadata, then patch if requested
1943 # write changeset metadata, then patch if requested
1944 key = self._parts[self._tref]
1944 key = self._parts[self._tref]
1945 self.ui.write(templater.stringify(self.t(key, **props)))
1945 self.ui.write(templater.stringify(self.t(key, **props)))
1946 self.showpatch(ctx, matchfn)
1946 self.showpatch(ctx, matchfn)
1947
1947
1948 if self._parts['footer']:
1948 if self._parts['footer']:
1949 if not self.footer:
1949 if not self.footer:
1950 self.footer = templater.stringify(
1950 self.footer = templater.stringify(
1951 self.t(self._parts['footer'], **props))
1951 self.t(self._parts['footer'], **props))
1952
1952
1953 def logtemplatespec(tmpl, mapfile):
1953 def logtemplatespec(tmpl, mapfile):
1954 if mapfile:
1954 if mapfile:
1955 return formatter.templatespec('changeset', tmpl, mapfile)
1955 return formatter.templatespec('changeset', tmpl, mapfile)
1956 else:
1956 else:
1957 return formatter.templatespec('', tmpl, None)
1957 return formatter.templatespec('', tmpl, None)
1958
1958
1959 def _lookuplogtemplate(ui, tmpl, style):
1959 def _lookuplogtemplate(ui, tmpl, style):
1960 """Find the template matching the given template spec or style
1960 """Find the template matching the given template spec or style
1961
1961
1962 See formatter.lookuptemplate() for details.
1962 See formatter.lookuptemplate() for details.
1963 """
1963 """
1964
1964
1965 # ui settings
1965 # ui settings
1966 if not tmpl and not style: # template are stronger than style
1966 if not tmpl and not style: # template are stronger than style
1967 tmpl = ui.config('ui', 'logtemplate')
1967 tmpl = ui.config('ui', 'logtemplate')
1968 if tmpl:
1968 if tmpl:
1969 return logtemplatespec(templater.unquotestring(tmpl), None)
1969 return logtemplatespec(templater.unquotestring(tmpl), None)
1970 else:
1970 else:
1971 style = util.expandpath(ui.config('ui', 'style'))
1971 style = util.expandpath(ui.config('ui', 'style'))
1972
1972
1973 if not tmpl and style:
1973 if not tmpl and style:
1974 mapfile = style
1974 mapfile = style
1975 if not os.path.split(mapfile)[0]:
1975 if not os.path.split(mapfile)[0]:
1976 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1976 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1977 or templater.templatepath(mapfile))
1977 or templater.templatepath(mapfile))
1978 if mapname:
1978 if mapname:
1979 mapfile = mapname
1979 mapfile = mapname
1980 return logtemplatespec(None, mapfile)
1980 return logtemplatespec(None, mapfile)
1981
1981
1982 if not tmpl:
1982 if not tmpl:
1983 return logtemplatespec(None, None)
1983 return logtemplatespec(None, None)
1984
1984
1985 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1985 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1986
1986
1987 def makelogtemplater(ui, repo, tmpl, buffered=False):
1987 def makelogtemplater(ui, repo, tmpl, buffered=False):
1988 """Create a changeset_templater from a literal template 'tmpl'"""
1988 """Create a changeset_templater from a literal template 'tmpl'"""
1989 spec = logtemplatespec(tmpl, None)
1989 spec = logtemplatespec(tmpl, None)
1990 return changeset_templater(ui, repo, spec, buffered=buffered)
1990 return changeset_templater(ui, repo, spec, buffered=buffered)
1991
1991
1992 def show_changeset(ui, repo, opts, buffered=False):
1992 def show_changeset(ui, repo, opts, buffered=False):
1993 """show one changeset using template or regular display.
1993 """show one changeset using template or regular display.
1994
1994
1995 Display format will be the first non-empty hit of:
1995 Display format will be the first non-empty hit of:
1996 1. option 'template'
1996 1. option 'template'
1997 2. option 'style'
1997 2. option 'style'
1998 3. [ui] setting 'logtemplate'
1998 3. [ui] setting 'logtemplate'
1999 4. [ui] setting 'style'
1999 4. [ui] setting 'style'
2000 If all of these values are either the unset or the empty string,
2000 If all of these values are either the unset or the empty string,
2001 regular display via changeset_printer() is done.
2001 regular display via changeset_printer() is done.
2002 """
2002 """
2003 # options
2003 # options
2004 match = None
2004 match = None
2005 if opts.get('patch') or opts.get('stat'):
2005 if opts.get('patch') or opts.get('stat'):
2006 match = scmutil.matchall(repo)
2006 match = scmutil.matchall(repo)
2007
2007
2008 if opts.get('template') == 'json':
2008 if opts.get('template') == 'json':
2009 return jsonchangeset(ui, repo, match, opts, buffered)
2009 return jsonchangeset(ui, repo, match, opts, buffered)
2010
2010
2011 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
2011 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
2012
2012
2013 if not spec.ref and not spec.tmpl and not spec.mapfile:
2013 if not spec.ref and not spec.tmpl and not spec.mapfile:
2014 return changeset_printer(ui, repo, match, opts, buffered)
2014 return changeset_printer(ui, repo, match, opts, buffered)
2015
2015
2016 return changeset_templater(ui, repo, spec, match, opts, buffered)
2016 return changeset_templater(ui, repo, spec, match, opts, buffered)
2017
2017
2018 def showmarker(fm, marker, index=None):
2018 def showmarker(fm, marker, index=None):
2019 """utility function to display obsolescence marker in a readable way
2019 """utility function to display obsolescence marker in a readable way
2020
2020
2021 To be used by debug function."""
2021 To be used by debug function."""
2022 if index is not None:
2022 if index is not None:
2023 fm.write('index', '%i ', index)
2023 fm.write('index', '%i ', index)
2024 fm.write('prednode', '%s ', hex(marker.prednode()))
2024 fm.write('prednode', '%s ', hex(marker.prednode()))
2025 succs = marker.succnodes()
2025 succs = marker.succnodes()
2026 fm.condwrite(succs, 'succnodes', '%s ',
2026 fm.condwrite(succs, 'succnodes', '%s ',
2027 fm.formatlist(map(hex, succs), name='node'))
2027 fm.formatlist(map(hex, succs), name='node'))
2028 fm.write('flag', '%X ', marker.flags())
2028 fm.write('flag', '%X ', marker.flags())
2029 parents = marker.parentnodes()
2029 parents = marker.parentnodes()
2030 if parents is not None:
2030 if parents is not None:
2031 fm.write('parentnodes', '{%s} ',
2031 fm.write('parentnodes', '{%s} ',
2032 fm.formatlist(map(hex, parents), name='node', sep=', '))
2032 fm.formatlist(map(hex, parents), name='node', sep=', '))
2033 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2033 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2034 meta = marker.metadata().copy()
2034 meta = marker.metadata().copy()
2035 meta.pop('date', None)
2035 meta.pop('date', None)
2036 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2036 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2037 fm.plain('\n')
2037 fm.plain('\n')
2038
2038
2039 def finddate(ui, repo, date):
2039 def finddate(ui, repo, date):
2040 """Find the tipmost changeset that matches the given date spec"""
2040 """Find the tipmost changeset that matches the given date spec"""
2041
2041
2042 df = util.matchdate(date)
2042 df = util.matchdate(date)
2043 m = scmutil.matchall(repo)
2043 m = scmutil.matchall(repo)
2044 results = {}
2044 results = {}
2045
2045
2046 def prep(ctx, fns):
2046 def prep(ctx, fns):
2047 d = ctx.date()
2047 d = ctx.date()
2048 if df(d[0]):
2048 if df(d[0]):
2049 results[ctx.rev()] = d
2049 results[ctx.rev()] = d
2050
2050
2051 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2051 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2052 rev = ctx.rev()
2052 rev = ctx.rev()
2053 if rev in results:
2053 if rev in results:
2054 ui.status(_("found revision %s from %s\n") %
2054 ui.status(_("found revision %s from %s\n") %
2055 (rev, util.datestr(results[rev])))
2055 (rev, util.datestr(results[rev])))
2056 return '%d' % rev
2056 return '%d' % rev
2057
2057
2058 raise error.Abort(_("revision matching date not found"))
2058 raise error.Abort(_("revision matching date not found"))
2059
2059
2060 def increasingwindows(windowsize=8, sizelimit=512):
2060 def increasingwindows(windowsize=8, sizelimit=512):
2061 while True:
2061 while True:
2062 yield windowsize
2062 yield windowsize
2063 if windowsize < sizelimit:
2063 if windowsize < sizelimit:
2064 windowsize *= 2
2064 windowsize *= 2
2065
2065
2066 class FileWalkError(Exception):
2066 class FileWalkError(Exception):
2067 pass
2067 pass
2068
2068
2069 def walkfilerevs(repo, match, follow, revs, fncache):
2069 def walkfilerevs(repo, match, follow, revs, fncache):
2070 '''Walks the file history for the matched files.
2070 '''Walks the file history for the matched files.
2071
2071
2072 Returns the changeset revs that are involved in the file history.
2072 Returns the changeset revs that are involved in the file history.
2073
2073
2074 Throws FileWalkError if the file history can't be walked using
2074 Throws FileWalkError if the file history can't be walked using
2075 filelogs alone.
2075 filelogs alone.
2076 '''
2076 '''
2077 wanted = set()
2077 wanted = set()
2078 copies = []
2078 copies = []
2079 minrev, maxrev = min(revs), max(revs)
2079 minrev, maxrev = min(revs), max(revs)
2080 def filerevgen(filelog, last):
2080 def filerevgen(filelog, last):
2081 """
2081 """
2082 Only files, no patterns. Check the history of each file.
2082 Only files, no patterns. Check the history of each file.
2083
2083
2084 Examines filelog entries within minrev, maxrev linkrev range
2084 Examines filelog entries within minrev, maxrev linkrev range
2085 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2085 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2086 tuples in backwards order
2086 tuples in backwards order
2087 """
2087 """
2088 cl_count = len(repo)
2088 cl_count = len(repo)
2089 revs = []
2089 revs = []
2090 for j in xrange(0, last + 1):
2090 for j in xrange(0, last + 1):
2091 linkrev = filelog.linkrev(j)
2091 linkrev = filelog.linkrev(j)
2092 if linkrev < minrev:
2092 if linkrev < minrev:
2093 continue
2093 continue
2094 # only yield rev for which we have the changelog, it can
2094 # only yield rev for which we have the changelog, it can
2095 # happen while doing "hg log" during a pull or commit
2095 # happen while doing "hg log" during a pull or commit
2096 if linkrev >= cl_count:
2096 if linkrev >= cl_count:
2097 break
2097 break
2098
2098
2099 parentlinkrevs = []
2099 parentlinkrevs = []
2100 for p in filelog.parentrevs(j):
2100 for p in filelog.parentrevs(j):
2101 if p != nullrev:
2101 if p != nullrev:
2102 parentlinkrevs.append(filelog.linkrev(p))
2102 parentlinkrevs.append(filelog.linkrev(p))
2103 n = filelog.node(j)
2103 n = filelog.node(j)
2104 revs.append((linkrev, parentlinkrevs,
2104 revs.append((linkrev, parentlinkrevs,
2105 follow and filelog.renamed(n)))
2105 follow and filelog.renamed(n)))
2106
2106
2107 return reversed(revs)
2107 return reversed(revs)
2108 def iterfiles():
2108 def iterfiles():
2109 pctx = repo['.']
2109 pctx = repo['.']
2110 for filename in match.files():
2110 for filename in match.files():
2111 if follow:
2111 if follow:
2112 if filename not in pctx:
2112 if filename not in pctx:
2113 raise error.Abort(_('cannot follow file not in parent '
2113 raise error.Abort(_('cannot follow file not in parent '
2114 'revision: "%s"') % filename)
2114 'revision: "%s"') % filename)
2115 yield filename, pctx[filename].filenode()
2115 yield filename, pctx[filename].filenode()
2116 else:
2116 else:
2117 yield filename, None
2117 yield filename, None
2118 for filename_node in copies:
2118 for filename_node in copies:
2119 yield filename_node
2119 yield filename_node
2120
2120
2121 for file_, node in iterfiles():
2121 for file_, node in iterfiles():
2122 filelog = repo.file(file_)
2122 filelog = repo.file(file_)
2123 if not len(filelog):
2123 if not len(filelog):
2124 if node is None:
2124 if node is None:
2125 # A zero count may be a directory or deleted file, so
2125 # A zero count may be a directory or deleted file, so
2126 # try to find matching entries on the slow path.
2126 # try to find matching entries on the slow path.
2127 if follow:
2127 if follow:
2128 raise error.Abort(
2128 raise error.Abort(
2129 _('cannot follow nonexistent file: "%s"') % file_)
2129 _('cannot follow nonexistent file: "%s"') % file_)
2130 raise FileWalkError("Cannot walk via filelog")
2130 raise FileWalkError("Cannot walk via filelog")
2131 else:
2131 else:
2132 continue
2132 continue
2133
2133
2134 if node is None:
2134 if node is None:
2135 last = len(filelog) - 1
2135 last = len(filelog) - 1
2136 else:
2136 else:
2137 last = filelog.rev(node)
2137 last = filelog.rev(node)
2138
2138
2139 # keep track of all ancestors of the file
2139 # keep track of all ancestors of the file
2140 ancestors = {filelog.linkrev(last)}
2140 ancestors = {filelog.linkrev(last)}
2141
2141
2142 # iterate from latest to oldest revision
2142 # iterate from latest to oldest revision
2143 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2143 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2144 if not follow:
2144 if not follow:
2145 if rev > maxrev:
2145 if rev > maxrev:
2146 continue
2146 continue
2147 else:
2147 else:
2148 # Note that last might not be the first interesting
2148 # Note that last might not be the first interesting
2149 # rev to us:
2149 # rev to us:
2150 # if the file has been changed after maxrev, we'll
2150 # if the file has been changed after maxrev, we'll
2151 # have linkrev(last) > maxrev, and we still need
2151 # have linkrev(last) > maxrev, and we still need
2152 # to explore the file graph
2152 # to explore the file graph
2153 if rev not in ancestors:
2153 if rev not in ancestors:
2154 continue
2154 continue
2155 # XXX insert 1327 fix here
2155 # XXX insert 1327 fix here
2156 if flparentlinkrevs:
2156 if flparentlinkrevs:
2157 ancestors.update(flparentlinkrevs)
2157 ancestors.update(flparentlinkrevs)
2158
2158
2159 fncache.setdefault(rev, []).append(file_)
2159 fncache.setdefault(rev, []).append(file_)
2160 wanted.add(rev)
2160 wanted.add(rev)
2161 if copied:
2161 if copied:
2162 copies.append(copied)
2162 copies.append(copied)
2163
2163
2164 return wanted
2164 return wanted
2165
2165
2166 class _followfilter(object):
2166 class _followfilter(object):
2167 def __init__(self, repo, onlyfirst=False):
2167 def __init__(self, repo, onlyfirst=False):
2168 self.repo = repo
2168 self.repo = repo
2169 self.startrev = nullrev
2169 self.startrev = nullrev
2170 self.roots = set()
2170 self.roots = set()
2171 self.onlyfirst = onlyfirst
2171 self.onlyfirst = onlyfirst
2172
2172
2173 def match(self, rev):
2173 def match(self, rev):
2174 def realparents(rev):
2174 def realparents(rev):
2175 if self.onlyfirst:
2175 if self.onlyfirst:
2176 return self.repo.changelog.parentrevs(rev)[0:1]
2176 return self.repo.changelog.parentrevs(rev)[0:1]
2177 else:
2177 else:
2178 return filter(lambda x: x != nullrev,
2178 return filter(lambda x: x != nullrev,
2179 self.repo.changelog.parentrevs(rev))
2179 self.repo.changelog.parentrevs(rev))
2180
2180
2181 if self.startrev == nullrev:
2181 if self.startrev == nullrev:
2182 self.startrev = rev
2182 self.startrev = rev
2183 return True
2183 return True
2184
2184
2185 if rev > self.startrev:
2185 if rev > self.startrev:
2186 # forward: all descendants
2186 # forward: all descendants
2187 if not self.roots:
2187 if not self.roots:
2188 self.roots.add(self.startrev)
2188 self.roots.add(self.startrev)
2189 for parent in realparents(rev):
2189 for parent in realparents(rev):
2190 if parent in self.roots:
2190 if parent in self.roots:
2191 self.roots.add(rev)
2191 self.roots.add(rev)
2192 return True
2192 return True
2193 else:
2193 else:
2194 # backwards: all parents
2194 # backwards: all parents
2195 if not self.roots:
2195 if not self.roots:
2196 self.roots.update(realparents(self.startrev))
2196 self.roots.update(realparents(self.startrev))
2197 if rev in self.roots:
2197 if rev in self.roots:
2198 self.roots.remove(rev)
2198 self.roots.remove(rev)
2199 self.roots.update(realparents(rev))
2199 self.roots.update(realparents(rev))
2200 return True
2200 return True
2201
2201
2202 return False
2202 return False
2203
2203
2204 def walkchangerevs(repo, match, opts, prepare):
2204 def walkchangerevs(repo, match, opts, prepare):
2205 '''Iterate over files and the revs in which they changed.
2205 '''Iterate over files and the revs in which they changed.
2206
2206
2207 Callers most commonly need to iterate backwards over the history
2207 Callers most commonly need to iterate backwards over the history
2208 in which they are interested. Doing so has awful (quadratic-looking)
2208 in which they are interested. Doing so has awful (quadratic-looking)
2209 performance, so we use iterators in a "windowed" way.
2209 performance, so we use iterators in a "windowed" way.
2210
2210
2211 We walk a window of revisions in the desired order. Within the
2211 We walk a window of revisions in the desired order. Within the
2212 window, we first walk forwards to gather data, then in the desired
2212 window, we first walk forwards to gather data, then in the desired
2213 order (usually backwards) to display it.
2213 order (usually backwards) to display it.
2214
2214
2215 This function returns an iterator yielding contexts. Before
2215 This function returns an iterator yielding contexts. Before
2216 yielding each context, the iterator will first call the prepare
2216 yielding each context, the iterator will first call the prepare
2217 function on each context in the window in forward order.'''
2217 function on each context in the window in forward order.'''
2218
2218
2219 follow = opts.get('follow') or opts.get('follow_first')
2219 follow = opts.get('follow') or opts.get('follow_first')
2220 revs = _logrevs(repo, opts)
2220 revs = _logrevs(repo, opts)
2221 if not revs:
2221 if not revs:
2222 return []
2222 return []
2223 wanted = set()
2223 wanted = set()
2224 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2224 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2225 opts.get('removed'))
2225 opts.get('removed'))
2226 fncache = {}
2226 fncache = {}
2227 change = repo.changectx
2227 change = repo.changectx
2228
2228
2229 # First step is to fill wanted, the set of revisions that we want to yield.
2229 # First step is to fill wanted, the set of revisions that we want to yield.
2230 # When it does not induce extra cost, we also fill fncache for revisions in
2230 # When it does not induce extra cost, we also fill fncache for revisions in
2231 # wanted: a cache of filenames that were changed (ctx.files()) and that
2231 # wanted: a cache of filenames that were changed (ctx.files()) and that
2232 # match the file filtering conditions.
2232 # match the file filtering conditions.
2233
2233
2234 if match.always():
2234 if match.always():
2235 # No files, no patterns. Display all revs.
2235 # No files, no patterns. Display all revs.
2236 wanted = revs
2236 wanted = revs
2237 elif not slowpath:
2237 elif not slowpath:
2238 # We only have to read through the filelog to find wanted revisions
2238 # We only have to read through the filelog to find wanted revisions
2239
2239
2240 try:
2240 try:
2241 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2241 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2242 except FileWalkError:
2242 except FileWalkError:
2243 slowpath = True
2243 slowpath = True
2244
2244
2245 # We decided to fall back to the slowpath because at least one
2245 # We decided to fall back to the slowpath because at least one
2246 # of the paths was not a file. Check to see if at least one of them
2246 # of the paths was not a file. Check to see if at least one of them
2247 # existed in history, otherwise simply return
2247 # existed in history, otherwise simply return
2248 for path in match.files():
2248 for path in match.files():
2249 if path == '.' or path in repo.store:
2249 if path == '.' or path in repo.store:
2250 break
2250 break
2251 else:
2251 else:
2252 return []
2252 return []
2253
2253
2254 if slowpath:
2254 if slowpath:
2255 # We have to read the changelog to match filenames against
2255 # We have to read the changelog to match filenames against
2256 # changed files
2256 # changed files
2257
2257
2258 if follow:
2258 if follow:
2259 raise error.Abort(_('can only follow copies/renames for explicit '
2259 raise error.Abort(_('can only follow copies/renames for explicit '
2260 'filenames'))
2260 'filenames'))
2261
2261
2262 # The slow path checks files modified in every changeset.
2262 # The slow path checks files modified in every changeset.
2263 # This is really slow on large repos, so compute the set lazily.
2263 # This is really slow on large repos, so compute the set lazily.
2264 class lazywantedset(object):
2264 class lazywantedset(object):
2265 def __init__(self):
2265 def __init__(self):
2266 self.set = set()
2266 self.set = set()
2267 self.revs = set(revs)
2267 self.revs = set(revs)
2268
2268
2269 # No need to worry about locality here because it will be accessed
2269 # No need to worry about locality here because it will be accessed
2270 # in the same order as the increasing window below.
2270 # in the same order as the increasing window below.
2271 def __contains__(self, value):
2271 def __contains__(self, value):
2272 if value in self.set:
2272 if value in self.set:
2273 return True
2273 return True
2274 elif not value in self.revs:
2274 elif not value in self.revs:
2275 return False
2275 return False
2276 else:
2276 else:
2277 self.revs.discard(value)
2277 self.revs.discard(value)
2278 ctx = change(value)
2278 ctx = change(value)
2279 matches = filter(match, ctx.files())
2279 matches = filter(match, ctx.files())
2280 if matches:
2280 if matches:
2281 fncache[value] = matches
2281 fncache[value] = matches
2282 self.set.add(value)
2282 self.set.add(value)
2283 return True
2283 return True
2284 return False
2284 return False
2285
2285
2286 def discard(self, value):
2286 def discard(self, value):
2287 self.revs.discard(value)
2287 self.revs.discard(value)
2288 self.set.discard(value)
2288 self.set.discard(value)
2289
2289
2290 wanted = lazywantedset()
2290 wanted = lazywantedset()
2291
2291
2292 # it might be worthwhile to do this in the iterator if the rev range
2292 # it might be worthwhile to do this in the iterator if the rev range
2293 # is descending and the prune args are all within that range
2293 # is descending and the prune args are all within that range
2294 for rev in opts.get('prune', ()):
2294 for rev in opts.get('prune', ()):
2295 rev = repo[rev].rev()
2295 rev = repo[rev].rev()
2296 ff = _followfilter(repo)
2296 ff = _followfilter(repo)
2297 stop = min(revs[0], revs[-1])
2297 stop = min(revs[0], revs[-1])
2298 for x in xrange(rev, stop - 1, -1):
2298 for x in xrange(rev, stop - 1, -1):
2299 if ff.match(x):
2299 if ff.match(x):
2300 wanted = wanted - [x]
2300 wanted = wanted - [x]
2301
2301
2302 # Now that wanted is correctly initialized, we can iterate over the
2302 # Now that wanted is correctly initialized, we can iterate over the
2303 # revision range, yielding only revisions in wanted.
2303 # revision range, yielding only revisions in wanted.
2304 def iterate():
2304 def iterate():
2305 if follow and match.always():
2305 if follow and match.always():
2306 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2306 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2307 def want(rev):
2307 def want(rev):
2308 return ff.match(rev) and rev in wanted
2308 return ff.match(rev) and rev in wanted
2309 else:
2309 else:
2310 def want(rev):
2310 def want(rev):
2311 return rev in wanted
2311 return rev in wanted
2312
2312
2313 it = iter(revs)
2313 it = iter(revs)
2314 stopiteration = False
2314 stopiteration = False
2315 for windowsize in increasingwindows():
2315 for windowsize in increasingwindows():
2316 nrevs = []
2316 nrevs = []
2317 for i in xrange(windowsize):
2317 for i in xrange(windowsize):
2318 rev = next(it, None)
2318 rev = next(it, None)
2319 if rev is None:
2319 if rev is None:
2320 stopiteration = True
2320 stopiteration = True
2321 break
2321 break
2322 elif want(rev):
2322 elif want(rev):
2323 nrevs.append(rev)
2323 nrevs.append(rev)
2324 for rev in sorted(nrevs):
2324 for rev in sorted(nrevs):
2325 fns = fncache.get(rev)
2325 fns = fncache.get(rev)
2326 ctx = change(rev)
2326 ctx = change(rev)
2327 if not fns:
2327 if not fns:
2328 def fns_generator():
2328 def fns_generator():
2329 for f in ctx.files():
2329 for f in ctx.files():
2330 if match(f):
2330 if match(f):
2331 yield f
2331 yield f
2332 fns = fns_generator()
2332 fns = fns_generator()
2333 prepare(ctx, fns)
2333 prepare(ctx, fns)
2334 for rev in nrevs:
2334 for rev in nrevs:
2335 yield change(rev)
2335 yield change(rev)
2336
2336
2337 if stopiteration:
2337 if stopiteration:
2338 break
2338 break
2339
2339
2340 return iterate()
2340 return iterate()
2341
2341
2342 def _makefollowlogfilematcher(repo, files, followfirst):
2342 def _makefollowlogfilematcher(repo, files, followfirst):
2343 # When displaying a revision with --patch --follow FILE, we have
2343 # When displaying a revision with --patch --follow FILE, we have
2344 # to know which file of the revision must be diffed. With
2344 # to know which file of the revision must be diffed. With
2345 # --follow, we want the names of the ancestors of FILE in the
2345 # --follow, we want the names of the ancestors of FILE in the
2346 # revision, stored in "fcache". "fcache" is populated by
2346 # revision, stored in "fcache". "fcache" is populated by
2347 # reproducing the graph traversal already done by --follow revset
2347 # reproducing the graph traversal already done by --follow revset
2348 # and relating revs to file names (which is not "correct" but
2348 # and relating revs to file names (which is not "correct" but
2349 # good enough).
2349 # good enough).
2350 fcache = {}
2350 fcache = {}
2351 fcacheready = [False]
2351 fcacheready = [False]
2352 pctx = repo['.']
2352 pctx = repo['.']
2353
2353
2354 def populate():
2354 def populate():
2355 for fn in files:
2355 for fn in files:
2356 fctx = pctx[fn]
2356 fctx = pctx[fn]
2357 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2357 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2358 for c in fctx.ancestors(followfirst=followfirst):
2358 for c in fctx.ancestors(followfirst=followfirst):
2359 fcache.setdefault(c.rev(), set()).add(c.path())
2359 fcache.setdefault(c.rev(), set()).add(c.path())
2360
2360
2361 def filematcher(rev):
2361 def filematcher(rev):
2362 if not fcacheready[0]:
2362 if not fcacheready[0]:
2363 # Lazy initialization
2363 # Lazy initialization
2364 fcacheready[0] = True
2364 fcacheready[0] = True
2365 populate()
2365 populate()
2366 return scmutil.matchfiles(repo, fcache.get(rev, []))
2366 return scmutil.matchfiles(repo, fcache.get(rev, []))
2367
2367
2368 return filematcher
2368 return filematcher
2369
2369
2370 def _makenofollowlogfilematcher(repo, pats, opts):
2370 def _makenofollowlogfilematcher(repo, pats, opts):
2371 '''hook for extensions to override the filematcher for non-follow cases'''
2371 '''hook for extensions to override the filematcher for non-follow cases'''
2372 return None
2372 return None
2373
2373
2374 def _makelogrevset(repo, pats, opts, revs):
2374 def _makelogrevset(repo, pats, opts, revs):
2375 """Return (expr, filematcher) where expr is a revset string built
2375 """Return (expr, filematcher) where expr is a revset string built
2376 from log options and file patterns or None. If --stat or --patch
2376 from log options and file patterns or None. If --stat or --patch
2377 are not passed filematcher is None. Otherwise it is a callable
2377 are not passed filematcher is None. Otherwise it is a callable
2378 taking a revision number and returning a match objects filtering
2378 taking a revision number and returning a match objects filtering
2379 the files to be detailed when displaying the revision.
2379 the files to be detailed when displaying the revision.
2380 """
2380 """
2381 opt2revset = {
2381 opt2revset = {
2382 'no_merges': ('not merge()', None),
2382 'no_merges': ('not merge()', None),
2383 'only_merges': ('merge()', None),
2383 'only_merges': ('merge()', None),
2384 '_ancestors': ('ancestors(%(val)s)', None),
2384 '_ancestors': ('ancestors(%(val)s)', None),
2385 '_fancestors': ('_firstancestors(%(val)s)', None),
2385 '_fancestors': ('_firstancestors(%(val)s)', None),
2386 '_descendants': ('descendants(%(val)s)', None),
2386 '_descendants': ('descendants(%(val)s)', None),
2387 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2387 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2388 '_matchfiles': ('_matchfiles(%(val)s)', None),
2388 '_matchfiles': ('_matchfiles(%(val)s)', None),
2389 'date': ('date(%(val)r)', None),
2389 'date': ('date(%(val)r)', None),
2390 'branch': ('branch(%(val)r)', ' or '),
2390 'branch': ('branch(%(val)r)', ' or '),
2391 '_patslog': ('filelog(%(val)r)', ' or '),
2391 '_patslog': ('filelog(%(val)r)', ' or '),
2392 '_patsfollow': ('follow(%(val)r)', ' or '),
2392 '_patsfollow': ('follow(%(val)r)', ' or '),
2393 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2393 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2394 'keyword': ('keyword(%(val)r)', ' or '),
2394 'keyword': ('keyword(%(val)r)', ' or '),
2395 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2395 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2396 'user': ('user(%(val)r)', ' or '),
2396 'user': ('user(%(val)r)', ' or '),
2397 }
2397 }
2398
2398
2399 opts = dict(opts)
2399 opts = dict(opts)
2400 # follow or not follow?
2400 # follow or not follow?
2401 follow = opts.get('follow') or opts.get('follow_first')
2401 follow = opts.get('follow') or opts.get('follow_first')
2402 if opts.get('follow_first'):
2402 if opts.get('follow_first'):
2403 followfirst = 1
2403 followfirst = 1
2404 else:
2404 else:
2405 followfirst = 0
2405 followfirst = 0
2406 # --follow with FILE behavior depends on revs...
2406 # --follow with FILE behavior depends on revs...
2407 it = iter(revs)
2407 it = iter(revs)
2408 startrev = next(it)
2408 startrev = next(it)
2409 followdescendants = startrev < next(it, startrev)
2409 followdescendants = startrev < next(it, startrev)
2410
2410
2411 # branch and only_branch are really aliases and must be handled at
2411 # branch and only_branch are really aliases and must be handled at
2412 # the same time
2412 # the same time
2413 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2413 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2414 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2414 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2415 # pats/include/exclude are passed to match.match() directly in
2415 # pats/include/exclude are passed to match.match() directly in
2416 # _matchfiles() revset but walkchangerevs() builds its matcher with
2416 # _matchfiles() revset but walkchangerevs() builds its matcher with
2417 # scmutil.match(). The difference is input pats are globbed on
2417 # scmutil.match(). The difference is input pats are globbed on
2418 # platforms without shell expansion (windows).
2418 # platforms without shell expansion (windows).
2419 wctx = repo[None]
2419 wctx = repo[None]
2420 match, pats = scmutil.matchandpats(wctx, pats, opts)
2420 match, pats = scmutil.matchandpats(wctx, pats, opts)
2421 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2421 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2422 opts.get('removed'))
2422 opts.get('removed'))
2423 if not slowpath:
2423 if not slowpath:
2424 for f in match.files():
2424 for f in match.files():
2425 if follow and f not in wctx:
2425 if follow and f not in wctx:
2426 # If the file exists, it may be a directory, so let it
2426 # If the file exists, it may be a directory, so let it
2427 # take the slow path.
2427 # take the slow path.
2428 if os.path.exists(repo.wjoin(f)):
2428 if os.path.exists(repo.wjoin(f)):
2429 slowpath = True
2429 slowpath = True
2430 continue
2430 continue
2431 else:
2431 else:
2432 raise error.Abort(_('cannot follow file not in parent '
2432 raise error.Abort(_('cannot follow file not in parent '
2433 'revision: "%s"') % f)
2433 'revision: "%s"') % f)
2434 filelog = repo.file(f)
2434 filelog = repo.file(f)
2435 if not filelog:
2435 if not filelog:
2436 # A zero count may be a directory or deleted file, so
2436 # A zero count may be a directory or deleted file, so
2437 # try to find matching entries on the slow path.
2437 # try to find matching entries on the slow path.
2438 if follow:
2438 if follow:
2439 raise error.Abort(
2439 raise error.Abort(
2440 _('cannot follow nonexistent file: "%s"') % f)
2440 _('cannot follow nonexistent file: "%s"') % f)
2441 slowpath = True
2441 slowpath = True
2442
2442
2443 # We decided to fall back to the slowpath because at least one
2443 # We decided to fall back to the slowpath because at least one
2444 # of the paths was not a file. Check to see if at least one of them
2444 # of the paths was not a file. Check to see if at least one of them
2445 # existed in history - in that case, we'll continue down the
2445 # existed in history - in that case, we'll continue down the
2446 # slowpath; otherwise, we can turn off the slowpath
2446 # slowpath; otherwise, we can turn off the slowpath
2447 if slowpath:
2447 if slowpath:
2448 for path in match.files():
2448 for path in match.files():
2449 if path == '.' or path in repo.store:
2449 if path == '.' or path in repo.store:
2450 break
2450 break
2451 else:
2451 else:
2452 slowpath = False
2452 slowpath = False
2453
2453
2454 fpats = ('_patsfollow', '_patsfollowfirst')
2454 fpats = ('_patsfollow', '_patsfollowfirst')
2455 fnopats = (('_ancestors', '_fancestors'),
2455 fnopats = (('_ancestors', '_fancestors'),
2456 ('_descendants', '_fdescendants'))
2456 ('_descendants', '_fdescendants'))
2457 if slowpath:
2457 if slowpath:
2458 # See walkchangerevs() slow path.
2458 # See walkchangerevs() slow path.
2459 #
2459 #
2460 # pats/include/exclude cannot be represented as separate
2460 # pats/include/exclude cannot be represented as separate
2461 # revset expressions as their filtering logic applies at file
2461 # revset expressions as their filtering logic applies at file
2462 # level. For instance "-I a -X a" matches a revision touching
2462 # level. For instance "-I a -X a" matches a revision touching
2463 # "a" and "b" while "file(a) and not file(b)" does
2463 # "a" and "b" while "file(a) and not file(b)" does
2464 # not. Besides, filesets are evaluated against the working
2464 # not. Besides, filesets are evaluated against the working
2465 # directory.
2465 # directory.
2466 matchargs = ['r:', 'd:relpath']
2466 matchargs = ['r:', 'd:relpath']
2467 for p in pats:
2467 for p in pats:
2468 matchargs.append('p:' + p)
2468 matchargs.append('p:' + p)
2469 for p in opts.get('include', []):
2469 for p in opts.get('include', []):
2470 matchargs.append('i:' + p)
2470 matchargs.append('i:' + p)
2471 for p in opts.get('exclude', []):
2471 for p in opts.get('exclude', []):
2472 matchargs.append('x:' + p)
2472 matchargs.append('x:' + p)
2473 matchargs = ','.join(('%r' % p) for p in matchargs)
2473 matchargs = ','.join(('%r' % p) for p in matchargs)
2474 opts['_matchfiles'] = matchargs
2474 opts['_matchfiles'] = matchargs
2475 if follow:
2475 if follow:
2476 opts[fnopats[0][followfirst]] = '.'
2476 opts[fnopats[0][followfirst]] = '.'
2477 else:
2477 else:
2478 if follow:
2478 if follow:
2479 if pats:
2479 if pats:
2480 # follow() revset interprets its file argument as a
2480 # follow() revset interprets its file argument as a
2481 # manifest entry, so use match.files(), not pats.
2481 # manifest entry, so use match.files(), not pats.
2482 opts[fpats[followfirst]] = list(match.files())
2482 opts[fpats[followfirst]] = list(match.files())
2483 else:
2483 else:
2484 op = fnopats[followdescendants][followfirst]
2484 op = fnopats[followdescendants][followfirst]
2485 opts[op] = 'rev(%d)' % startrev
2485 opts[op] = 'rev(%d)' % startrev
2486 else:
2486 else:
2487 opts['_patslog'] = list(pats)
2487 opts['_patslog'] = list(pats)
2488
2488
2489 filematcher = None
2489 filematcher = None
2490 if opts.get('patch') or opts.get('stat'):
2490 if opts.get('patch') or opts.get('stat'):
2491 # When following files, track renames via a special matcher.
2491 # When following files, track renames via a special matcher.
2492 # If we're forced to take the slowpath it means we're following
2492 # If we're forced to take the slowpath it means we're following
2493 # at least one pattern/directory, so don't bother with rename tracking.
2493 # at least one pattern/directory, so don't bother with rename tracking.
2494 if follow and not match.always() and not slowpath:
2494 if follow and not match.always() and not slowpath:
2495 # _makefollowlogfilematcher expects its files argument to be
2495 # _makefollowlogfilematcher expects its files argument to be
2496 # relative to the repo root, so use match.files(), not pats.
2496 # relative to the repo root, so use match.files(), not pats.
2497 filematcher = _makefollowlogfilematcher(repo, match.files(),
2497 filematcher = _makefollowlogfilematcher(repo, match.files(),
2498 followfirst)
2498 followfirst)
2499 else:
2499 else:
2500 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2500 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2501 if filematcher is None:
2501 if filematcher is None:
2502 filematcher = lambda rev: match
2502 filematcher = lambda rev: match
2503
2503
2504 expr = []
2504 expr = []
2505 for op, val in sorted(opts.iteritems()):
2505 for op, val in sorted(opts.iteritems()):
2506 if not val:
2506 if not val:
2507 continue
2507 continue
2508 if op not in opt2revset:
2508 if op not in opt2revset:
2509 continue
2509 continue
2510 revop, andor = opt2revset[op]
2510 revop, andor = opt2revset[op]
2511 if '%(val)' not in revop:
2511 if '%(val)' not in revop:
2512 expr.append(revop)
2512 expr.append(revop)
2513 else:
2513 else:
2514 if not isinstance(val, list):
2514 if not isinstance(val, list):
2515 e = revop % {'val': val}
2515 e = revop % {'val': val}
2516 else:
2516 else:
2517 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2517 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2518 expr.append(e)
2518 expr.append(e)
2519
2519
2520 if expr:
2520 if expr:
2521 expr = '(' + ' and '.join(expr) + ')'
2521 expr = '(' + ' and '.join(expr) + ')'
2522 else:
2522 else:
2523 expr = None
2523 expr = None
2524 return expr, filematcher
2524 return expr, filematcher
2525
2525
2526 def _logrevs(repo, opts):
2526 def _logrevs(repo, opts):
2527 # Default --rev value depends on --follow but --follow behavior
2527 # Default --rev value depends on --follow but --follow behavior
2528 # depends on revisions resolved from --rev...
2528 # depends on revisions resolved from --rev...
2529 follow = opts.get('follow') or opts.get('follow_first')
2529 follow = opts.get('follow') or opts.get('follow_first')
2530 if opts.get('rev'):
2530 if opts.get('rev'):
2531 revs = scmutil.revrange(repo, opts['rev'])
2531 revs = scmutil.revrange(repo, opts['rev'])
2532 elif follow and repo.dirstate.p1() == nullid:
2532 elif follow and repo.dirstate.p1() == nullid:
2533 revs = smartset.baseset()
2533 revs = smartset.baseset()
2534 elif follow:
2534 elif follow:
2535 revs = repo.revs('reverse(:.)')
2535 revs = repo.revs('reverse(:.)')
2536 else:
2536 else:
2537 revs = smartset.spanset(repo)
2537 revs = smartset.spanset(repo)
2538 revs.reverse()
2538 revs.reverse()
2539 return revs
2539 return revs
2540
2540
2541 def getgraphlogrevs(repo, pats, opts):
2541 def getgraphlogrevs(repo, pats, opts):
2542 """Return (revs, expr, filematcher) where revs is an iterable of
2542 """Return (revs, expr, filematcher) where revs is an iterable of
2543 revision numbers, expr is a revset string built from log options
2543 revision numbers, expr is a revset string built from log options
2544 and file patterns or None, and used to filter 'revs'. If --stat or
2544 and file patterns or None, and used to filter 'revs'. If --stat or
2545 --patch are not passed filematcher is None. Otherwise it is a
2545 --patch are not passed filematcher is None. Otherwise it is a
2546 callable taking a revision number and returning a match objects
2546 callable taking a revision number and returning a match objects
2547 filtering the files to be detailed when displaying the revision.
2547 filtering the files to be detailed when displaying the revision.
2548 """
2548 """
2549 limit = loglimit(opts)
2549 limit = loglimit(opts)
2550 revs = _logrevs(repo, opts)
2550 revs = _logrevs(repo, opts)
2551 if not revs:
2551 if not revs:
2552 return smartset.baseset(), None, None
2552 return smartset.baseset(), None, None
2553 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2553 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2554 if opts.get('rev'):
2554 if opts.get('rev'):
2555 # User-specified revs might be unsorted, but don't sort before
2555 # User-specified revs might be unsorted, but don't sort before
2556 # _makelogrevset because it might depend on the order of revs
2556 # _makelogrevset because it might depend on the order of revs
2557 if not (revs.isdescending() or revs.istopo()):
2557 if not (revs.isdescending() or revs.istopo()):
2558 revs.sort(reverse=True)
2558 revs.sort(reverse=True)
2559 if expr:
2559 if expr:
2560 matcher = revset.match(repo.ui, expr)
2560 matcher = revset.match(repo.ui, expr)
2561 revs = matcher(repo, revs)
2561 revs = matcher(repo, revs)
2562 if limit is not None:
2562 if limit is not None:
2563 limitedrevs = []
2563 limitedrevs = []
2564 for idx, rev in enumerate(revs):
2564 for idx, rev in enumerate(revs):
2565 if idx >= limit:
2565 if idx >= limit:
2566 break
2566 break
2567 limitedrevs.append(rev)
2567 limitedrevs.append(rev)
2568 revs = smartset.baseset(limitedrevs)
2568 revs = smartset.baseset(limitedrevs)
2569
2569
2570 return revs, expr, filematcher
2570 return revs, expr, filematcher
2571
2571
2572 def getlogrevs(repo, pats, opts):
2572 def getlogrevs(repo, pats, opts):
2573 """Return (revs, expr, filematcher) where revs is an iterable of
2573 """Return (revs, expr, filematcher) where revs is an iterable of
2574 revision numbers, expr is a revset string built from log options
2574 revision numbers, expr is a revset string built from log options
2575 and file patterns or None, and used to filter 'revs'. If --stat or
2575 and file patterns or None, and used to filter 'revs'. If --stat or
2576 --patch are not passed filematcher is None. Otherwise it is a
2576 --patch are not passed filematcher is None. Otherwise it is a
2577 callable taking a revision number and returning a match objects
2577 callable taking a revision number and returning a match objects
2578 filtering the files to be detailed when displaying the revision.
2578 filtering the files to be detailed when displaying the revision.
2579 """
2579 """
2580 limit = loglimit(opts)
2580 limit = loglimit(opts)
2581 revs = _logrevs(repo, opts)
2581 revs = _logrevs(repo, opts)
2582 if not revs:
2582 if not revs:
2583 return smartset.baseset([]), None, None
2583 return smartset.baseset([]), None, None
2584 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2584 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2585 if expr:
2585 if expr:
2586 matcher = revset.match(repo.ui, expr)
2586 matcher = revset.match(repo.ui, expr)
2587 revs = matcher(repo, revs)
2587 revs = matcher(repo, revs)
2588 if limit is not None:
2588 if limit is not None:
2589 limitedrevs = []
2589 limitedrevs = []
2590 for idx, r in enumerate(revs):
2590 for idx, r in enumerate(revs):
2591 if limit <= idx:
2591 if limit <= idx:
2592 break
2592 break
2593 limitedrevs.append(r)
2593 limitedrevs.append(r)
2594 revs = smartset.baseset(limitedrevs)
2594 revs = smartset.baseset(limitedrevs)
2595
2595
2596 return revs, expr, filematcher
2596 return revs, expr, filematcher
2597
2597
2598 def _graphnodeformatter(ui, displayer):
2598 def _graphnodeformatter(ui, displayer):
2599 spec = ui.config('ui', 'graphnodetemplate')
2599 spec = ui.config('ui', 'graphnodetemplate')
2600 if not spec:
2600 if not spec:
2601 return templatekw.showgraphnode # fast path for "{graphnode}"
2601 return templatekw.showgraphnode # fast path for "{graphnode}"
2602
2602
2603 spec = templater.unquotestring(spec)
2603 spec = templater.unquotestring(spec)
2604 templ = formatter.maketemplater(ui, spec)
2604 templ = formatter.maketemplater(ui, spec)
2605 cache = {}
2605 cache = {}
2606 if isinstance(displayer, changeset_templater):
2606 if isinstance(displayer, changeset_templater):
2607 cache = displayer.cache # reuse cache of slow templates
2607 cache = displayer.cache # reuse cache of slow templates
2608 props = templatekw.keywords.copy()
2608 props = templatekw.keywords.copy()
2609 props['templ'] = templ
2609 props['templ'] = templ
2610 props['cache'] = cache
2610 props['cache'] = cache
2611 def formatnode(repo, ctx):
2611 def formatnode(repo, ctx):
2612 props['ctx'] = ctx
2612 props['ctx'] = ctx
2613 props['repo'] = repo
2613 props['repo'] = repo
2614 props['ui'] = repo.ui
2614 props['ui'] = repo.ui
2615 props['revcache'] = {}
2615 props['revcache'] = {}
2616 return templ.render(props)
2616 return templ.render(props)
2617 return formatnode
2617 return formatnode
2618
2618
2619 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2619 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2620 filematcher=None):
2620 filematcher=None):
2621 formatnode = _graphnodeformatter(ui, displayer)
2621 formatnode = _graphnodeformatter(ui, displayer)
2622 state = graphmod.asciistate()
2622 state = graphmod.asciistate()
2623 styles = state['styles']
2623 styles = state['styles']
2624
2624
2625 # only set graph styling if HGPLAIN is not set.
2625 # only set graph styling if HGPLAIN is not set.
2626 if ui.plain('graph'):
2626 if ui.plain('graph'):
2627 # set all edge styles to |, the default pre-3.8 behaviour
2627 # set all edge styles to |, the default pre-3.8 behaviour
2628 styles.update(dict.fromkeys(styles, '|'))
2628 styles.update(dict.fromkeys(styles, '|'))
2629 else:
2629 else:
2630 edgetypes = {
2630 edgetypes = {
2631 'parent': graphmod.PARENT,
2631 'parent': graphmod.PARENT,
2632 'grandparent': graphmod.GRANDPARENT,
2632 'grandparent': graphmod.GRANDPARENT,
2633 'missing': graphmod.MISSINGPARENT
2633 'missing': graphmod.MISSINGPARENT
2634 }
2634 }
2635 for name, key in edgetypes.items():
2635 for name, key in edgetypes.items():
2636 # experimental config: experimental.graphstyle.*
2636 # experimental config: experimental.graphstyle.*
2637 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2637 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2638 styles[key])
2638 styles[key])
2639 if not styles[key]:
2639 if not styles[key]:
2640 styles[key] = None
2640 styles[key] = None
2641
2641
2642 # experimental config: experimental.graphshorten
2642 # experimental config: experimental.graphshorten
2643 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2643 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2644
2644
2645 for rev, type, ctx, parents in dag:
2645 for rev, type, ctx, parents in dag:
2646 char = formatnode(repo, ctx)
2646 char = formatnode(repo, ctx)
2647 copies = None
2647 copies = None
2648 if getrenamed and ctx.rev():
2648 if getrenamed and ctx.rev():
2649 copies = []
2649 copies = []
2650 for fn in ctx.files():
2650 for fn in ctx.files():
2651 rename = getrenamed(fn, ctx.rev())
2651 rename = getrenamed(fn, ctx.rev())
2652 if rename:
2652 if rename:
2653 copies.append((fn, rename[0]))
2653 copies.append((fn, rename[0]))
2654 revmatchfn = None
2654 revmatchfn = None
2655 if filematcher is not None:
2655 if filematcher is not None:
2656 revmatchfn = filematcher(ctx.rev())
2656 revmatchfn = filematcher(ctx.rev())
2657 edges = edgefn(type, char, state, rev, parents)
2657 edges = edgefn(type, char, state, rev, parents)
2658 firstedge = next(edges)
2658 firstedge = next(edges)
2659 width = firstedge[2]
2659 width = firstedge[2]
2660 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
2660 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
2661 _graphwidth=width)
2661 _graphwidth=width)
2662 lines = displayer.hunk.pop(rev).split('\n')
2662 lines = displayer.hunk.pop(rev).split('\n')
2663 if not lines[-1]:
2663 if not lines[-1]:
2664 del lines[-1]
2664 del lines[-1]
2665 displayer.flush(ctx)
2665 displayer.flush(ctx)
2666 for type, char, width, coldata in itertools.chain([firstedge], edges):
2666 for type, char, width, coldata in itertools.chain([firstedge], edges):
2667 graphmod.ascii(ui, state, type, char, lines, coldata)
2667 graphmod.ascii(ui, state, type, char, lines, coldata)
2668 lines = []
2668 lines = []
2669 displayer.close()
2669 displayer.close()
2670
2670
2671 def graphlog(ui, repo, pats, opts):
2671 def graphlog(ui, repo, pats, opts):
2672 # Parameters are identical to log command ones
2672 # Parameters are identical to log command ones
2673 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2673 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2674 revdag = graphmod.dagwalker(repo, revs)
2674 revdag = graphmod.dagwalker(repo, revs)
2675
2675
2676 getrenamed = None
2676 getrenamed = None
2677 if opts.get('copies'):
2677 if opts.get('copies'):
2678 endrev = None
2678 endrev = None
2679 if opts.get('rev'):
2679 if opts.get('rev'):
2680 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2680 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2681 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2681 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2682
2682
2683 ui.pager('log')
2683 ui.pager('log')
2684 displayer = show_changeset(ui, repo, opts, buffered=True)
2684 displayer = show_changeset(ui, repo, opts, buffered=True)
2685 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2685 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2686 filematcher)
2686 filematcher)
2687
2687
2688 def checkunsupportedgraphflags(pats, opts):
2688 def checkunsupportedgraphflags(pats, opts):
2689 for op in ["newest_first"]:
2689 for op in ["newest_first"]:
2690 if op in opts and opts[op]:
2690 if op in opts and opts[op]:
2691 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2691 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2692 % op.replace("_", "-"))
2692 % op.replace("_", "-"))
2693
2693
2694 def graphrevs(repo, nodes, opts):
2694 def graphrevs(repo, nodes, opts):
2695 limit = loglimit(opts)
2695 limit = loglimit(opts)
2696 nodes.reverse()
2696 nodes.reverse()
2697 if limit is not None:
2697 if limit is not None:
2698 nodes = nodes[:limit]
2698 nodes = nodes[:limit]
2699 return graphmod.nodes(repo, nodes)
2699 return graphmod.nodes(repo, nodes)
2700
2700
2701 def add(ui, repo, match, prefix, explicitonly, **opts):
2701 def add(ui, repo, match, prefix, explicitonly, **opts):
2702 join = lambda f: os.path.join(prefix, f)
2702 join = lambda f: os.path.join(prefix, f)
2703 bad = []
2703 bad = []
2704
2704
2705 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2705 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2706 names = []
2706 names = []
2707 wctx = repo[None]
2707 wctx = repo[None]
2708 cca = None
2708 cca = None
2709 abort, warn = scmutil.checkportabilityalert(ui)
2709 abort, warn = scmutil.checkportabilityalert(ui)
2710 if abort or warn:
2710 if abort or warn:
2711 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2711 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2712
2712
2713 badmatch = matchmod.badmatch(match, badfn)
2713 badmatch = matchmod.badmatch(match, badfn)
2714 dirstate = repo.dirstate
2714 dirstate = repo.dirstate
2715 # We don't want to just call wctx.walk here, since it would return a lot of
2715 # We don't want to just call wctx.walk here, since it would return a lot of
2716 # clean files, which we aren't interested in and takes time.
2716 # clean files, which we aren't interested in and takes time.
2717 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2717 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2718 True, False, full=False)):
2718 True, False, full=False)):
2719 exact = match.exact(f)
2719 exact = match.exact(f)
2720 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2720 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2721 if cca:
2721 if cca:
2722 cca(f)
2722 cca(f)
2723 names.append(f)
2723 names.append(f)
2724 if ui.verbose or not exact:
2724 if ui.verbose or not exact:
2725 ui.status(_('adding %s\n') % match.rel(f))
2725 ui.status(_('adding %s\n') % match.rel(f))
2726
2726
2727 for subpath in sorted(wctx.substate):
2727 for subpath in sorted(wctx.substate):
2728 sub = wctx.sub(subpath)
2728 sub = wctx.sub(subpath)
2729 try:
2729 try:
2730 submatch = matchmod.subdirmatcher(subpath, match)
2730 submatch = matchmod.subdirmatcher(subpath, match)
2731 if opts.get(r'subrepos'):
2731 if opts.get(r'subrepos'):
2732 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2732 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2733 else:
2733 else:
2734 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2734 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2735 except error.LookupError:
2735 except error.LookupError:
2736 ui.status(_("skipping missing subrepository: %s\n")
2736 ui.status(_("skipping missing subrepository: %s\n")
2737 % join(subpath))
2737 % join(subpath))
2738
2738
2739 if not opts.get(r'dry_run'):
2739 if not opts.get(r'dry_run'):
2740 rejected = wctx.add(names, prefix)
2740 rejected = wctx.add(names, prefix)
2741 bad.extend(f for f in rejected if f in match.files())
2741 bad.extend(f for f in rejected if f in match.files())
2742 return bad
2742 return bad
2743
2743
2744 def addwebdirpath(repo, serverpath, webconf):
2744 def addwebdirpath(repo, serverpath, webconf):
2745 webconf[serverpath] = repo.root
2745 webconf[serverpath] = repo.root
2746 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2746 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2747
2747
2748 for r in repo.revs('filelog("path:.hgsub")'):
2748 for r in repo.revs('filelog("path:.hgsub")'):
2749 ctx = repo[r]
2749 ctx = repo[r]
2750 for subpath in ctx.substate:
2750 for subpath in ctx.substate:
2751 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2751 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2752
2752
2753 def forget(ui, repo, match, prefix, explicitonly):
2753 def forget(ui, repo, match, prefix, explicitonly):
2754 join = lambda f: os.path.join(prefix, f)
2754 join = lambda f: os.path.join(prefix, f)
2755 bad = []
2755 bad = []
2756 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2756 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2757 wctx = repo[None]
2757 wctx = repo[None]
2758 forgot = []
2758 forgot = []
2759
2759
2760 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2760 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2761 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2761 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2762 if explicitonly:
2762 if explicitonly:
2763 forget = [f for f in forget if match.exact(f)]
2763 forget = [f for f in forget if match.exact(f)]
2764
2764
2765 for subpath in sorted(wctx.substate):
2765 for subpath in sorted(wctx.substate):
2766 sub = wctx.sub(subpath)
2766 sub = wctx.sub(subpath)
2767 try:
2767 try:
2768 submatch = matchmod.subdirmatcher(subpath, match)
2768 submatch = matchmod.subdirmatcher(subpath, match)
2769 subbad, subforgot = sub.forget(submatch, prefix)
2769 subbad, subforgot = sub.forget(submatch, prefix)
2770 bad.extend([subpath + '/' + f for f in subbad])
2770 bad.extend([subpath + '/' + f for f in subbad])
2771 forgot.extend([subpath + '/' + f for f in subforgot])
2771 forgot.extend([subpath + '/' + f for f in subforgot])
2772 except error.LookupError:
2772 except error.LookupError:
2773 ui.status(_("skipping missing subrepository: %s\n")
2773 ui.status(_("skipping missing subrepository: %s\n")
2774 % join(subpath))
2774 % join(subpath))
2775
2775
2776 if not explicitonly:
2776 if not explicitonly:
2777 for f in match.files():
2777 for f in match.files():
2778 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2778 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2779 if f not in forgot:
2779 if f not in forgot:
2780 if repo.wvfs.exists(f):
2780 if repo.wvfs.exists(f):
2781 # Don't complain if the exact case match wasn't given.
2781 # Don't complain if the exact case match wasn't given.
2782 # But don't do this until after checking 'forgot', so
2782 # But don't do this until after checking 'forgot', so
2783 # that subrepo files aren't normalized, and this op is
2783 # that subrepo files aren't normalized, and this op is
2784 # purely from data cached by the status walk above.
2784 # purely from data cached by the status walk above.
2785 if repo.dirstate.normalize(f) in repo.dirstate:
2785 if repo.dirstate.normalize(f) in repo.dirstate:
2786 continue
2786 continue
2787 ui.warn(_('not removing %s: '
2787 ui.warn(_('not removing %s: '
2788 'file is already untracked\n')
2788 'file is already untracked\n')
2789 % match.rel(f))
2789 % match.rel(f))
2790 bad.append(f)
2790 bad.append(f)
2791
2791
2792 for f in forget:
2792 for f in forget:
2793 if ui.verbose or not match.exact(f):
2793 if ui.verbose or not match.exact(f):
2794 ui.status(_('removing %s\n') % match.rel(f))
2794 ui.status(_('removing %s\n') % match.rel(f))
2795
2795
2796 rejected = wctx.forget(forget, prefix)
2796 rejected = wctx.forget(forget, prefix)
2797 bad.extend(f for f in rejected if f in match.files())
2797 bad.extend(f for f in rejected if f in match.files())
2798 forgot.extend(f for f in forget if f not in rejected)
2798 forgot.extend(f for f in forget if f not in rejected)
2799 return bad, forgot
2799 return bad, forgot
2800
2800
2801 def files(ui, ctx, m, fm, fmt, subrepos):
2801 def files(ui, ctx, m, fm, fmt, subrepos):
2802 rev = ctx.rev()
2802 rev = ctx.rev()
2803 ret = 1
2803 ret = 1
2804 ds = ctx.repo().dirstate
2804 ds = ctx.repo().dirstate
2805
2805
2806 for f in ctx.matches(m):
2806 for f in ctx.matches(m):
2807 if rev is None and ds[f] == 'r':
2807 if rev is None and ds[f] == 'r':
2808 continue
2808 continue
2809 fm.startitem()
2809 fm.startitem()
2810 if ui.verbose:
2810 if ui.verbose:
2811 fc = ctx[f]
2811 fc = ctx[f]
2812 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2812 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2813 fm.data(abspath=f)
2813 fm.data(abspath=f)
2814 fm.write('path', fmt, m.rel(f))
2814 fm.write('path', fmt, m.rel(f))
2815 ret = 0
2815 ret = 0
2816
2816
2817 for subpath in sorted(ctx.substate):
2817 for subpath in sorted(ctx.substate):
2818 submatch = matchmod.subdirmatcher(subpath, m)
2818 submatch = matchmod.subdirmatcher(subpath, m)
2819 if (subrepos or m.exact(subpath) or any(submatch.files())):
2819 if (subrepos or m.exact(subpath) or any(submatch.files())):
2820 sub = ctx.sub(subpath)
2820 sub = ctx.sub(subpath)
2821 try:
2821 try:
2822 recurse = m.exact(subpath) or subrepos
2822 recurse = m.exact(subpath) or subrepos
2823 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2823 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2824 ret = 0
2824 ret = 0
2825 except error.LookupError:
2825 except error.LookupError:
2826 ui.status(_("skipping missing subrepository: %s\n")
2826 ui.status(_("skipping missing subrepository: %s\n")
2827 % m.abs(subpath))
2827 % m.abs(subpath))
2828
2828
2829 return ret
2829 return ret
2830
2830
2831 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2831 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2832 join = lambda f: os.path.join(prefix, f)
2832 join = lambda f: os.path.join(prefix, f)
2833 ret = 0
2833 ret = 0
2834 s = repo.status(match=m, clean=True)
2834 s = repo.status(match=m, clean=True)
2835 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2835 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2836
2836
2837 wctx = repo[None]
2837 wctx = repo[None]
2838
2838
2839 if warnings is None:
2839 if warnings is None:
2840 warnings = []
2840 warnings = []
2841 warn = True
2841 warn = True
2842 else:
2842 else:
2843 warn = False
2843 warn = False
2844
2844
2845 subs = sorted(wctx.substate)
2845 subs = sorted(wctx.substate)
2846 total = len(subs)
2846 total = len(subs)
2847 count = 0
2847 count = 0
2848 for subpath in subs:
2848 for subpath in subs:
2849 count += 1
2849 count += 1
2850 submatch = matchmod.subdirmatcher(subpath, m)
2850 submatch = matchmod.subdirmatcher(subpath, m)
2851 if subrepos or m.exact(subpath) or any(submatch.files()):
2851 if subrepos or m.exact(subpath) or any(submatch.files()):
2852 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2852 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2853 sub = wctx.sub(subpath)
2853 sub = wctx.sub(subpath)
2854 try:
2854 try:
2855 if sub.removefiles(submatch, prefix, after, force, subrepos,
2855 if sub.removefiles(submatch, prefix, after, force, subrepos,
2856 warnings):
2856 warnings):
2857 ret = 1
2857 ret = 1
2858 except error.LookupError:
2858 except error.LookupError:
2859 warnings.append(_("skipping missing subrepository: %s\n")
2859 warnings.append(_("skipping missing subrepository: %s\n")
2860 % join(subpath))
2860 % join(subpath))
2861 ui.progress(_('searching'), None)
2861 ui.progress(_('searching'), None)
2862
2862
2863 # warn about failure to delete explicit files/dirs
2863 # warn about failure to delete explicit files/dirs
2864 deleteddirs = util.dirs(deleted)
2864 deleteddirs = util.dirs(deleted)
2865 files = m.files()
2865 files = m.files()
2866 total = len(files)
2866 total = len(files)
2867 count = 0
2867 count = 0
2868 for f in files:
2868 for f in files:
2869 def insubrepo():
2869 def insubrepo():
2870 for subpath in wctx.substate:
2870 for subpath in wctx.substate:
2871 if f.startswith(subpath + '/'):
2871 if f.startswith(subpath + '/'):
2872 return True
2872 return True
2873 return False
2873 return False
2874
2874
2875 count += 1
2875 count += 1
2876 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2876 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2877 isdir = f in deleteddirs or wctx.hasdir(f)
2877 isdir = f in deleteddirs or wctx.hasdir(f)
2878 if (f in repo.dirstate or isdir or f == '.'
2878 if (f in repo.dirstate or isdir or f == '.'
2879 or insubrepo() or f in subs):
2879 or insubrepo() or f in subs):
2880 continue
2880 continue
2881
2881
2882 if repo.wvfs.exists(f):
2882 if repo.wvfs.exists(f):
2883 if repo.wvfs.isdir(f):
2883 if repo.wvfs.isdir(f):
2884 warnings.append(_('not removing %s: no tracked files\n')
2884 warnings.append(_('not removing %s: no tracked files\n')
2885 % m.rel(f))
2885 % m.rel(f))
2886 else:
2886 else:
2887 warnings.append(_('not removing %s: file is untracked\n')
2887 warnings.append(_('not removing %s: file is untracked\n')
2888 % m.rel(f))
2888 % m.rel(f))
2889 # missing files will generate a warning elsewhere
2889 # missing files will generate a warning elsewhere
2890 ret = 1
2890 ret = 1
2891 ui.progress(_('deleting'), None)
2891 ui.progress(_('deleting'), None)
2892
2892
2893 if force:
2893 if force:
2894 list = modified + deleted + clean + added
2894 list = modified + deleted + clean + added
2895 elif after:
2895 elif after:
2896 list = deleted
2896 list = deleted
2897 remaining = modified + added + clean
2897 remaining = modified + added + clean
2898 total = len(remaining)
2898 total = len(remaining)
2899 count = 0
2899 count = 0
2900 for f in remaining:
2900 for f in remaining:
2901 count += 1
2901 count += 1
2902 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2902 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2903 warnings.append(_('not removing %s: file still exists\n')
2903 warnings.append(_('not removing %s: file still exists\n')
2904 % m.rel(f))
2904 % m.rel(f))
2905 ret = 1
2905 ret = 1
2906 ui.progress(_('skipping'), None)
2906 ui.progress(_('skipping'), None)
2907 else:
2907 else:
2908 list = deleted + clean
2908 list = deleted + clean
2909 total = len(modified) + len(added)
2909 total = len(modified) + len(added)
2910 count = 0
2910 count = 0
2911 for f in modified:
2911 for f in modified:
2912 count += 1
2912 count += 1
2913 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2913 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2914 warnings.append(_('not removing %s: file is modified (use -f'
2914 warnings.append(_('not removing %s: file is modified (use -f'
2915 ' to force removal)\n') % m.rel(f))
2915 ' to force removal)\n') % m.rel(f))
2916 ret = 1
2916 ret = 1
2917 for f in added:
2917 for f in added:
2918 count += 1
2918 count += 1
2919 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2919 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2920 warnings.append(_("not removing %s: file has been marked for add"
2920 warnings.append(_("not removing %s: file has been marked for add"
2921 " (use 'hg forget' to undo add)\n") % m.rel(f))
2921 " (use 'hg forget' to undo add)\n") % m.rel(f))
2922 ret = 1
2922 ret = 1
2923 ui.progress(_('skipping'), None)
2923 ui.progress(_('skipping'), None)
2924
2924
2925 list = sorted(list)
2925 list = sorted(list)
2926 total = len(list)
2926 total = len(list)
2927 count = 0
2927 count = 0
2928 for f in list:
2928 for f in list:
2929 count += 1
2929 count += 1
2930 if ui.verbose or not m.exact(f):
2930 if ui.verbose or not m.exact(f):
2931 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2931 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2932 ui.status(_('removing %s\n') % m.rel(f))
2932 ui.status(_('removing %s\n') % m.rel(f))
2933 ui.progress(_('deleting'), None)
2933 ui.progress(_('deleting'), None)
2934
2934
2935 with repo.wlock():
2935 with repo.wlock():
2936 if not after:
2936 if not after:
2937 for f in list:
2937 for f in list:
2938 if f in added:
2938 if f in added:
2939 continue # we never unlink added files on remove
2939 continue # we never unlink added files on remove
2940 repo.wvfs.unlinkpath(f, ignoremissing=True)
2940 repo.wvfs.unlinkpath(f, ignoremissing=True)
2941 repo[None].forget(list)
2941 repo[None].forget(list)
2942
2942
2943 if warn:
2943 if warn:
2944 for warning in warnings:
2944 for warning in warnings:
2945 ui.warn(warning)
2945 ui.warn(warning)
2946
2946
2947 return ret
2947 return ret
2948
2948
2949 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2949 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2950 err = 1
2950 err = 1
2951
2951
2952 def write(path):
2952 def write(path):
2953 filename = None
2953 filename = None
2954 if fntemplate:
2954 if fntemplate:
2955 filename = makefilename(repo, fntemplate, ctx.node(),
2955 filename = makefilename(repo, fntemplate, ctx.node(),
2956 pathname=os.path.join(prefix, path))
2956 pathname=os.path.join(prefix, path))
2957 with formatter.maybereopen(basefm, filename, opts) as fm:
2957 with formatter.maybereopen(basefm, filename, opts) as fm:
2958 data = ctx[path].data()
2958 data = ctx[path].data()
2959 if opts.get('decode'):
2959 if opts.get('decode'):
2960 data = repo.wwritedata(path, data)
2960 data = repo.wwritedata(path, data)
2961 fm.startitem()
2961 fm.startitem()
2962 fm.write('data', '%s', data)
2962 fm.write('data', '%s', data)
2963 fm.data(abspath=path, path=matcher.rel(path))
2963 fm.data(abspath=path, path=matcher.rel(path))
2964
2964
2965 # Automation often uses hg cat on single files, so special case it
2965 # Automation often uses hg cat on single files, so special case it
2966 # for performance to avoid the cost of parsing the manifest.
2966 # for performance to avoid the cost of parsing the manifest.
2967 if len(matcher.files()) == 1 and not matcher.anypats():
2967 if len(matcher.files()) == 1 and not matcher.anypats():
2968 file = matcher.files()[0]
2968 file = matcher.files()[0]
2969 mfl = repo.manifestlog
2969 mfl = repo.manifestlog
2970 mfnode = ctx.manifestnode()
2970 mfnode = ctx.manifestnode()
2971 try:
2971 try:
2972 if mfnode and mfl[mfnode].find(file)[0]:
2972 if mfnode and mfl[mfnode].find(file)[0]:
2973 write(file)
2973 write(file)
2974 return 0
2974 return 0
2975 except KeyError:
2975 except KeyError:
2976 pass
2976 pass
2977
2977
2978 for abs in ctx.walk(matcher):
2978 for abs in ctx.walk(matcher):
2979 write(abs)
2979 write(abs)
2980 err = 0
2980 err = 0
2981
2981
2982 for subpath in sorted(ctx.substate):
2982 for subpath in sorted(ctx.substate):
2983 sub = ctx.sub(subpath)
2983 sub = ctx.sub(subpath)
2984 try:
2984 try:
2985 submatch = matchmod.subdirmatcher(subpath, matcher)
2985 submatch = matchmod.subdirmatcher(subpath, matcher)
2986
2986
2987 if not sub.cat(submatch, basefm, fntemplate,
2987 if not sub.cat(submatch, basefm, fntemplate,
2988 os.path.join(prefix, sub._path), **opts):
2988 os.path.join(prefix, sub._path), **opts):
2989 err = 0
2989 err = 0
2990 except error.RepoLookupError:
2990 except error.RepoLookupError:
2991 ui.status(_("skipping missing subrepository: %s\n")
2991 ui.status(_("skipping missing subrepository: %s\n")
2992 % os.path.join(prefix, subpath))
2992 % os.path.join(prefix, subpath))
2993
2993
2994 return err
2994 return err
2995
2995
2996 def commit(ui, repo, commitfunc, pats, opts):
2996 def commit(ui, repo, commitfunc, pats, opts):
2997 '''commit the specified files or all outstanding changes'''
2997 '''commit the specified files or all outstanding changes'''
2998 date = opts.get('date')
2998 date = opts.get('date')
2999 if date:
2999 if date:
3000 opts['date'] = util.parsedate(date)
3000 opts['date'] = util.parsedate(date)
3001 message = logmessage(ui, opts)
3001 message = logmessage(ui, opts)
3002 matcher = scmutil.match(repo[None], pats, opts)
3002 matcher = scmutil.match(repo[None], pats, opts)
3003
3003
3004 dsguard = None
3004 dsguard = None
3005 # extract addremove carefully -- this function can be called from a command
3005 # extract addremove carefully -- this function can be called from a command
3006 # that doesn't support addremove
3006 # that doesn't support addremove
3007 if opts.get('addremove'):
3007 if opts.get('addremove'):
3008 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3008 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3009 with dsguard or util.nullcontextmanager():
3009 with dsguard or util.nullcontextmanager():
3010 if dsguard:
3010 if dsguard:
3011 if scmutil.addremove(repo, matcher, "", opts) != 0:
3011 if scmutil.addremove(repo, matcher, "", opts) != 0:
3012 raise error.Abort(
3012 raise error.Abort(
3013 _("failed to mark all new/missing files as added/removed"))
3013 _("failed to mark all new/missing files as added/removed"))
3014
3014
3015 return commitfunc(ui, repo, message, matcher, opts)
3015 return commitfunc(ui, repo, message, matcher, opts)
3016
3016
3017 def samefile(f, ctx1, ctx2):
3017 def samefile(f, ctx1, ctx2):
3018 if f in ctx1.manifest():
3018 if f in ctx1.manifest():
3019 a = ctx1.filectx(f)
3019 a = ctx1.filectx(f)
3020 if f in ctx2.manifest():
3020 if f in ctx2.manifest():
3021 b = ctx2.filectx(f)
3021 b = ctx2.filectx(f)
3022 return (not a.cmp(b)
3022 return (not a.cmp(b)
3023 and a.flags() == b.flags())
3023 and a.flags() == b.flags())
3024 else:
3024 else:
3025 return False
3025 return False
3026 else:
3026 else:
3027 return f not in ctx2.manifest()
3027 return f not in ctx2.manifest()
3028
3028
3029 def amend(ui, repo, old, extra, pats, opts):
3029 def amend(ui, repo, old, extra, pats, opts):
3030 # avoid cycle context -> subrepo -> cmdutil
3030 # avoid cycle context -> subrepo -> cmdutil
3031 from . import context
3031 from . import context
3032
3032
3033 # amend will reuse the existing user if not specified, but the obsolete
3033 # amend will reuse the existing user if not specified, but the obsolete
3034 # marker creation requires that the current user's name is specified.
3034 # marker creation requires that the current user's name is specified.
3035 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3035 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3036 ui.username() # raise exception if username not set
3036 ui.username() # raise exception if username not set
3037
3037
3038 ui.note(_('amending changeset %s\n') % old)
3038 ui.note(_('amending changeset %s\n') % old)
3039 base = old.p1()
3039 base = old.p1()
3040
3040
3041 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3041 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3042 # Participating changesets:
3042 # Participating changesets:
3043 #
3043 #
3044 # wctx o - workingctx that contains changes from working copy
3044 # wctx o - workingctx that contains changes from working copy
3045 # | to go into amending commit
3045 # | to go into amending commit
3046 # |
3046 # |
3047 # old o - changeset to amend
3047 # old o - changeset to amend
3048 # |
3048 # |
3049 # base o - first parent of the changeset to amend
3049 # base o - first parent of the changeset to amend
3050 wctx = repo[None]
3050 wctx = repo[None]
3051
3051
3052 # Update extra dict from amended commit (e.g. to preserve graft
3052 # Update extra dict from amended commit (e.g. to preserve graft
3053 # source)
3053 # source)
3054 extra.update(old.extra())
3054 extra.update(old.extra())
3055
3055
3056 # Also update it from the from the wctx
3056 # Also update it from the from the wctx
3057 extra.update(wctx.extra())
3057 extra.update(wctx.extra())
3058
3058
3059 user = opts.get('user') or old.user()
3059 user = opts.get('user') or old.user()
3060 date = opts.get('date') or old.date()
3060 date = opts.get('date') or old.date()
3061
3061
3062 # Parse the date to allow comparison between date and old.date()
3063 date = util.parsedate(date)
3064
3062 if len(old.parents()) > 1:
3065 if len(old.parents()) > 1:
3063 # ctx.files() isn't reliable for merges, so fall back to the
3066 # ctx.files() isn't reliable for merges, so fall back to the
3064 # slower repo.status() method
3067 # slower repo.status() method
3065 files = set([fn for st in repo.status(base, old)[:3]
3068 files = set([fn for st in repo.status(base, old)[:3]
3066 for fn in st])
3069 for fn in st])
3067 else:
3070 else:
3068 files = set(old.files())
3071 files = set(old.files())
3069
3072
3070 # add/remove the files to the working copy if the "addremove" option
3073 # add/remove the files to the working copy if the "addremove" option
3071 # was specified.
3074 # was specified.
3072 matcher = scmutil.match(wctx, pats, opts)
3075 matcher = scmutil.match(wctx, pats, opts)
3073 if (opts.get('addremove')
3076 if (opts.get('addremove')
3074 and scmutil.addremove(repo, matcher, "", opts)):
3077 and scmutil.addremove(repo, matcher, "", opts)):
3075 raise error.Abort(
3078 raise error.Abort(
3076 _("failed to mark all new/missing files as added/removed"))
3079 _("failed to mark all new/missing files as added/removed"))
3077
3080
3078 filestoamend = set(f for f in wctx.files() if matcher(f))
3081 filestoamend = set(f for f in wctx.files() if matcher(f))
3079
3082
3080 changes = (len(filestoamend) > 0)
3083 changes = (len(filestoamend) > 0)
3081 if changes:
3084 if changes:
3082 # Recompute copies (avoid recording a -> b -> a)
3085 # Recompute copies (avoid recording a -> b -> a)
3083 copied = copies.pathcopies(base, wctx, matcher)
3086 copied = copies.pathcopies(base, wctx, matcher)
3084 if old.p2:
3087 if old.p2:
3085 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3088 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3086
3089
3087 # Prune files which were reverted by the updates: if old
3090 # Prune files which were reverted by the updates: if old
3088 # introduced file X and the file was renamed in the working
3091 # introduced file X and the file was renamed in the working
3089 # copy, then those two files are the same and
3092 # copy, then those two files are the same and
3090 # we can discard X from our list of files. Likewise if X
3093 # we can discard X from our list of files. Likewise if X
3091 # was deleted, it's no longer relevant
3094 # was deleted, it's no longer relevant
3092 files.update(filestoamend)
3095 files.update(filestoamend)
3093 files = [f for f in files if not samefile(f, wctx, base)]
3096 files = [f for f in files if not samefile(f, wctx, base)]
3094
3097
3095 def filectxfn(repo, ctx_, path):
3098 def filectxfn(repo, ctx_, path):
3096 try:
3099 try:
3097 # If the file being considered is not amongst the files
3100 # If the file being considered is not amongst the files
3098 # to be amended, we should return the file context from the
3101 # to be amended, we should return the file context from the
3099 # old changeset. This avoids issues when only some files in
3102 # old changeset. This avoids issues when only some files in
3100 # the working copy are being amended but there are also
3103 # the working copy are being amended but there are also
3101 # changes to other files from the old changeset.
3104 # changes to other files from the old changeset.
3102 if path not in filestoamend:
3105 if path not in filestoamend:
3103 return old.filectx(path)
3106 return old.filectx(path)
3104
3107
3105 fctx = wctx[path]
3108 fctx = wctx[path]
3106
3109
3107 # Return None for removed files.
3110 # Return None for removed files.
3108 if not fctx.exists():
3111 if not fctx.exists():
3109 return None
3112 return None
3110
3113
3111 flags = fctx.flags()
3114 flags = fctx.flags()
3112 mctx = context.memfilectx(repo,
3115 mctx = context.memfilectx(repo,
3113 fctx.path(), fctx.data(),
3116 fctx.path(), fctx.data(),
3114 islink='l' in flags,
3117 islink='l' in flags,
3115 isexec='x' in flags,
3118 isexec='x' in flags,
3116 copied=copied.get(path))
3119 copied=copied.get(path))
3117 return mctx
3120 return mctx
3118 except KeyError:
3121 except KeyError:
3119 return None
3122 return None
3120 else:
3123 else:
3121 ui.note(_('copying changeset %s to %s\n') % (old, base))
3124 ui.note(_('copying changeset %s to %s\n') % (old, base))
3122
3125
3123 # Use version of files as in the old cset
3126 # Use version of files as in the old cset
3124 def filectxfn(repo, ctx_, path):
3127 def filectxfn(repo, ctx_, path):
3125 try:
3128 try:
3126 return old.filectx(path)
3129 return old.filectx(path)
3127 except KeyError:
3130 except KeyError:
3128 return None
3131 return None
3129
3132
3130 # See if we got a message from -m or -l, if not, open the editor with
3133 # See if we got a message from -m or -l, if not, open the editor with
3131 # the message of the changeset to amend.
3134 # the message of the changeset to amend.
3132 message = logmessage(ui, opts)
3135 message = logmessage(ui, opts)
3133
3136
3134 editform = mergeeditform(old, 'commit.amend')
3137 editform = mergeeditform(old, 'commit.amend')
3135 editor = getcommiteditor(editform=editform,
3138 editor = getcommiteditor(editform=editform,
3136 **pycompat.strkwargs(opts))
3139 **pycompat.strkwargs(opts))
3137
3140
3138 if not message:
3141 if not message:
3139 editor = getcommiteditor(edit=True, editform=editform)
3142 editor = getcommiteditor(edit=True, editform=editform)
3140 message = old.description()
3143 message = old.description()
3141
3144
3142 pureextra = extra.copy()
3145 pureextra = extra.copy()
3143 extra['amend_source'] = old.hex()
3146 extra['amend_source'] = old.hex()
3144
3147
3145 new = context.memctx(repo,
3148 new = context.memctx(repo,
3146 parents=[base.node(), old.p2().node()],
3149 parents=[base.node(), old.p2().node()],
3147 text=message,
3150 text=message,
3148 files=files,
3151 files=files,
3149 filectxfn=filectxfn,
3152 filectxfn=filectxfn,
3150 user=user,
3153 user=user,
3151 date=date,
3154 date=date,
3152 extra=extra,
3155 extra=extra,
3153 editor=editor)
3156 editor=editor)
3154
3157
3155 newdesc = changelog.stripdesc(new.description())
3158 newdesc = changelog.stripdesc(new.description())
3156 if ((not changes)
3159 if ((not changes)
3157 and newdesc == old.description()
3160 and newdesc == old.description()
3158 and user == old.user()
3161 and user == old.user()
3159 and date == old.date()
3162 and date == old.date()
3160 and pureextra == old.extra()):
3163 and pureextra == old.extra()):
3161 # nothing changed. continuing here would create a new node
3164 # nothing changed. continuing here would create a new node
3162 # anyway because of the amend_source noise.
3165 # anyway because of the amend_source noise.
3163 #
3166 #
3164 # This not what we expect from amend.
3167 # This not what we expect from amend.
3165 return old.node()
3168 return old.node()
3166
3169
3167 if opts.get('secret'):
3170 if opts.get('secret'):
3168 commitphase = 'secret'
3171 commitphase = 'secret'
3169 else:
3172 else:
3170 commitphase = old.phase()
3173 commitphase = old.phase()
3171 overrides = {('phases', 'new-commit'): commitphase}
3174 overrides = {('phases', 'new-commit'): commitphase}
3172 with ui.configoverride(overrides, 'amend'):
3175 with ui.configoverride(overrides, 'amend'):
3173 newid = repo.commitctx(new)
3176 newid = repo.commitctx(new)
3174
3177
3175 # Reroute the working copy parent to the new changeset
3178 # Reroute the working copy parent to the new changeset
3176 repo.setparents(newid, nullid)
3179 repo.setparents(newid, nullid)
3177 mapping = {old.node(): (newid,)}
3180 mapping = {old.node(): (newid,)}
3178 scmutil.cleanupnodes(repo, mapping, 'amend')
3181 scmutil.cleanupnodes(repo, mapping, 'amend')
3179
3182
3180 # Fixing the dirstate because localrepo.commitctx does not update
3183 # Fixing the dirstate because localrepo.commitctx does not update
3181 # it. This is rather convenient because we did not need to update
3184 # it. This is rather convenient because we did not need to update
3182 # the dirstate for all the files in the new commit which commitctx
3185 # the dirstate for all the files in the new commit which commitctx
3183 # could have done if it updated the dirstate. Now, we can
3186 # could have done if it updated the dirstate. Now, we can
3184 # selectively update the dirstate only for the amended files.
3187 # selectively update the dirstate only for the amended files.
3185 dirstate = repo.dirstate
3188 dirstate = repo.dirstate
3186
3189
3187 # Update the state of the files which were added and
3190 # Update the state of the files which were added and
3188 # and modified in the amend to "normal" in the dirstate.
3191 # and modified in the amend to "normal" in the dirstate.
3189 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3192 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3190 for f in normalfiles:
3193 for f in normalfiles:
3191 dirstate.normal(f)
3194 dirstate.normal(f)
3192
3195
3193 # Update the state of files which were removed in the amend
3196 # Update the state of files which were removed in the amend
3194 # to "removed" in the dirstate.
3197 # to "removed" in the dirstate.
3195 removedfiles = set(wctx.removed()) & filestoamend
3198 removedfiles = set(wctx.removed()) & filestoamend
3196 for f in removedfiles:
3199 for f in removedfiles:
3197 dirstate.drop(f)
3200 dirstate.drop(f)
3198
3201
3199 return newid
3202 return newid
3200
3203
3201 def commiteditor(repo, ctx, subs, editform=''):
3204 def commiteditor(repo, ctx, subs, editform=''):
3202 if ctx.description():
3205 if ctx.description():
3203 return ctx.description()
3206 return ctx.description()
3204 return commitforceeditor(repo, ctx, subs, editform=editform,
3207 return commitforceeditor(repo, ctx, subs, editform=editform,
3205 unchangedmessagedetection=True)
3208 unchangedmessagedetection=True)
3206
3209
3207 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3210 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3208 editform='', unchangedmessagedetection=False):
3211 editform='', unchangedmessagedetection=False):
3209 if not extramsg:
3212 if not extramsg:
3210 extramsg = _("Leave message empty to abort commit.")
3213 extramsg = _("Leave message empty to abort commit.")
3211
3214
3212 forms = [e for e in editform.split('.') if e]
3215 forms = [e for e in editform.split('.') if e]
3213 forms.insert(0, 'changeset')
3216 forms.insert(0, 'changeset')
3214 templatetext = None
3217 templatetext = None
3215 while forms:
3218 while forms:
3216 ref = '.'.join(forms)
3219 ref = '.'.join(forms)
3217 if repo.ui.config('committemplate', ref):
3220 if repo.ui.config('committemplate', ref):
3218 templatetext = committext = buildcommittemplate(
3221 templatetext = committext = buildcommittemplate(
3219 repo, ctx, subs, extramsg, ref)
3222 repo, ctx, subs, extramsg, ref)
3220 break
3223 break
3221 forms.pop()
3224 forms.pop()
3222 else:
3225 else:
3223 committext = buildcommittext(repo, ctx, subs, extramsg)
3226 committext = buildcommittext(repo, ctx, subs, extramsg)
3224
3227
3225 # run editor in the repository root
3228 # run editor in the repository root
3226 olddir = pycompat.getcwd()
3229 olddir = pycompat.getcwd()
3227 os.chdir(repo.root)
3230 os.chdir(repo.root)
3228
3231
3229 # make in-memory changes visible to external process
3232 # make in-memory changes visible to external process
3230 tr = repo.currenttransaction()
3233 tr = repo.currenttransaction()
3231 repo.dirstate.write(tr)
3234 repo.dirstate.write(tr)
3232 pending = tr and tr.writepending() and repo.root
3235 pending = tr and tr.writepending() and repo.root
3233
3236
3234 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3237 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3235 editform=editform, pending=pending,
3238 editform=editform, pending=pending,
3236 repopath=repo.path, action='commit')
3239 repopath=repo.path, action='commit')
3237 text = editortext
3240 text = editortext
3238
3241
3239 # strip away anything below this special string (used for editors that want
3242 # strip away anything below this special string (used for editors that want
3240 # to display the diff)
3243 # to display the diff)
3241 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3244 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3242 if stripbelow:
3245 if stripbelow:
3243 text = text[:stripbelow.start()]
3246 text = text[:stripbelow.start()]
3244
3247
3245 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3248 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3246 os.chdir(olddir)
3249 os.chdir(olddir)
3247
3250
3248 if finishdesc:
3251 if finishdesc:
3249 text = finishdesc(text)
3252 text = finishdesc(text)
3250 if not text.strip():
3253 if not text.strip():
3251 raise error.Abort(_("empty commit message"))
3254 raise error.Abort(_("empty commit message"))
3252 if unchangedmessagedetection and editortext == templatetext:
3255 if unchangedmessagedetection and editortext == templatetext:
3253 raise error.Abort(_("commit message unchanged"))
3256 raise error.Abort(_("commit message unchanged"))
3254
3257
3255 return text
3258 return text
3256
3259
3257 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3260 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3258 ui = repo.ui
3261 ui = repo.ui
3259 spec = formatter.templatespec(ref, None, None)
3262 spec = formatter.templatespec(ref, None, None)
3260 t = changeset_templater(ui, repo, spec, None, {}, False)
3263 t = changeset_templater(ui, repo, spec, None, {}, False)
3261 t.t.cache.update((k, templater.unquotestring(v))
3264 t.t.cache.update((k, templater.unquotestring(v))
3262 for k, v in repo.ui.configitems('committemplate'))
3265 for k, v in repo.ui.configitems('committemplate'))
3263
3266
3264 if not extramsg:
3267 if not extramsg:
3265 extramsg = '' # ensure that extramsg is string
3268 extramsg = '' # ensure that extramsg is string
3266
3269
3267 ui.pushbuffer()
3270 ui.pushbuffer()
3268 t.show(ctx, extramsg=extramsg)
3271 t.show(ctx, extramsg=extramsg)
3269 return ui.popbuffer()
3272 return ui.popbuffer()
3270
3273
3271 def hgprefix(msg):
3274 def hgprefix(msg):
3272 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3275 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3273
3276
3274 def buildcommittext(repo, ctx, subs, extramsg):
3277 def buildcommittext(repo, ctx, subs, extramsg):
3275 edittext = []
3278 edittext = []
3276 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3279 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3277 if ctx.description():
3280 if ctx.description():
3278 edittext.append(ctx.description())
3281 edittext.append(ctx.description())
3279 edittext.append("")
3282 edittext.append("")
3280 edittext.append("") # Empty line between message and comments.
3283 edittext.append("") # Empty line between message and comments.
3281 edittext.append(hgprefix(_("Enter commit message."
3284 edittext.append(hgprefix(_("Enter commit message."
3282 " Lines beginning with 'HG:' are removed.")))
3285 " Lines beginning with 'HG:' are removed.")))
3283 edittext.append(hgprefix(extramsg))
3286 edittext.append(hgprefix(extramsg))
3284 edittext.append("HG: --")
3287 edittext.append("HG: --")
3285 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3288 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3286 if ctx.p2():
3289 if ctx.p2():
3287 edittext.append(hgprefix(_("branch merge")))
3290 edittext.append(hgprefix(_("branch merge")))
3288 if ctx.branch():
3291 if ctx.branch():
3289 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3292 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3290 if bookmarks.isactivewdirparent(repo):
3293 if bookmarks.isactivewdirparent(repo):
3291 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3294 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3292 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3295 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3293 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3296 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3294 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3297 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3295 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3298 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3296 if not added and not modified and not removed:
3299 if not added and not modified and not removed:
3297 edittext.append(hgprefix(_("no files changed")))
3300 edittext.append(hgprefix(_("no files changed")))
3298 edittext.append("")
3301 edittext.append("")
3299
3302
3300 return "\n".join(edittext)
3303 return "\n".join(edittext)
3301
3304
3302 def commitstatus(repo, node, branch, bheads=None, opts=None):
3305 def commitstatus(repo, node, branch, bheads=None, opts=None):
3303 if opts is None:
3306 if opts is None:
3304 opts = {}
3307 opts = {}
3305 ctx = repo[node]
3308 ctx = repo[node]
3306 parents = ctx.parents()
3309 parents = ctx.parents()
3307
3310
3308 if (not opts.get('amend') and bheads and node not in bheads and not
3311 if (not opts.get('amend') and bheads and node not in bheads and not
3309 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3312 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3310 repo.ui.status(_('created new head\n'))
3313 repo.ui.status(_('created new head\n'))
3311 # The message is not printed for initial roots. For the other
3314 # The message is not printed for initial roots. For the other
3312 # changesets, it is printed in the following situations:
3315 # changesets, it is printed in the following situations:
3313 #
3316 #
3314 # Par column: for the 2 parents with ...
3317 # Par column: for the 2 parents with ...
3315 # N: null or no parent
3318 # N: null or no parent
3316 # B: parent is on another named branch
3319 # B: parent is on another named branch
3317 # C: parent is a regular non head changeset
3320 # C: parent is a regular non head changeset
3318 # H: parent was a branch head of the current branch
3321 # H: parent was a branch head of the current branch
3319 # Msg column: whether we print "created new head" message
3322 # Msg column: whether we print "created new head" message
3320 # In the following, it is assumed that there already exists some
3323 # In the following, it is assumed that there already exists some
3321 # initial branch heads of the current branch, otherwise nothing is
3324 # initial branch heads of the current branch, otherwise nothing is
3322 # printed anyway.
3325 # printed anyway.
3323 #
3326 #
3324 # Par Msg Comment
3327 # Par Msg Comment
3325 # N N y additional topo root
3328 # N N y additional topo root
3326 #
3329 #
3327 # B N y additional branch root
3330 # B N y additional branch root
3328 # C N y additional topo head
3331 # C N y additional topo head
3329 # H N n usual case
3332 # H N n usual case
3330 #
3333 #
3331 # B B y weird additional branch root
3334 # B B y weird additional branch root
3332 # C B y branch merge
3335 # C B y branch merge
3333 # H B n merge with named branch
3336 # H B n merge with named branch
3334 #
3337 #
3335 # C C y additional head from merge
3338 # C C y additional head from merge
3336 # C H n merge with a head
3339 # C H n merge with a head
3337 #
3340 #
3338 # H H n head merge: head count decreases
3341 # H H n head merge: head count decreases
3339
3342
3340 if not opts.get('close_branch'):
3343 if not opts.get('close_branch'):
3341 for r in parents:
3344 for r in parents:
3342 if r.closesbranch() and r.branch() == branch:
3345 if r.closesbranch() and r.branch() == branch:
3343 repo.ui.status(_('reopening closed branch head %d\n') % r)
3346 repo.ui.status(_('reopening closed branch head %d\n') % r)
3344
3347
3345 if repo.ui.debugflag:
3348 if repo.ui.debugflag:
3346 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3349 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3347 elif repo.ui.verbose:
3350 elif repo.ui.verbose:
3348 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3351 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3349
3352
3350 def postcommitstatus(repo, pats, opts):
3353 def postcommitstatus(repo, pats, opts):
3351 return repo.status(match=scmutil.match(repo[None], pats, opts))
3354 return repo.status(match=scmutil.match(repo[None], pats, opts))
3352
3355
3353 def revert(ui, repo, ctx, parents, *pats, **opts):
3356 def revert(ui, repo, ctx, parents, *pats, **opts):
3354 parent, p2 = parents
3357 parent, p2 = parents
3355 node = ctx.node()
3358 node = ctx.node()
3356
3359
3357 mf = ctx.manifest()
3360 mf = ctx.manifest()
3358 if node == p2:
3361 if node == p2:
3359 parent = p2
3362 parent = p2
3360
3363
3361 # need all matching names in dirstate and manifest of target rev,
3364 # need all matching names in dirstate and manifest of target rev,
3362 # so have to walk both. do not print errors if files exist in one
3365 # so have to walk both. do not print errors if files exist in one
3363 # but not other. in both cases, filesets should be evaluated against
3366 # but not other. in both cases, filesets should be evaluated against
3364 # workingctx to get consistent result (issue4497). this means 'set:**'
3367 # workingctx to get consistent result (issue4497). this means 'set:**'
3365 # cannot be used to select missing files from target rev.
3368 # cannot be used to select missing files from target rev.
3366
3369
3367 # `names` is a mapping for all elements in working copy and target revision
3370 # `names` is a mapping for all elements in working copy and target revision
3368 # The mapping is in the form:
3371 # The mapping is in the form:
3369 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3372 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3370 names = {}
3373 names = {}
3371
3374
3372 with repo.wlock():
3375 with repo.wlock():
3373 ## filling of the `names` mapping
3376 ## filling of the `names` mapping
3374 # walk dirstate to fill `names`
3377 # walk dirstate to fill `names`
3375
3378
3376 interactive = opts.get('interactive', False)
3379 interactive = opts.get('interactive', False)
3377 wctx = repo[None]
3380 wctx = repo[None]
3378 m = scmutil.match(wctx, pats, opts)
3381 m = scmutil.match(wctx, pats, opts)
3379
3382
3380 # we'll need this later
3383 # we'll need this later
3381 targetsubs = sorted(s for s in wctx.substate if m(s))
3384 targetsubs = sorted(s for s in wctx.substate if m(s))
3382
3385
3383 if not m.always():
3386 if not m.always():
3384 matcher = matchmod.badmatch(m, lambda x, y: False)
3387 matcher = matchmod.badmatch(m, lambda x, y: False)
3385 for abs in wctx.walk(matcher):
3388 for abs in wctx.walk(matcher):
3386 names[abs] = m.rel(abs), m.exact(abs)
3389 names[abs] = m.rel(abs), m.exact(abs)
3387
3390
3388 # walk target manifest to fill `names`
3391 # walk target manifest to fill `names`
3389
3392
3390 def badfn(path, msg):
3393 def badfn(path, msg):
3391 if path in names:
3394 if path in names:
3392 return
3395 return
3393 if path in ctx.substate:
3396 if path in ctx.substate:
3394 return
3397 return
3395 path_ = path + '/'
3398 path_ = path + '/'
3396 for f in names:
3399 for f in names:
3397 if f.startswith(path_):
3400 if f.startswith(path_):
3398 return
3401 return
3399 ui.warn("%s: %s\n" % (m.rel(path), msg))
3402 ui.warn("%s: %s\n" % (m.rel(path), msg))
3400
3403
3401 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3404 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3402 if abs not in names:
3405 if abs not in names:
3403 names[abs] = m.rel(abs), m.exact(abs)
3406 names[abs] = m.rel(abs), m.exact(abs)
3404
3407
3405 # Find status of all file in `names`.
3408 # Find status of all file in `names`.
3406 m = scmutil.matchfiles(repo, names)
3409 m = scmutil.matchfiles(repo, names)
3407
3410
3408 changes = repo.status(node1=node, match=m,
3411 changes = repo.status(node1=node, match=m,
3409 unknown=True, ignored=True, clean=True)
3412 unknown=True, ignored=True, clean=True)
3410 else:
3413 else:
3411 changes = repo.status(node1=node, match=m)
3414 changes = repo.status(node1=node, match=m)
3412 for kind in changes:
3415 for kind in changes:
3413 for abs in kind:
3416 for abs in kind:
3414 names[abs] = m.rel(abs), m.exact(abs)
3417 names[abs] = m.rel(abs), m.exact(abs)
3415
3418
3416 m = scmutil.matchfiles(repo, names)
3419 m = scmutil.matchfiles(repo, names)
3417
3420
3418 modified = set(changes.modified)
3421 modified = set(changes.modified)
3419 added = set(changes.added)
3422 added = set(changes.added)
3420 removed = set(changes.removed)
3423 removed = set(changes.removed)
3421 _deleted = set(changes.deleted)
3424 _deleted = set(changes.deleted)
3422 unknown = set(changes.unknown)
3425 unknown = set(changes.unknown)
3423 unknown.update(changes.ignored)
3426 unknown.update(changes.ignored)
3424 clean = set(changes.clean)
3427 clean = set(changes.clean)
3425 modadded = set()
3428 modadded = set()
3426
3429
3427 # We need to account for the state of the file in the dirstate,
3430 # We need to account for the state of the file in the dirstate,
3428 # even when we revert against something else than parent. This will
3431 # even when we revert against something else than parent. This will
3429 # slightly alter the behavior of revert (doing back up or not, delete
3432 # slightly alter the behavior of revert (doing back up or not, delete
3430 # or just forget etc).
3433 # or just forget etc).
3431 if parent == node:
3434 if parent == node:
3432 dsmodified = modified
3435 dsmodified = modified
3433 dsadded = added
3436 dsadded = added
3434 dsremoved = removed
3437 dsremoved = removed
3435 # store all local modifications, useful later for rename detection
3438 # store all local modifications, useful later for rename detection
3436 localchanges = dsmodified | dsadded
3439 localchanges = dsmodified | dsadded
3437 modified, added, removed = set(), set(), set()
3440 modified, added, removed = set(), set(), set()
3438 else:
3441 else:
3439 changes = repo.status(node1=parent, match=m)
3442 changes = repo.status(node1=parent, match=m)
3440 dsmodified = set(changes.modified)
3443 dsmodified = set(changes.modified)
3441 dsadded = set(changes.added)
3444 dsadded = set(changes.added)
3442 dsremoved = set(changes.removed)
3445 dsremoved = set(changes.removed)
3443 # store all local modifications, useful later for rename detection
3446 # store all local modifications, useful later for rename detection
3444 localchanges = dsmodified | dsadded
3447 localchanges = dsmodified | dsadded
3445
3448
3446 # only take into account for removes between wc and target
3449 # only take into account for removes between wc and target
3447 clean |= dsremoved - removed
3450 clean |= dsremoved - removed
3448 dsremoved &= removed
3451 dsremoved &= removed
3449 # distinct between dirstate remove and other
3452 # distinct between dirstate remove and other
3450 removed -= dsremoved
3453 removed -= dsremoved
3451
3454
3452 modadded = added & dsmodified
3455 modadded = added & dsmodified
3453 added -= modadded
3456 added -= modadded
3454
3457
3455 # tell newly modified apart.
3458 # tell newly modified apart.
3456 dsmodified &= modified
3459 dsmodified &= modified
3457 dsmodified |= modified & dsadded # dirstate added may need backup
3460 dsmodified |= modified & dsadded # dirstate added may need backup
3458 modified -= dsmodified
3461 modified -= dsmodified
3459
3462
3460 # We need to wait for some post-processing to update this set
3463 # We need to wait for some post-processing to update this set
3461 # before making the distinction. The dirstate will be used for
3464 # before making the distinction. The dirstate will be used for
3462 # that purpose.
3465 # that purpose.
3463 dsadded = added
3466 dsadded = added
3464
3467
3465 # in case of merge, files that are actually added can be reported as
3468 # in case of merge, files that are actually added can be reported as
3466 # modified, we need to post process the result
3469 # modified, we need to post process the result
3467 if p2 != nullid:
3470 if p2 != nullid:
3468 mergeadd = set(dsmodified)
3471 mergeadd = set(dsmodified)
3469 for path in dsmodified:
3472 for path in dsmodified:
3470 if path in mf:
3473 if path in mf:
3471 mergeadd.remove(path)
3474 mergeadd.remove(path)
3472 dsadded |= mergeadd
3475 dsadded |= mergeadd
3473 dsmodified -= mergeadd
3476 dsmodified -= mergeadd
3474
3477
3475 # if f is a rename, update `names` to also revert the source
3478 # if f is a rename, update `names` to also revert the source
3476 cwd = repo.getcwd()
3479 cwd = repo.getcwd()
3477 for f in localchanges:
3480 for f in localchanges:
3478 src = repo.dirstate.copied(f)
3481 src = repo.dirstate.copied(f)
3479 # XXX should we check for rename down to target node?
3482 # XXX should we check for rename down to target node?
3480 if src and src not in names and repo.dirstate[src] == 'r':
3483 if src and src not in names and repo.dirstate[src] == 'r':
3481 dsremoved.add(src)
3484 dsremoved.add(src)
3482 names[src] = (repo.pathto(src, cwd), True)
3485 names[src] = (repo.pathto(src, cwd), True)
3483
3486
3484 # determine the exact nature of the deleted changesets
3487 # determine the exact nature of the deleted changesets
3485 deladded = set(_deleted)
3488 deladded = set(_deleted)
3486 for path in _deleted:
3489 for path in _deleted:
3487 if path in mf:
3490 if path in mf:
3488 deladded.remove(path)
3491 deladded.remove(path)
3489 deleted = _deleted - deladded
3492 deleted = _deleted - deladded
3490
3493
3491 # distinguish between file to forget and the other
3494 # distinguish between file to forget and the other
3492 added = set()
3495 added = set()
3493 for abs in dsadded:
3496 for abs in dsadded:
3494 if repo.dirstate[abs] != 'a':
3497 if repo.dirstate[abs] != 'a':
3495 added.add(abs)
3498 added.add(abs)
3496 dsadded -= added
3499 dsadded -= added
3497
3500
3498 for abs in deladded:
3501 for abs in deladded:
3499 if repo.dirstate[abs] == 'a':
3502 if repo.dirstate[abs] == 'a':
3500 dsadded.add(abs)
3503 dsadded.add(abs)
3501 deladded -= dsadded
3504 deladded -= dsadded
3502
3505
3503 # For files marked as removed, we check if an unknown file is present at
3506 # For files marked as removed, we check if an unknown file is present at
3504 # the same path. If a such file exists it may need to be backed up.
3507 # the same path. If a such file exists it may need to be backed up.
3505 # Making the distinction at this stage helps have simpler backup
3508 # Making the distinction at this stage helps have simpler backup
3506 # logic.
3509 # logic.
3507 removunk = set()
3510 removunk = set()
3508 for abs in removed:
3511 for abs in removed:
3509 target = repo.wjoin(abs)
3512 target = repo.wjoin(abs)
3510 if os.path.lexists(target):
3513 if os.path.lexists(target):
3511 removunk.add(abs)
3514 removunk.add(abs)
3512 removed -= removunk
3515 removed -= removunk
3513
3516
3514 dsremovunk = set()
3517 dsremovunk = set()
3515 for abs in dsremoved:
3518 for abs in dsremoved:
3516 target = repo.wjoin(abs)
3519 target = repo.wjoin(abs)
3517 if os.path.lexists(target):
3520 if os.path.lexists(target):
3518 dsremovunk.add(abs)
3521 dsremovunk.add(abs)
3519 dsremoved -= dsremovunk
3522 dsremoved -= dsremovunk
3520
3523
3521 # action to be actually performed by revert
3524 # action to be actually performed by revert
3522 # (<list of file>, message>) tuple
3525 # (<list of file>, message>) tuple
3523 actions = {'revert': ([], _('reverting %s\n')),
3526 actions = {'revert': ([], _('reverting %s\n')),
3524 'add': ([], _('adding %s\n')),
3527 'add': ([], _('adding %s\n')),
3525 'remove': ([], _('removing %s\n')),
3528 'remove': ([], _('removing %s\n')),
3526 'drop': ([], _('removing %s\n')),
3529 'drop': ([], _('removing %s\n')),
3527 'forget': ([], _('forgetting %s\n')),
3530 'forget': ([], _('forgetting %s\n')),
3528 'undelete': ([], _('undeleting %s\n')),
3531 'undelete': ([], _('undeleting %s\n')),
3529 'noop': (None, _('no changes needed to %s\n')),
3532 'noop': (None, _('no changes needed to %s\n')),
3530 'unknown': (None, _('file not managed: %s\n')),
3533 'unknown': (None, _('file not managed: %s\n')),
3531 }
3534 }
3532
3535
3533 # "constant" that convey the backup strategy.
3536 # "constant" that convey the backup strategy.
3534 # All set to `discard` if `no-backup` is set do avoid checking
3537 # All set to `discard` if `no-backup` is set do avoid checking
3535 # no_backup lower in the code.
3538 # no_backup lower in the code.
3536 # These values are ordered for comparison purposes
3539 # These values are ordered for comparison purposes
3537 backupinteractive = 3 # do backup if interactively modified
3540 backupinteractive = 3 # do backup if interactively modified
3538 backup = 2 # unconditionally do backup
3541 backup = 2 # unconditionally do backup
3539 check = 1 # check if the existing file differs from target
3542 check = 1 # check if the existing file differs from target
3540 discard = 0 # never do backup
3543 discard = 0 # never do backup
3541 if opts.get('no_backup'):
3544 if opts.get('no_backup'):
3542 backupinteractive = backup = check = discard
3545 backupinteractive = backup = check = discard
3543 if interactive:
3546 if interactive:
3544 dsmodifiedbackup = backupinteractive
3547 dsmodifiedbackup = backupinteractive
3545 else:
3548 else:
3546 dsmodifiedbackup = backup
3549 dsmodifiedbackup = backup
3547 tobackup = set()
3550 tobackup = set()
3548
3551
3549 backupanddel = actions['remove']
3552 backupanddel = actions['remove']
3550 if not opts.get('no_backup'):
3553 if not opts.get('no_backup'):
3551 backupanddel = actions['drop']
3554 backupanddel = actions['drop']
3552
3555
3553 disptable = (
3556 disptable = (
3554 # dispatch table:
3557 # dispatch table:
3555 # file state
3558 # file state
3556 # action
3559 # action
3557 # make backup
3560 # make backup
3558
3561
3559 ## Sets that results that will change file on disk
3562 ## Sets that results that will change file on disk
3560 # Modified compared to target, no local change
3563 # Modified compared to target, no local change
3561 (modified, actions['revert'], discard),
3564 (modified, actions['revert'], discard),
3562 # Modified compared to target, but local file is deleted
3565 # Modified compared to target, but local file is deleted
3563 (deleted, actions['revert'], discard),
3566 (deleted, actions['revert'], discard),
3564 # Modified compared to target, local change
3567 # Modified compared to target, local change
3565 (dsmodified, actions['revert'], dsmodifiedbackup),
3568 (dsmodified, actions['revert'], dsmodifiedbackup),
3566 # Added since target
3569 # Added since target
3567 (added, actions['remove'], discard),
3570 (added, actions['remove'], discard),
3568 # Added in working directory
3571 # Added in working directory
3569 (dsadded, actions['forget'], discard),
3572 (dsadded, actions['forget'], discard),
3570 # Added since target, have local modification
3573 # Added since target, have local modification
3571 (modadded, backupanddel, backup),
3574 (modadded, backupanddel, backup),
3572 # Added since target but file is missing in working directory
3575 # Added since target but file is missing in working directory
3573 (deladded, actions['drop'], discard),
3576 (deladded, actions['drop'], discard),
3574 # Removed since target, before working copy parent
3577 # Removed since target, before working copy parent
3575 (removed, actions['add'], discard),
3578 (removed, actions['add'], discard),
3576 # Same as `removed` but an unknown file exists at the same path
3579 # Same as `removed` but an unknown file exists at the same path
3577 (removunk, actions['add'], check),
3580 (removunk, actions['add'], check),
3578 # Removed since targe, marked as such in working copy parent
3581 # Removed since targe, marked as such in working copy parent
3579 (dsremoved, actions['undelete'], discard),
3582 (dsremoved, actions['undelete'], discard),
3580 # Same as `dsremoved` but an unknown file exists at the same path
3583 # Same as `dsremoved` but an unknown file exists at the same path
3581 (dsremovunk, actions['undelete'], check),
3584 (dsremovunk, actions['undelete'], check),
3582 ## the following sets does not result in any file changes
3585 ## the following sets does not result in any file changes
3583 # File with no modification
3586 # File with no modification
3584 (clean, actions['noop'], discard),
3587 (clean, actions['noop'], discard),
3585 # Existing file, not tracked anywhere
3588 # Existing file, not tracked anywhere
3586 (unknown, actions['unknown'], discard),
3589 (unknown, actions['unknown'], discard),
3587 )
3590 )
3588
3591
3589 for abs, (rel, exact) in sorted(names.items()):
3592 for abs, (rel, exact) in sorted(names.items()):
3590 # target file to be touch on disk (relative to cwd)
3593 # target file to be touch on disk (relative to cwd)
3591 target = repo.wjoin(abs)
3594 target = repo.wjoin(abs)
3592 # search the entry in the dispatch table.
3595 # search the entry in the dispatch table.
3593 # if the file is in any of these sets, it was touched in the working
3596 # if the file is in any of these sets, it was touched in the working
3594 # directory parent and we are sure it needs to be reverted.
3597 # directory parent and we are sure it needs to be reverted.
3595 for table, (xlist, msg), dobackup in disptable:
3598 for table, (xlist, msg), dobackup in disptable:
3596 if abs not in table:
3599 if abs not in table:
3597 continue
3600 continue
3598 if xlist is not None:
3601 if xlist is not None:
3599 xlist.append(abs)
3602 xlist.append(abs)
3600 if dobackup:
3603 if dobackup:
3601 # If in interactive mode, don't automatically create
3604 # If in interactive mode, don't automatically create
3602 # .orig files (issue4793)
3605 # .orig files (issue4793)
3603 if dobackup == backupinteractive:
3606 if dobackup == backupinteractive:
3604 tobackup.add(abs)
3607 tobackup.add(abs)
3605 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3608 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3606 bakname = scmutil.origpath(ui, repo, rel)
3609 bakname = scmutil.origpath(ui, repo, rel)
3607 ui.note(_('saving current version of %s as %s\n') %
3610 ui.note(_('saving current version of %s as %s\n') %
3608 (rel, bakname))
3611 (rel, bakname))
3609 if not opts.get('dry_run'):
3612 if not opts.get('dry_run'):
3610 if interactive:
3613 if interactive:
3611 util.copyfile(target, bakname)
3614 util.copyfile(target, bakname)
3612 else:
3615 else:
3613 util.rename(target, bakname)
3616 util.rename(target, bakname)
3614 if ui.verbose or not exact:
3617 if ui.verbose or not exact:
3615 if not isinstance(msg, basestring):
3618 if not isinstance(msg, basestring):
3616 msg = msg(abs)
3619 msg = msg(abs)
3617 ui.status(msg % rel)
3620 ui.status(msg % rel)
3618 elif exact:
3621 elif exact:
3619 ui.warn(msg % rel)
3622 ui.warn(msg % rel)
3620 break
3623 break
3621
3624
3622 if not opts.get('dry_run'):
3625 if not opts.get('dry_run'):
3623 needdata = ('revert', 'add', 'undelete')
3626 needdata = ('revert', 'add', 'undelete')
3624 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3627 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3625 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3628 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3626
3629
3627 if targetsubs:
3630 if targetsubs:
3628 # Revert the subrepos on the revert list
3631 # Revert the subrepos on the revert list
3629 for sub in targetsubs:
3632 for sub in targetsubs:
3630 try:
3633 try:
3631 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3634 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3632 except KeyError:
3635 except KeyError:
3633 raise error.Abort("subrepository '%s' does not exist in %s!"
3636 raise error.Abort("subrepository '%s' does not exist in %s!"
3634 % (sub, short(ctx.node())))
3637 % (sub, short(ctx.node())))
3635
3638
3636 def _revertprefetch(repo, ctx, *files):
3639 def _revertprefetch(repo, ctx, *files):
3637 """Let extension changing the storage layer prefetch content"""
3640 """Let extension changing the storage layer prefetch content"""
3638 pass
3641 pass
3639
3642
3640 def _performrevert(repo, parents, ctx, actions, interactive=False,
3643 def _performrevert(repo, parents, ctx, actions, interactive=False,
3641 tobackup=None):
3644 tobackup=None):
3642 """function that actually perform all the actions computed for revert
3645 """function that actually perform all the actions computed for revert
3643
3646
3644 This is an independent function to let extension to plug in and react to
3647 This is an independent function to let extension to plug in and react to
3645 the imminent revert.
3648 the imminent revert.
3646
3649
3647 Make sure you have the working directory locked when calling this function.
3650 Make sure you have the working directory locked when calling this function.
3648 """
3651 """
3649 parent, p2 = parents
3652 parent, p2 = parents
3650 node = ctx.node()
3653 node = ctx.node()
3651 excluded_files = []
3654 excluded_files = []
3652 matcher_opts = {"exclude": excluded_files}
3655 matcher_opts = {"exclude": excluded_files}
3653
3656
3654 def checkout(f):
3657 def checkout(f):
3655 fc = ctx[f]
3658 fc = ctx[f]
3656 repo.wwrite(f, fc.data(), fc.flags())
3659 repo.wwrite(f, fc.data(), fc.flags())
3657
3660
3658 def doremove(f):
3661 def doremove(f):
3659 try:
3662 try:
3660 repo.wvfs.unlinkpath(f)
3663 repo.wvfs.unlinkpath(f)
3661 except OSError:
3664 except OSError:
3662 pass
3665 pass
3663 repo.dirstate.remove(f)
3666 repo.dirstate.remove(f)
3664
3667
3665 audit_path = pathutil.pathauditor(repo.root, cached=True)
3668 audit_path = pathutil.pathauditor(repo.root, cached=True)
3666 for f in actions['forget'][0]:
3669 for f in actions['forget'][0]:
3667 if interactive:
3670 if interactive:
3668 choice = repo.ui.promptchoice(
3671 choice = repo.ui.promptchoice(
3669 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3672 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3670 if choice == 0:
3673 if choice == 0:
3671 repo.dirstate.drop(f)
3674 repo.dirstate.drop(f)
3672 else:
3675 else:
3673 excluded_files.append(repo.wjoin(f))
3676 excluded_files.append(repo.wjoin(f))
3674 else:
3677 else:
3675 repo.dirstate.drop(f)
3678 repo.dirstate.drop(f)
3676 for f in actions['remove'][0]:
3679 for f in actions['remove'][0]:
3677 audit_path(f)
3680 audit_path(f)
3678 if interactive:
3681 if interactive:
3679 choice = repo.ui.promptchoice(
3682 choice = repo.ui.promptchoice(
3680 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3683 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3681 if choice == 0:
3684 if choice == 0:
3682 doremove(f)
3685 doremove(f)
3683 else:
3686 else:
3684 excluded_files.append(repo.wjoin(f))
3687 excluded_files.append(repo.wjoin(f))
3685 else:
3688 else:
3686 doremove(f)
3689 doremove(f)
3687 for f in actions['drop'][0]:
3690 for f in actions['drop'][0]:
3688 audit_path(f)
3691 audit_path(f)
3689 repo.dirstate.remove(f)
3692 repo.dirstate.remove(f)
3690
3693
3691 normal = None
3694 normal = None
3692 if node == parent:
3695 if node == parent:
3693 # We're reverting to our parent. If possible, we'd like status
3696 # We're reverting to our parent. If possible, we'd like status
3694 # to report the file as clean. We have to use normallookup for
3697 # to report the file as clean. We have to use normallookup for
3695 # merges to avoid losing information about merged/dirty files.
3698 # merges to avoid losing information about merged/dirty files.
3696 if p2 != nullid:
3699 if p2 != nullid:
3697 normal = repo.dirstate.normallookup
3700 normal = repo.dirstate.normallookup
3698 else:
3701 else:
3699 normal = repo.dirstate.normal
3702 normal = repo.dirstate.normal
3700
3703
3701 newlyaddedandmodifiedfiles = set()
3704 newlyaddedandmodifiedfiles = set()
3702 if interactive:
3705 if interactive:
3703 # Prompt the user for changes to revert
3706 # Prompt the user for changes to revert
3704 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3707 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3705 m = scmutil.match(ctx, torevert, matcher_opts)
3708 m = scmutil.match(ctx, torevert, matcher_opts)
3706 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3709 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3707 diffopts.nodates = True
3710 diffopts.nodates = True
3708 diffopts.git = True
3711 diffopts.git = True
3709 operation = 'discard'
3712 operation = 'discard'
3710 reversehunks = True
3713 reversehunks = True
3711 if node != parent:
3714 if node != parent:
3712 operation = 'revert'
3715 operation = 'revert'
3713 reversehunks = repo.ui.configbool('experimental',
3716 reversehunks = repo.ui.configbool('experimental',
3714 'revertalternateinteractivemode')
3717 'revertalternateinteractivemode')
3715 if reversehunks:
3718 if reversehunks:
3716 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3719 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3717 else:
3720 else:
3718 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3721 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3719 originalchunks = patch.parsepatch(diff)
3722 originalchunks = patch.parsepatch(diff)
3720
3723
3721 try:
3724 try:
3722
3725
3723 chunks, opts = recordfilter(repo.ui, originalchunks,
3726 chunks, opts = recordfilter(repo.ui, originalchunks,
3724 operation=operation)
3727 operation=operation)
3725 if reversehunks:
3728 if reversehunks:
3726 chunks = patch.reversehunks(chunks)
3729 chunks = patch.reversehunks(chunks)
3727
3730
3728 except patch.PatchError as err:
3731 except patch.PatchError as err:
3729 raise error.Abort(_('error parsing patch: %s') % err)
3732 raise error.Abort(_('error parsing patch: %s') % err)
3730
3733
3731 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3734 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3732 if tobackup is None:
3735 if tobackup is None:
3733 tobackup = set()
3736 tobackup = set()
3734 # Apply changes
3737 # Apply changes
3735 fp = stringio()
3738 fp = stringio()
3736 for c in chunks:
3739 for c in chunks:
3737 # Create a backup file only if this hunk should be backed up
3740 # Create a backup file only if this hunk should be backed up
3738 if ishunk(c) and c.header.filename() in tobackup:
3741 if ishunk(c) and c.header.filename() in tobackup:
3739 abs = c.header.filename()
3742 abs = c.header.filename()
3740 target = repo.wjoin(abs)
3743 target = repo.wjoin(abs)
3741 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3744 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3742 util.copyfile(target, bakname)
3745 util.copyfile(target, bakname)
3743 tobackup.remove(abs)
3746 tobackup.remove(abs)
3744 c.write(fp)
3747 c.write(fp)
3745 dopatch = fp.tell()
3748 dopatch = fp.tell()
3746 fp.seek(0)
3749 fp.seek(0)
3747 if dopatch:
3750 if dopatch:
3748 try:
3751 try:
3749 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3752 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3750 except patch.PatchError as err:
3753 except patch.PatchError as err:
3751 raise error.Abort(str(err))
3754 raise error.Abort(str(err))
3752 del fp
3755 del fp
3753 else:
3756 else:
3754 for f in actions['revert'][0]:
3757 for f in actions['revert'][0]:
3755 checkout(f)
3758 checkout(f)
3756 if normal:
3759 if normal:
3757 normal(f)
3760 normal(f)
3758
3761
3759 for f in actions['add'][0]:
3762 for f in actions['add'][0]:
3760 # Don't checkout modified files, they are already created by the diff
3763 # Don't checkout modified files, they are already created by the diff
3761 if f not in newlyaddedandmodifiedfiles:
3764 if f not in newlyaddedandmodifiedfiles:
3762 checkout(f)
3765 checkout(f)
3763 repo.dirstate.add(f)
3766 repo.dirstate.add(f)
3764
3767
3765 normal = repo.dirstate.normallookup
3768 normal = repo.dirstate.normallookup
3766 if node == parent and p2 == nullid:
3769 if node == parent and p2 == nullid:
3767 normal = repo.dirstate.normal
3770 normal = repo.dirstate.normal
3768 for f in actions['undelete'][0]:
3771 for f in actions['undelete'][0]:
3769 checkout(f)
3772 checkout(f)
3770 normal(f)
3773 normal(f)
3771
3774
3772 copied = copies.pathcopies(repo[parent], ctx)
3775 copied = copies.pathcopies(repo[parent], ctx)
3773
3776
3774 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3777 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3775 if f in copied:
3778 if f in copied:
3776 repo.dirstate.copy(copied[f], f)
3779 repo.dirstate.copy(copied[f], f)
3777
3780
3778 class command(registrar.command):
3781 class command(registrar.command):
3779 def _doregister(self, func, name, *args, **kwargs):
3782 def _doregister(self, func, name, *args, **kwargs):
3780 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3783 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3781 return super(command, self)._doregister(func, name, *args, **kwargs)
3784 return super(command, self)._doregister(func, name, *args, **kwargs)
3782
3785
3783 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3786 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3784 # commands.outgoing. "missing" is "missing" of the result of
3787 # commands.outgoing. "missing" is "missing" of the result of
3785 # "findcommonoutgoing()"
3788 # "findcommonoutgoing()"
3786 outgoinghooks = util.hooks()
3789 outgoinghooks = util.hooks()
3787
3790
3788 # a list of (ui, repo) functions called by commands.summary
3791 # a list of (ui, repo) functions called by commands.summary
3789 summaryhooks = util.hooks()
3792 summaryhooks = util.hooks()
3790
3793
3791 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3794 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3792 #
3795 #
3793 # functions should return tuple of booleans below, if 'changes' is None:
3796 # functions should return tuple of booleans below, if 'changes' is None:
3794 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3797 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3795 #
3798 #
3796 # otherwise, 'changes' is a tuple of tuples below:
3799 # otherwise, 'changes' is a tuple of tuples below:
3797 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3800 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3798 # - (desturl, destbranch, destpeer, outgoing)
3801 # - (desturl, destbranch, destpeer, outgoing)
3799 summaryremotehooks = util.hooks()
3802 summaryremotehooks = util.hooks()
3800
3803
3801 # A list of state files kept by multistep operations like graft.
3804 # A list of state files kept by multistep operations like graft.
3802 # Since graft cannot be aborted, it is considered 'clearable' by update.
3805 # Since graft cannot be aborted, it is considered 'clearable' by update.
3803 # note: bisect is intentionally excluded
3806 # note: bisect is intentionally excluded
3804 # (state file, clearable, allowcommit, error, hint)
3807 # (state file, clearable, allowcommit, error, hint)
3805 unfinishedstates = [
3808 unfinishedstates = [
3806 ('graftstate', True, False, _('graft in progress'),
3809 ('graftstate', True, False, _('graft in progress'),
3807 _("use 'hg graft --continue' or 'hg update' to abort")),
3810 _("use 'hg graft --continue' or 'hg update' to abort")),
3808 ('updatestate', True, False, _('last update was interrupted'),
3811 ('updatestate', True, False, _('last update was interrupted'),
3809 _("use 'hg update' to get a consistent checkout"))
3812 _("use 'hg update' to get a consistent checkout"))
3810 ]
3813 ]
3811
3814
3812 def checkunfinished(repo, commit=False):
3815 def checkunfinished(repo, commit=False):
3813 '''Look for an unfinished multistep operation, like graft, and abort
3816 '''Look for an unfinished multistep operation, like graft, and abort
3814 if found. It's probably good to check this right before
3817 if found. It's probably good to check this right before
3815 bailifchanged().
3818 bailifchanged().
3816 '''
3819 '''
3817 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3820 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3818 if commit and allowcommit:
3821 if commit and allowcommit:
3819 continue
3822 continue
3820 if repo.vfs.exists(f):
3823 if repo.vfs.exists(f):
3821 raise error.Abort(msg, hint=hint)
3824 raise error.Abort(msg, hint=hint)
3822
3825
3823 def clearunfinished(repo):
3826 def clearunfinished(repo):
3824 '''Check for unfinished operations (as above), and clear the ones
3827 '''Check for unfinished operations (as above), and clear the ones
3825 that are clearable.
3828 that are clearable.
3826 '''
3829 '''
3827 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3830 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3828 if not clearable and repo.vfs.exists(f):
3831 if not clearable and repo.vfs.exists(f):
3829 raise error.Abort(msg, hint=hint)
3832 raise error.Abort(msg, hint=hint)
3830 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3833 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3831 if clearable and repo.vfs.exists(f):
3834 if clearable and repo.vfs.exists(f):
3832 util.unlink(repo.vfs.join(f))
3835 util.unlink(repo.vfs.join(f))
3833
3836
3834 afterresolvedstates = [
3837 afterresolvedstates = [
3835 ('graftstate',
3838 ('graftstate',
3836 _('hg graft --continue')),
3839 _('hg graft --continue')),
3837 ]
3840 ]
3838
3841
3839 def howtocontinue(repo):
3842 def howtocontinue(repo):
3840 '''Check for an unfinished operation and return the command to finish
3843 '''Check for an unfinished operation and return the command to finish
3841 it.
3844 it.
3842
3845
3843 afterresolvedstates tuples define a .hg/{file} and the corresponding
3846 afterresolvedstates tuples define a .hg/{file} and the corresponding
3844 command needed to finish it.
3847 command needed to finish it.
3845
3848
3846 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3849 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3847 a boolean.
3850 a boolean.
3848 '''
3851 '''
3849 contmsg = _("continue: %s")
3852 contmsg = _("continue: %s")
3850 for f, msg in afterresolvedstates:
3853 for f, msg in afterresolvedstates:
3851 if repo.vfs.exists(f):
3854 if repo.vfs.exists(f):
3852 return contmsg % msg, True
3855 return contmsg % msg, True
3853 if repo[None].dirty(missing=True, merge=False, branch=False):
3856 if repo[None].dirty(missing=True, merge=False, branch=False):
3854 return contmsg % _("hg commit"), False
3857 return contmsg % _("hg commit"), False
3855 return None, None
3858 return None, None
3856
3859
3857 def checkafterresolved(repo):
3860 def checkafterresolved(repo):
3858 '''Inform the user about the next action after completing hg resolve
3861 '''Inform the user about the next action after completing hg resolve
3859
3862
3860 If there's a matching afterresolvedstates, howtocontinue will yield
3863 If there's a matching afterresolvedstates, howtocontinue will yield
3861 repo.ui.warn as the reporter.
3864 repo.ui.warn as the reporter.
3862
3865
3863 Otherwise, it will yield repo.ui.note.
3866 Otherwise, it will yield repo.ui.note.
3864 '''
3867 '''
3865 msg, warning = howtocontinue(repo)
3868 msg, warning = howtocontinue(repo)
3866 if msg is not None:
3869 if msg is not None:
3867 if warning:
3870 if warning:
3868 repo.ui.warn("%s\n" % msg)
3871 repo.ui.warn("%s\n" % msg)
3869 else:
3872 else:
3870 repo.ui.note("%s\n" % msg)
3873 repo.ui.note("%s\n" % msg)
3871
3874
3872 def wrongtooltocontinue(repo, task):
3875 def wrongtooltocontinue(repo, task):
3873 '''Raise an abort suggesting how to properly continue if there is an
3876 '''Raise an abort suggesting how to properly continue if there is an
3874 active task.
3877 active task.
3875
3878
3876 Uses howtocontinue() to find the active task.
3879 Uses howtocontinue() to find the active task.
3877
3880
3878 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3881 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3879 a hint.
3882 a hint.
3880 '''
3883 '''
3881 after = howtocontinue(repo)
3884 after = howtocontinue(repo)
3882 hint = None
3885 hint = None
3883 if after[1]:
3886 if after[1]:
3884 hint = after[0]
3887 hint = after[0]
3885 raise error.Abort(_('no %s in progress') % task, hint=hint)
3888 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,217 +1,225 b''
1 #testcases obsstore-off obsstore-on
1 #testcases obsstore-off obsstore-on
2
2
3 $ cat << EOF >> $HGRCPATH
3 $ cat << EOF >> $HGRCPATH
4 > [extensions]
4 > [extensions]
5 > amend=
5 > amend=
6 > debugdrawdag=$TESTDIR/drawdag.py
6 > debugdrawdag=$TESTDIR/drawdag.py
7 > [diff]
7 > [diff]
8 > git=1
8 > git=1
9 > EOF
9 > EOF
10
10
11 #if obsstore-on
11 #if obsstore-on
12 $ cat << EOF >> $HGRCPATH
12 $ cat << EOF >> $HGRCPATH
13 > [experimental]
13 > [experimental]
14 > stabilization=createmarkers
14 > stabilization=createmarkers
15 > EOF
15 > EOF
16 #endif
16 #endif
17
17
18 Basic amend
18 Basic amend
19
19
20 $ hg init repo1
20 $ hg init repo1
21 $ cd repo1
21 $ cd repo1
22 $ hg debugdrawdag <<'EOS'
22 $ hg debugdrawdag <<'EOS'
23 > B
23 > B
24 > |
24 > |
25 > A
25 > A
26 > EOS
26 > EOS
27
27
28 $ hg update B -q
28 $ hg update B -q
29 $ echo 2 >> B
29 $ echo 2 >> B
30
30
31 $ hg amend
31 $ hg amend
32 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-7e959a55-amend.hg (glob) (obsstore-off !)
32 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-7e959a55-amend.hg (glob) (obsstore-off !)
33 #if obsstore-off
33 #if obsstore-off
34 $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
34 $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
35 @ 1 be169c7e8dbe B
35 @ 1 be169c7e8dbe B
36 | diff --git a/B b/B
36 | diff --git a/B b/B
37 | new file mode 100644
37 | new file mode 100644
38 | --- /dev/null
38 | --- /dev/null
39 | +++ b/B
39 | +++ b/B
40 | @@ -0,0 +1,1 @@
40 | @@ -0,0 +1,1 @@
41 | +B2
41 | +B2
42 |
42 |
43 o 0 426bada5c675 A
43 o 0 426bada5c675 A
44 diff --git a/A b/A
44 diff --git a/A b/A
45 new file mode 100644
45 new file mode 100644
46 --- /dev/null
46 --- /dev/null
47 +++ b/A
47 +++ b/A
48 @@ -0,0 +1,1 @@
48 @@ -0,0 +1,1 @@
49 +A
49 +A
50 \ No newline at end of file
50 \ No newline at end of file
51
51
52 #else
52 #else
53 $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
53 $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
54 @ 2 be169c7e8dbe B
54 @ 2 be169c7e8dbe B
55 | diff --git a/B b/B
55 | diff --git a/B b/B
56 | new file mode 100644
56 | new file mode 100644
57 | --- /dev/null
57 | --- /dev/null
58 | +++ b/B
58 | +++ b/B
59 | @@ -0,0 +1,1 @@
59 | @@ -0,0 +1,1 @@
60 | +B2
60 | +B2
61 |
61 |
62 | x 1 112478962961 B
62 | x 1 112478962961 B
63 |/ diff --git a/B b/B
63 |/ diff --git a/B b/B
64 | new file mode 100644
64 | new file mode 100644
65 | --- /dev/null
65 | --- /dev/null
66 | +++ b/B
66 | +++ b/B
67 | @@ -0,0 +1,1 @@
67 | @@ -0,0 +1,1 @@
68 | +B
68 | +B
69 | \ No newline at end of file
69 | \ No newline at end of file
70 |
70 |
71 o 0 426bada5c675 A
71 o 0 426bada5c675 A
72 diff --git a/A b/A
72 diff --git a/A b/A
73 new file mode 100644
73 new file mode 100644
74 --- /dev/null
74 --- /dev/null
75 +++ b/A
75 +++ b/A
76 @@ -0,0 +1,1 @@
76 @@ -0,0 +1,1 @@
77 +A
77 +A
78 \ No newline at end of file
78 \ No newline at end of file
79
79
80 #endif
80 #endif
81
81
82 Nothing changed
82 Nothing changed
83
83
84 $ hg amend
84 $ hg amend
85 nothing changed
85 nothing changed
86 [1]
86 [1]
87
87
88 $ hg amend -d "0 0"
89 nothing changed
90 [1]
91
92 $ hg amend -d "Thu Jan 01 00:00:00 1970 UTC"
93 nothing changed
94 [1]
95
88 Matcher and metadata options
96 Matcher and metadata options
89
97
90 $ echo 3 > C
98 $ echo 3 > C
91 $ echo 4 > D
99 $ echo 4 > D
92 $ hg add C D
100 $ hg add C D
93 $ hg amend -m NEWMESSAGE -I C
101 $ hg amend -m NEWMESSAGE -I C
94 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/be169c7e8dbe-7684ddc5-amend.hg (glob) (obsstore-off !)
102 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/be169c7e8dbe-7684ddc5-amend.hg (glob) (obsstore-off !)
95 $ hg log -r . -T '{node|short} {desc} {files}\n'
103 $ hg log -r . -T '{node|short} {desc} {files}\n'
96 c7ba14d9075b NEWMESSAGE B C
104 c7ba14d9075b NEWMESSAGE B C
97 $ echo 5 > E
105 $ echo 5 > E
98 $ rm C
106 $ rm C
99 $ hg amend -d '2000 1000' -u 'Foo <foo@example.com>' -A C D
107 $ hg amend -d '2000 1000' -u 'Foo <foo@example.com>' -A C D
100 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/c7ba14d9075b-b3e76daa-amend.hg (glob) (obsstore-off !)
108 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/c7ba14d9075b-b3e76daa-amend.hg (glob) (obsstore-off !)
101 $ hg log -r . -T '{node|short} {desc} {files} {author} {date}\n'
109 $ hg log -r . -T '{node|short} {desc} {files} {author} {date}\n'
102 14f6c4bcc865 NEWMESSAGE B D Foo <foo@example.com> 2000.01000
110 14f6c4bcc865 NEWMESSAGE B D Foo <foo@example.com> 2000.01000
103
111
104 Amend with editor
112 Amend with editor
105
113
106 $ cat > $TESTTMP/prefix.sh <<'EOF'
114 $ cat > $TESTTMP/prefix.sh <<'EOF'
107 > printf 'EDITED: ' > $TESTTMP/msg
115 > printf 'EDITED: ' > $TESTTMP/msg
108 > cat "$1" >> $TESTTMP/msg
116 > cat "$1" >> $TESTTMP/msg
109 > mv $TESTTMP/msg "$1"
117 > mv $TESTTMP/msg "$1"
110 > EOF
118 > EOF
111 $ chmod +x $TESTTMP/prefix.sh
119 $ chmod +x $TESTTMP/prefix.sh
112
120
113 $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend --edit
121 $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend --edit
114 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/14f6c4bcc865-6591f15d-amend.hg (glob) (obsstore-off !)
122 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/14f6c4bcc865-6591f15d-amend.hg (glob) (obsstore-off !)
115 $ hg log -r . -T '{node|short} {desc}\n'
123 $ hg log -r . -T '{node|short} {desc}\n'
116 298f085230c3 EDITED: NEWMESSAGE
124 298f085230c3 EDITED: NEWMESSAGE
117 $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend -e -m MSG
125 $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend -e -m MSG
118 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/298f085230c3-d81a6ad3-amend.hg (glob) (obsstore-off !)
126 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/298f085230c3-d81a6ad3-amend.hg (glob) (obsstore-off !)
119 $ hg log -r . -T '{node|short} {desc}\n'
127 $ hg log -r . -T '{node|short} {desc}\n'
120 974f07f28537 EDITED: MSG
128 974f07f28537 EDITED: MSG
121
129
122 $ echo FOO > $TESTTMP/msg
130 $ echo FOO > $TESTTMP/msg
123 $ hg amend -l $TESTTMP/msg -m BAR
131 $ hg amend -l $TESTTMP/msg -m BAR
124 abort: options --message and --logfile are mutually exclusive
132 abort: options --message and --logfile are mutually exclusive
125 [255]
133 [255]
126 $ hg amend -l $TESTTMP/msg
134 $ hg amend -l $TESTTMP/msg
127 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/974f07f28537-edb6470a-amend.hg (glob) (obsstore-off !)
135 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/974f07f28537-edb6470a-amend.hg (glob) (obsstore-off !)
128 $ hg log -r . -T '{node|short} {desc}\n'
136 $ hg log -r . -T '{node|short} {desc}\n'
129 507be9bdac71 FOO
137 507be9bdac71 FOO
130
138
131 Interactive mode
139 Interactive mode
132
140
133 $ touch F G
141 $ touch F G
134 $ hg add F G
142 $ hg add F G
135 $ cat <<EOS | hg amend -i --config ui.interactive=1
143 $ cat <<EOS | hg amend -i --config ui.interactive=1
136 > y
144 > y
137 > n
145 > n
138 > EOS
146 > EOS
139 diff --git a/F b/F
147 diff --git a/F b/F
140 new file mode 100644
148 new file mode 100644
141 examine changes to 'F'? [Ynesfdaq?] y
149 examine changes to 'F'? [Ynesfdaq?] y
142
150
143 diff --git a/G b/G
151 diff --git a/G b/G
144 new file mode 100644
152 new file mode 100644
145 examine changes to 'G'? [Ynesfdaq?] n
153 examine changes to 'G'? [Ynesfdaq?] n
146
154
147 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/507be9bdac71-c8077452-amend.hg (glob) (obsstore-off !)
155 saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/507be9bdac71-c8077452-amend.hg (glob) (obsstore-off !)
148 $ hg log -r . -T '{files}\n'
156 $ hg log -r . -T '{files}\n'
149 B D F
157 B D F
150
158
151 Amend in the middle of a stack
159 Amend in the middle of a stack
152
160
153 $ hg init $TESTTMP/repo2
161 $ hg init $TESTTMP/repo2
154 $ cd $TESTTMP/repo2
162 $ cd $TESTTMP/repo2
155 $ hg debugdrawdag <<'EOS'
163 $ hg debugdrawdag <<'EOS'
156 > C
164 > C
157 > |
165 > |
158 > B
166 > B
159 > |
167 > |
160 > A
168 > A
161 > EOS
169 > EOS
162
170
163 $ hg update -q B
171 $ hg update -q B
164 $ echo 2 >> B
172 $ echo 2 >> B
165 $ hg amend
173 $ hg amend
166 abort: cannot amend changeset with children
174 abort: cannot amend changeset with children
167 [255]
175 [255]
168
176
169 #if obsstore-on
177 #if obsstore-on
170
178
171 With allowunstable, amend could work in the middle of a stack
179 With allowunstable, amend could work in the middle of a stack
172
180
173 $ cat >> $HGRCPATH <<EOF
181 $ cat >> $HGRCPATH <<EOF
174 > [experimental]
182 > [experimental]
175 > stabilization=createmarkers, allowunstable
183 > stabilization=createmarkers, allowunstable
176 > EOF
184 > EOF
177
185
178 $ hg amend
186 $ hg amend
179 $ hg log -T '{rev} {node|short} {desc}\n' -G
187 $ hg log -T '{rev} {node|short} {desc}\n' -G
180 @ 3 be169c7e8dbe B
188 @ 3 be169c7e8dbe B
181 |
189 |
182 | o 2 26805aba1e60 C
190 | o 2 26805aba1e60 C
183 | |
191 | |
184 | x 1 112478962961 B
192 | x 1 112478962961 B
185 |/
193 |/
186 o 0 426bada5c675 A
194 o 0 426bada5c675 A
187
195
188 #endif
196 #endif
189
197
190 Cannot amend public changeset
198 Cannot amend public changeset
191
199
192 $ hg phase -r A --public
200 $ hg phase -r A --public
193 $ hg update -C -q A
201 $ hg update -C -q A
194 $ hg amend -m AMEND
202 $ hg amend -m AMEND
195 abort: cannot amend public changesets
203 abort: cannot amend public changesets
196 [255]
204 [255]
197
205
198 Amend a merge changeset
206 Amend a merge changeset
199
207
200 $ hg init $TESTTMP/repo3
208 $ hg init $TESTTMP/repo3
201 $ cd $TESTTMP/repo3
209 $ cd $TESTTMP/repo3
202 $ hg debugdrawdag <<'EOS'
210 $ hg debugdrawdag <<'EOS'
203 > C
211 > C
204 > /|
212 > /|
205 > A B
213 > A B
206 > EOS
214 > EOS
207 $ hg update -q C
215 $ hg update -q C
208 $ hg amend -m FOO
216 $ hg amend -m FOO
209 saved backup bundle to $TESTTMP/repo3/.hg/strip-backup/a35c07e8a2a4-15ff4612-amend.hg (glob) (obsstore-off !)
217 saved backup bundle to $TESTTMP/repo3/.hg/strip-backup/a35c07e8a2a4-15ff4612-amend.hg (glob) (obsstore-off !)
210 $ rm .hg/localtags
218 $ rm .hg/localtags
211 $ hg log -G -T '{desc}\n'
219 $ hg log -G -T '{desc}\n'
212 @ FOO
220 @ FOO
213 |\
221 |\
214 | o B
222 | o B
215 |
223 |
216 o A
224 o A
217
225
General Comments 0
You need to be logged in to leave comments. Login now