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