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