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