##// END OF EJS Templates
commit: select template by spec.ref name...
Yuya Nishihara -
r32878:a3a36bcf default
parent child Browse files
Show More
@@ -1,3594 +1,3591 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 list(choice.values())[0]
455 return list(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, tmplspec, matchfn, diffopts, buffered):
1581 def __init__(self, ui, repo, tmplspec, matchfn, diffopts, buffered):
1582 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1582 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1583 self.t = formatter.loadtemplater(ui, tmplspec,
1583 self.t = formatter.loadtemplater(ui, tmplspec,
1584 cache=templatekw.defaulttempl)
1584 cache=templatekw.defaulttempl)
1585 self._counter = itertools.count()
1585 self._counter = itertools.count()
1586 self.cache = {}
1586 self.cache = {}
1587
1587
1588 # find correct templates for current mode
1588 # find correct templates for current mode
1589 tmplmodes = [
1589 tmplmodes = [
1590 (True, None),
1590 (True, None),
1591 (self.ui.verbose, 'verbose'),
1591 (self.ui.verbose, 'verbose'),
1592 (self.ui.quiet, 'quiet'),
1592 (self.ui.quiet, 'quiet'),
1593 (self.ui.debugflag, 'debug'),
1593 (self.ui.debugflag, 'debug'),
1594 ]
1594 ]
1595
1595
1596 self._tref = tmplspec.ref
1596 self._tref = tmplspec.ref
1597 self._parts = {'header': '', 'footer': '',
1597 self._parts = {'header': '', 'footer': '',
1598 tmplspec.ref: tmplspec.ref,
1598 tmplspec.ref: tmplspec.ref,
1599 'docheader': '', 'docfooter': ''}
1599 'docheader': '', 'docfooter': ''}
1600 for mode, postfix in tmplmodes:
1600 for mode, postfix in tmplmodes:
1601 for t in self._parts:
1601 for t in self._parts:
1602 cur = t
1602 cur = t
1603 if postfix:
1603 if postfix:
1604 cur += "_" + postfix
1604 cur += "_" + postfix
1605 if mode and cur in self.t:
1605 if mode and cur in self.t:
1606 self._parts[t] = cur
1606 self._parts[t] = cur
1607
1607
1608 if self._parts['docheader']:
1608 if self._parts['docheader']:
1609 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1609 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1610
1610
1611 def close(self):
1611 def close(self):
1612 if self._parts['docfooter']:
1612 if self._parts['docfooter']:
1613 if not self.footer:
1613 if not self.footer:
1614 self.footer = ""
1614 self.footer = ""
1615 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1615 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1616 return super(changeset_templater, self).close()
1616 return super(changeset_templater, self).close()
1617
1617
1618 def _show(self, ctx, copies, matchfn, props):
1618 def _show(self, ctx, copies, matchfn, props):
1619 '''show a single changeset or file revision'''
1619 '''show a single changeset or file revision'''
1620 props = props.copy()
1620 props = props.copy()
1621 props.update(templatekw.keywords)
1621 props.update(templatekw.keywords)
1622 props['templ'] = self.t
1622 props['templ'] = self.t
1623 props['ctx'] = ctx
1623 props['ctx'] = ctx
1624 props['repo'] = self.repo
1624 props['repo'] = self.repo
1625 props['ui'] = self.repo.ui
1625 props['ui'] = self.repo.ui
1626 props['index'] = next(self._counter)
1626 props['index'] = next(self._counter)
1627 props['revcache'] = {'copies': copies}
1627 props['revcache'] = {'copies': copies}
1628 props['cache'] = self.cache
1628 props['cache'] = self.cache
1629 props = pycompat.strkwargs(props)
1629 props = pycompat.strkwargs(props)
1630
1630
1631 # write header
1631 # write header
1632 if self._parts['header']:
1632 if self._parts['header']:
1633 h = templater.stringify(self.t(self._parts['header'], **props))
1633 h = templater.stringify(self.t(self._parts['header'], **props))
1634 if self.buffered:
1634 if self.buffered:
1635 self.header[ctx.rev()] = h
1635 self.header[ctx.rev()] = h
1636 else:
1636 else:
1637 if self.lastheader != h:
1637 if self.lastheader != h:
1638 self.lastheader = h
1638 self.lastheader = h
1639 self.ui.write(h)
1639 self.ui.write(h)
1640
1640
1641 # write changeset metadata, then patch if requested
1641 # write changeset metadata, then patch if requested
1642 key = self._parts[self._tref]
1642 key = self._parts[self._tref]
1643 self.ui.write(templater.stringify(self.t(key, **props)))
1643 self.ui.write(templater.stringify(self.t(key, **props)))
1644 self.showpatch(ctx, matchfn)
1644 self.showpatch(ctx, matchfn)
1645
1645
1646 if self._parts['footer']:
1646 if self._parts['footer']:
1647 if not self.footer:
1647 if not self.footer:
1648 self.footer = templater.stringify(
1648 self.footer = templater.stringify(
1649 self.t(self._parts['footer'], **props))
1649 self.t(self._parts['footer'], **props))
1650
1650
1651 def logtemplatespec(tmpl, mapfile):
1651 def logtemplatespec(tmpl, mapfile):
1652 if mapfile:
1652 if mapfile:
1653 return formatter.templatespec('changeset', tmpl, mapfile)
1653 return formatter.templatespec('changeset', tmpl, mapfile)
1654 else:
1654 else:
1655 return formatter.templatespec('', tmpl, None)
1655 return formatter.templatespec('', tmpl, None)
1656
1656
1657 def _lookuplogtemplate(ui, tmpl, style):
1657 def _lookuplogtemplate(ui, tmpl, style):
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 See formatter.lookuptemplate() for details.
1660 See formatter.lookuptemplate() for details.
1661 """
1661 """
1662
1662
1663 # ui settings
1663 # ui settings
1664 if not tmpl and not style: # template are stronger than style
1664 if not tmpl and not style: # template are stronger than style
1665 tmpl = ui.config('ui', 'logtemplate')
1665 tmpl = ui.config('ui', 'logtemplate')
1666 if tmpl:
1666 if tmpl:
1667 return logtemplatespec(templater.unquotestring(tmpl), None)
1667 return logtemplatespec(templater.unquotestring(tmpl), None)
1668 else:
1668 else:
1669 style = util.expandpath(ui.config('ui', 'style', ''))
1669 style = util.expandpath(ui.config('ui', 'style', ''))
1670
1670
1671 if not tmpl and style:
1671 if not tmpl and style:
1672 mapfile = style
1672 mapfile = style
1673 if not os.path.split(mapfile)[0]:
1673 if not os.path.split(mapfile)[0]:
1674 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1674 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1675 or templater.templatepath(mapfile))
1675 or templater.templatepath(mapfile))
1676 if mapname:
1676 if mapname:
1677 mapfile = mapname
1677 mapfile = mapname
1678 return logtemplatespec(None, mapfile)
1678 return logtemplatespec(None, mapfile)
1679
1679
1680 if not tmpl:
1680 if not tmpl:
1681 return logtemplatespec(None, None)
1681 return logtemplatespec(None, None)
1682
1682
1683 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1683 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1684
1684
1685 def makelogtemplater(ui, repo, tmpl, buffered=False):
1685 def makelogtemplater(ui, repo, tmpl, buffered=False):
1686 """Create a changeset_templater from a literal template 'tmpl'"""
1686 """Create a changeset_templater from a literal template 'tmpl'"""
1687 spec = logtemplatespec(tmpl, None)
1687 spec = logtemplatespec(tmpl, None)
1688 return changeset_templater(ui, repo, spec, matchfn=None, diffopts={},
1688 return changeset_templater(ui, repo, spec, matchfn=None, diffopts={},
1689 buffered=buffered)
1689 buffered=buffered)
1690
1690
1691 def show_changeset(ui, repo, opts, buffered=False):
1691 def show_changeset(ui, repo, opts, buffered=False):
1692 """show one changeset using template or regular display.
1692 """show one changeset using template or regular display.
1693
1693
1694 Display format will be the first non-empty hit of:
1694 Display format will be the first non-empty hit of:
1695 1. option 'template'
1695 1. option 'template'
1696 2. option 'style'
1696 2. option 'style'
1697 3. [ui] setting 'logtemplate'
1697 3. [ui] setting 'logtemplate'
1698 4. [ui] setting 'style'
1698 4. [ui] setting 'style'
1699 If all of these values are either the unset or the empty string,
1699 If all of these values are either the unset or the empty string,
1700 regular display via changeset_printer() is done.
1700 regular display via changeset_printer() is done.
1701 """
1701 """
1702 # options
1702 # options
1703 matchfn = None
1703 matchfn = None
1704 if opts.get('patch') or opts.get('stat'):
1704 if opts.get('patch') or opts.get('stat'):
1705 matchfn = scmutil.matchall(repo)
1705 matchfn = scmutil.matchall(repo)
1706
1706
1707 if opts.get('template') == 'json':
1707 if opts.get('template') == 'json':
1708 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1708 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1709
1709
1710 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
1710 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
1711
1711
1712 if not spec.ref and not spec.tmpl and not spec.mapfile:
1712 if not spec.ref and not spec.tmpl and not spec.mapfile:
1713 return changeset_printer(ui, repo, matchfn, opts, buffered)
1713 return changeset_printer(ui, repo, matchfn, opts, buffered)
1714
1714
1715 return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
1715 return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
1716
1716
1717 def showmarker(fm, marker, index=None):
1717 def showmarker(fm, marker, index=None):
1718 """utility function to display obsolescence marker in a readable way
1718 """utility function to display obsolescence marker in a readable way
1719
1719
1720 To be used by debug function."""
1720 To be used by debug function."""
1721 if index is not None:
1721 if index is not None:
1722 fm.write('index', '%i ', index)
1722 fm.write('index', '%i ', index)
1723 fm.write('precnode', '%s ', hex(marker.precnode()))
1723 fm.write('precnode', '%s ', hex(marker.precnode()))
1724 succs = marker.succnodes()
1724 succs = marker.succnodes()
1725 fm.condwrite(succs, 'succnodes', '%s ',
1725 fm.condwrite(succs, 'succnodes', '%s ',
1726 fm.formatlist(map(hex, succs), name='node'))
1726 fm.formatlist(map(hex, succs), name='node'))
1727 fm.write('flag', '%X ', marker.flags())
1727 fm.write('flag', '%X ', marker.flags())
1728 parents = marker.parentnodes()
1728 parents = marker.parentnodes()
1729 if parents is not None:
1729 if parents is not None:
1730 fm.write('parentnodes', '{%s} ',
1730 fm.write('parentnodes', '{%s} ',
1731 fm.formatlist(map(hex, parents), name='node', sep=', '))
1731 fm.formatlist(map(hex, parents), name='node', sep=', '))
1732 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1732 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1733 meta = marker.metadata().copy()
1733 meta = marker.metadata().copy()
1734 meta.pop('date', None)
1734 meta.pop('date', None)
1735 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1735 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1736 fm.plain('\n')
1736 fm.plain('\n')
1737
1737
1738 def finddate(ui, repo, date):
1738 def finddate(ui, repo, date):
1739 """Find the tipmost changeset that matches the given date spec"""
1739 """Find the tipmost changeset that matches the given date spec"""
1740
1740
1741 df = util.matchdate(date)
1741 df = util.matchdate(date)
1742 m = scmutil.matchall(repo)
1742 m = scmutil.matchall(repo)
1743 results = {}
1743 results = {}
1744
1744
1745 def prep(ctx, fns):
1745 def prep(ctx, fns):
1746 d = ctx.date()
1746 d = ctx.date()
1747 if df(d[0]):
1747 if df(d[0]):
1748 results[ctx.rev()] = d
1748 results[ctx.rev()] = d
1749
1749
1750 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1750 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1751 rev = ctx.rev()
1751 rev = ctx.rev()
1752 if rev in results:
1752 if rev in results:
1753 ui.status(_("found revision %s from %s\n") %
1753 ui.status(_("found revision %s from %s\n") %
1754 (rev, util.datestr(results[rev])))
1754 (rev, util.datestr(results[rev])))
1755 return '%d' % rev
1755 return '%d' % rev
1756
1756
1757 raise error.Abort(_("revision matching date not found"))
1757 raise error.Abort(_("revision matching date not found"))
1758
1758
1759 def increasingwindows(windowsize=8, sizelimit=512):
1759 def increasingwindows(windowsize=8, sizelimit=512):
1760 while True:
1760 while True:
1761 yield windowsize
1761 yield windowsize
1762 if windowsize < sizelimit:
1762 if windowsize < sizelimit:
1763 windowsize *= 2
1763 windowsize *= 2
1764
1764
1765 class FileWalkError(Exception):
1765 class FileWalkError(Exception):
1766 pass
1766 pass
1767
1767
1768 def walkfilerevs(repo, match, follow, revs, fncache):
1768 def walkfilerevs(repo, match, follow, revs, fncache):
1769 '''Walks the file history for the matched files.
1769 '''Walks the file history for the matched files.
1770
1770
1771 Returns the changeset revs that are involved in the file history.
1771 Returns the changeset revs that are involved in the file history.
1772
1772
1773 Throws FileWalkError if the file history can't be walked using
1773 Throws FileWalkError if the file history can't be walked using
1774 filelogs alone.
1774 filelogs alone.
1775 '''
1775 '''
1776 wanted = set()
1776 wanted = set()
1777 copies = []
1777 copies = []
1778 minrev, maxrev = min(revs), max(revs)
1778 minrev, maxrev = min(revs), max(revs)
1779 def filerevgen(filelog, last):
1779 def filerevgen(filelog, last):
1780 """
1780 """
1781 Only files, no patterns. Check the history of each file.
1781 Only files, no patterns. Check the history of each file.
1782
1782
1783 Examines filelog entries within minrev, maxrev linkrev range
1783 Examines filelog entries within minrev, maxrev linkrev range
1784 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1784 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1785 tuples in backwards order
1785 tuples in backwards order
1786 """
1786 """
1787 cl_count = len(repo)
1787 cl_count = len(repo)
1788 revs = []
1788 revs = []
1789 for j in xrange(0, last + 1):
1789 for j in xrange(0, last + 1):
1790 linkrev = filelog.linkrev(j)
1790 linkrev = filelog.linkrev(j)
1791 if linkrev < minrev:
1791 if linkrev < minrev:
1792 continue
1792 continue
1793 # only yield rev for which we have the changelog, it can
1793 # only yield rev for which we have the changelog, it can
1794 # happen while doing "hg log" during a pull or commit
1794 # happen while doing "hg log" during a pull or commit
1795 if linkrev >= cl_count:
1795 if linkrev >= cl_count:
1796 break
1796 break
1797
1797
1798 parentlinkrevs = []
1798 parentlinkrevs = []
1799 for p in filelog.parentrevs(j):
1799 for p in filelog.parentrevs(j):
1800 if p != nullrev:
1800 if p != nullrev:
1801 parentlinkrevs.append(filelog.linkrev(p))
1801 parentlinkrevs.append(filelog.linkrev(p))
1802 n = filelog.node(j)
1802 n = filelog.node(j)
1803 revs.append((linkrev, parentlinkrevs,
1803 revs.append((linkrev, parentlinkrevs,
1804 follow and filelog.renamed(n)))
1804 follow and filelog.renamed(n)))
1805
1805
1806 return reversed(revs)
1806 return reversed(revs)
1807 def iterfiles():
1807 def iterfiles():
1808 pctx = repo['.']
1808 pctx = repo['.']
1809 for filename in match.files():
1809 for filename in match.files():
1810 if follow:
1810 if follow:
1811 if filename not in pctx:
1811 if filename not in pctx:
1812 raise error.Abort(_('cannot follow file not in parent '
1812 raise error.Abort(_('cannot follow file not in parent '
1813 'revision: "%s"') % filename)
1813 'revision: "%s"') % filename)
1814 yield filename, pctx[filename].filenode()
1814 yield filename, pctx[filename].filenode()
1815 else:
1815 else:
1816 yield filename, None
1816 yield filename, None
1817 for filename_node in copies:
1817 for filename_node in copies:
1818 yield filename_node
1818 yield filename_node
1819
1819
1820 for file_, node in iterfiles():
1820 for file_, node in iterfiles():
1821 filelog = repo.file(file_)
1821 filelog = repo.file(file_)
1822 if not len(filelog):
1822 if not len(filelog):
1823 if node is None:
1823 if node is None:
1824 # A zero count may be a directory or deleted file, so
1824 # A zero count may be a directory or deleted file, so
1825 # try to find matching entries on the slow path.
1825 # try to find matching entries on the slow path.
1826 if follow:
1826 if follow:
1827 raise error.Abort(
1827 raise error.Abort(
1828 _('cannot follow nonexistent file: "%s"') % file_)
1828 _('cannot follow nonexistent file: "%s"') % file_)
1829 raise FileWalkError("Cannot walk via filelog")
1829 raise FileWalkError("Cannot walk via filelog")
1830 else:
1830 else:
1831 continue
1831 continue
1832
1832
1833 if node is None:
1833 if node is None:
1834 last = len(filelog) - 1
1834 last = len(filelog) - 1
1835 else:
1835 else:
1836 last = filelog.rev(node)
1836 last = filelog.rev(node)
1837
1837
1838 # keep track of all ancestors of the file
1838 # keep track of all ancestors of the file
1839 ancestors = {filelog.linkrev(last)}
1839 ancestors = {filelog.linkrev(last)}
1840
1840
1841 # iterate from latest to oldest revision
1841 # iterate from latest to oldest revision
1842 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1842 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1843 if not follow:
1843 if not follow:
1844 if rev > maxrev:
1844 if rev > maxrev:
1845 continue
1845 continue
1846 else:
1846 else:
1847 # Note that last might not be the first interesting
1847 # Note that last might not be the first interesting
1848 # rev to us:
1848 # rev to us:
1849 # if the file has been changed after maxrev, we'll
1849 # if the file has been changed after maxrev, we'll
1850 # have linkrev(last) > maxrev, and we still need
1850 # have linkrev(last) > maxrev, and we still need
1851 # to explore the file graph
1851 # to explore the file graph
1852 if rev not in ancestors:
1852 if rev not in ancestors:
1853 continue
1853 continue
1854 # XXX insert 1327 fix here
1854 # XXX insert 1327 fix here
1855 if flparentlinkrevs:
1855 if flparentlinkrevs:
1856 ancestors.update(flparentlinkrevs)
1856 ancestors.update(flparentlinkrevs)
1857
1857
1858 fncache.setdefault(rev, []).append(file_)
1858 fncache.setdefault(rev, []).append(file_)
1859 wanted.add(rev)
1859 wanted.add(rev)
1860 if copied:
1860 if copied:
1861 copies.append(copied)
1861 copies.append(copied)
1862
1862
1863 return wanted
1863 return wanted
1864
1864
1865 class _followfilter(object):
1865 class _followfilter(object):
1866 def __init__(self, repo, onlyfirst=False):
1866 def __init__(self, repo, onlyfirst=False):
1867 self.repo = repo
1867 self.repo = repo
1868 self.startrev = nullrev
1868 self.startrev = nullrev
1869 self.roots = set()
1869 self.roots = set()
1870 self.onlyfirst = onlyfirst
1870 self.onlyfirst = onlyfirst
1871
1871
1872 def match(self, rev):
1872 def match(self, rev):
1873 def realparents(rev):
1873 def realparents(rev):
1874 if self.onlyfirst:
1874 if self.onlyfirst:
1875 return self.repo.changelog.parentrevs(rev)[0:1]
1875 return self.repo.changelog.parentrevs(rev)[0:1]
1876 else:
1876 else:
1877 return filter(lambda x: x != nullrev,
1877 return filter(lambda x: x != nullrev,
1878 self.repo.changelog.parentrevs(rev))
1878 self.repo.changelog.parentrevs(rev))
1879
1879
1880 if self.startrev == nullrev:
1880 if self.startrev == nullrev:
1881 self.startrev = rev
1881 self.startrev = rev
1882 return True
1882 return True
1883
1883
1884 if rev > self.startrev:
1884 if rev > self.startrev:
1885 # forward: all descendants
1885 # forward: all descendants
1886 if not self.roots:
1886 if not self.roots:
1887 self.roots.add(self.startrev)
1887 self.roots.add(self.startrev)
1888 for parent in realparents(rev):
1888 for parent in realparents(rev):
1889 if parent in self.roots:
1889 if parent in self.roots:
1890 self.roots.add(rev)
1890 self.roots.add(rev)
1891 return True
1891 return True
1892 else:
1892 else:
1893 # backwards: all parents
1893 # backwards: all parents
1894 if not self.roots:
1894 if not self.roots:
1895 self.roots.update(realparents(self.startrev))
1895 self.roots.update(realparents(self.startrev))
1896 if rev in self.roots:
1896 if rev in self.roots:
1897 self.roots.remove(rev)
1897 self.roots.remove(rev)
1898 self.roots.update(realparents(rev))
1898 self.roots.update(realparents(rev))
1899 return True
1899 return True
1900
1900
1901 return False
1901 return False
1902
1902
1903 def walkchangerevs(repo, match, opts, prepare):
1903 def walkchangerevs(repo, match, opts, prepare):
1904 '''Iterate over files and the revs in which they changed.
1904 '''Iterate over files and the revs in which they changed.
1905
1905
1906 Callers most commonly need to iterate backwards over the history
1906 Callers most commonly need to iterate backwards over the history
1907 in which they are interested. Doing so has awful (quadratic-looking)
1907 in which they are interested. Doing so has awful (quadratic-looking)
1908 performance, so we use iterators in a "windowed" way.
1908 performance, so we use iterators in a "windowed" way.
1909
1909
1910 We walk a window of revisions in the desired order. Within the
1910 We walk a window of revisions in the desired order. Within the
1911 window, we first walk forwards to gather data, then in the desired
1911 window, we first walk forwards to gather data, then in the desired
1912 order (usually backwards) to display it.
1912 order (usually backwards) to display it.
1913
1913
1914 This function returns an iterator yielding contexts. Before
1914 This function returns an iterator yielding contexts. Before
1915 yielding each context, the iterator will first call the prepare
1915 yielding each context, the iterator will first call the prepare
1916 function on each context in the window in forward order.'''
1916 function on each context in the window in forward order.'''
1917
1917
1918 follow = opts.get('follow') or opts.get('follow_first')
1918 follow = opts.get('follow') or opts.get('follow_first')
1919 revs = _logrevs(repo, opts)
1919 revs = _logrevs(repo, opts)
1920 if not revs:
1920 if not revs:
1921 return []
1921 return []
1922 wanted = set()
1922 wanted = set()
1923 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1923 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1924 opts.get('removed'))
1924 opts.get('removed'))
1925 fncache = {}
1925 fncache = {}
1926 change = repo.changectx
1926 change = repo.changectx
1927
1927
1928 # First step is to fill wanted, the set of revisions that we want to yield.
1928 # First step is to fill wanted, the set of revisions that we want to yield.
1929 # When it does not induce extra cost, we also fill fncache for revisions in
1929 # When it does not induce extra cost, we also fill fncache for revisions in
1930 # wanted: a cache of filenames that were changed (ctx.files()) and that
1930 # wanted: a cache of filenames that were changed (ctx.files()) and that
1931 # match the file filtering conditions.
1931 # match the file filtering conditions.
1932
1932
1933 if match.always():
1933 if match.always():
1934 # No files, no patterns. Display all revs.
1934 # No files, no patterns. Display all revs.
1935 wanted = revs
1935 wanted = revs
1936 elif not slowpath:
1936 elif not slowpath:
1937 # We only have to read through the filelog to find wanted revisions
1937 # We only have to read through the filelog to find wanted revisions
1938
1938
1939 try:
1939 try:
1940 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1940 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1941 except FileWalkError:
1941 except FileWalkError:
1942 slowpath = True
1942 slowpath = True
1943
1943
1944 # We decided to fall back to the slowpath because at least one
1944 # We decided to fall back to the slowpath because at least one
1945 # of the paths was not a file. Check to see if at least one of them
1945 # of the paths was not a file. Check to see if at least one of them
1946 # existed in history, otherwise simply return
1946 # existed in history, otherwise simply return
1947 for path in match.files():
1947 for path in match.files():
1948 if path == '.' or path in repo.store:
1948 if path == '.' or path in repo.store:
1949 break
1949 break
1950 else:
1950 else:
1951 return []
1951 return []
1952
1952
1953 if slowpath:
1953 if slowpath:
1954 # We have to read the changelog to match filenames against
1954 # We have to read the changelog to match filenames against
1955 # changed files
1955 # changed files
1956
1956
1957 if follow:
1957 if follow:
1958 raise error.Abort(_('can only follow copies/renames for explicit '
1958 raise error.Abort(_('can only follow copies/renames for explicit '
1959 'filenames'))
1959 'filenames'))
1960
1960
1961 # The slow path checks files modified in every changeset.
1961 # The slow path checks files modified in every changeset.
1962 # This is really slow on large repos, so compute the set lazily.
1962 # This is really slow on large repos, so compute the set lazily.
1963 class lazywantedset(object):
1963 class lazywantedset(object):
1964 def __init__(self):
1964 def __init__(self):
1965 self.set = set()
1965 self.set = set()
1966 self.revs = set(revs)
1966 self.revs = set(revs)
1967
1967
1968 # No need to worry about locality here because it will be accessed
1968 # No need to worry about locality here because it will be accessed
1969 # in the same order as the increasing window below.
1969 # in the same order as the increasing window below.
1970 def __contains__(self, value):
1970 def __contains__(self, value):
1971 if value in self.set:
1971 if value in self.set:
1972 return True
1972 return True
1973 elif not value in self.revs:
1973 elif not value in self.revs:
1974 return False
1974 return False
1975 else:
1975 else:
1976 self.revs.discard(value)
1976 self.revs.discard(value)
1977 ctx = change(value)
1977 ctx = change(value)
1978 matches = filter(match, ctx.files())
1978 matches = filter(match, ctx.files())
1979 if matches:
1979 if matches:
1980 fncache[value] = matches
1980 fncache[value] = matches
1981 self.set.add(value)
1981 self.set.add(value)
1982 return True
1982 return True
1983 return False
1983 return False
1984
1984
1985 def discard(self, value):
1985 def discard(self, value):
1986 self.revs.discard(value)
1986 self.revs.discard(value)
1987 self.set.discard(value)
1987 self.set.discard(value)
1988
1988
1989 wanted = lazywantedset()
1989 wanted = lazywantedset()
1990
1990
1991 # it might be worthwhile to do this in the iterator if the rev range
1991 # it might be worthwhile to do this in the iterator if the rev range
1992 # is descending and the prune args are all within that range
1992 # is descending and the prune args are all within that range
1993 for rev in opts.get('prune', ()):
1993 for rev in opts.get('prune', ()):
1994 rev = repo[rev].rev()
1994 rev = repo[rev].rev()
1995 ff = _followfilter(repo)
1995 ff = _followfilter(repo)
1996 stop = min(revs[0], revs[-1])
1996 stop = min(revs[0], revs[-1])
1997 for x in xrange(rev, stop - 1, -1):
1997 for x in xrange(rev, stop - 1, -1):
1998 if ff.match(x):
1998 if ff.match(x):
1999 wanted = wanted - [x]
1999 wanted = wanted - [x]
2000
2000
2001 # Now that wanted is correctly initialized, we can iterate over the
2001 # Now that wanted is correctly initialized, we can iterate over the
2002 # revision range, yielding only revisions in wanted.
2002 # revision range, yielding only revisions in wanted.
2003 def iterate():
2003 def iterate():
2004 if follow and match.always():
2004 if follow and match.always():
2005 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2005 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2006 def want(rev):
2006 def want(rev):
2007 return ff.match(rev) and rev in wanted
2007 return ff.match(rev) and rev in wanted
2008 else:
2008 else:
2009 def want(rev):
2009 def want(rev):
2010 return rev in wanted
2010 return rev in wanted
2011
2011
2012 it = iter(revs)
2012 it = iter(revs)
2013 stopiteration = False
2013 stopiteration = False
2014 for windowsize in increasingwindows():
2014 for windowsize in increasingwindows():
2015 nrevs = []
2015 nrevs = []
2016 for i in xrange(windowsize):
2016 for i in xrange(windowsize):
2017 rev = next(it, None)
2017 rev = next(it, None)
2018 if rev is None:
2018 if rev is None:
2019 stopiteration = True
2019 stopiteration = True
2020 break
2020 break
2021 elif want(rev):
2021 elif want(rev):
2022 nrevs.append(rev)
2022 nrevs.append(rev)
2023 for rev in sorted(nrevs):
2023 for rev in sorted(nrevs):
2024 fns = fncache.get(rev)
2024 fns = fncache.get(rev)
2025 ctx = change(rev)
2025 ctx = change(rev)
2026 if not fns:
2026 if not fns:
2027 def fns_generator():
2027 def fns_generator():
2028 for f in ctx.files():
2028 for f in ctx.files():
2029 if match(f):
2029 if match(f):
2030 yield f
2030 yield f
2031 fns = fns_generator()
2031 fns = fns_generator()
2032 prepare(ctx, fns)
2032 prepare(ctx, fns)
2033 for rev in nrevs:
2033 for rev in nrevs:
2034 yield change(rev)
2034 yield change(rev)
2035
2035
2036 if stopiteration:
2036 if stopiteration:
2037 break
2037 break
2038
2038
2039 return iterate()
2039 return iterate()
2040
2040
2041 def _makefollowlogfilematcher(repo, files, followfirst):
2041 def _makefollowlogfilematcher(repo, files, followfirst):
2042 # When displaying a revision with --patch --follow FILE, we have
2042 # When displaying a revision with --patch --follow FILE, we have
2043 # to know which file of the revision must be diffed. With
2043 # to know which file of the revision must be diffed. With
2044 # --follow, we want the names of the ancestors of FILE in the
2044 # --follow, we want the names of the ancestors of FILE in the
2045 # revision, stored in "fcache". "fcache" is populated by
2045 # revision, stored in "fcache". "fcache" is populated by
2046 # reproducing the graph traversal already done by --follow revset
2046 # reproducing the graph traversal already done by --follow revset
2047 # and relating revs to file names (which is not "correct" but
2047 # and relating revs to file names (which is not "correct" but
2048 # good enough).
2048 # good enough).
2049 fcache = {}
2049 fcache = {}
2050 fcacheready = [False]
2050 fcacheready = [False]
2051 pctx = repo['.']
2051 pctx = repo['.']
2052
2052
2053 def populate():
2053 def populate():
2054 for fn in files:
2054 for fn in files:
2055 fctx = pctx[fn]
2055 fctx = pctx[fn]
2056 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2056 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2057 for c in fctx.ancestors(followfirst=followfirst):
2057 for c in fctx.ancestors(followfirst=followfirst):
2058 fcache.setdefault(c.rev(), set()).add(c.path())
2058 fcache.setdefault(c.rev(), set()).add(c.path())
2059
2059
2060 def filematcher(rev):
2060 def filematcher(rev):
2061 if not fcacheready[0]:
2061 if not fcacheready[0]:
2062 # Lazy initialization
2062 # Lazy initialization
2063 fcacheready[0] = True
2063 fcacheready[0] = True
2064 populate()
2064 populate()
2065 return scmutil.matchfiles(repo, fcache.get(rev, []))
2065 return scmutil.matchfiles(repo, fcache.get(rev, []))
2066
2066
2067 return filematcher
2067 return filematcher
2068
2068
2069 def _makenofollowlogfilematcher(repo, pats, opts):
2069 def _makenofollowlogfilematcher(repo, pats, opts):
2070 '''hook for extensions to override the filematcher for non-follow cases'''
2070 '''hook for extensions to override the filematcher for non-follow cases'''
2071 return None
2071 return None
2072
2072
2073 def _makelogrevset(repo, pats, opts, revs):
2073 def _makelogrevset(repo, pats, opts, revs):
2074 """Return (expr, filematcher) where expr is a revset string built
2074 """Return (expr, filematcher) where expr is a revset string built
2075 from log options and file patterns or None. If --stat or --patch
2075 from log options and file patterns or None. If --stat or --patch
2076 are not passed filematcher is None. Otherwise it is a callable
2076 are not passed filematcher is None. Otherwise it is a callable
2077 taking a revision number and returning a match objects filtering
2077 taking a revision number and returning a match objects filtering
2078 the files to be detailed when displaying the revision.
2078 the files to be detailed when displaying the revision.
2079 """
2079 """
2080 opt2revset = {
2080 opt2revset = {
2081 'no_merges': ('not merge()', None),
2081 'no_merges': ('not merge()', None),
2082 'only_merges': ('merge()', None),
2082 'only_merges': ('merge()', None),
2083 '_ancestors': ('ancestors(%(val)s)', None),
2083 '_ancestors': ('ancestors(%(val)s)', None),
2084 '_fancestors': ('_firstancestors(%(val)s)', None),
2084 '_fancestors': ('_firstancestors(%(val)s)', None),
2085 '_descendants': ('descendants(%(val)s)', None),
2085 '_descendants': ('descendants(%(val)s)', None),
2086 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2086 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2087 '_matchfiles': ('_matchfiles(%(val)s)', None),
2087 '_matchfiles': ('_matchfiles(%(val)s)', None),
2088 'date': ('date(%(val)r)', None),
2088 'date': ('date(%(val)r)', None),
2089 'branch': ('branch(%(val)r)', ' or '),
2089 'branch': ('branch(%(val)r)', ' or '),
2090 '_patslog': ('filelog(%(val)r)', ' or '),
2090 '_patslog': ('filelog(%(val)r)', ' or '),
2091 '_patsfollow': ('follow(%(val)r)', ' or '),
2091 '_patsfollow': ('follow(%(val)r)', ' or '),
2092 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2092 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2093 'keyword': ('keyword(%(val)r)', ' or '),
2093 'keyword': ('keyword(%(val)r)', ' or '),
2094 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2094 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2095 'user': ('user(%(val)r)', ' or '),
2095 'user': ('user(%(val)r)', ' or '),
2096 }
2096 }
2097
2097
2098 opts = dict(opts)
2098 opts = dict(opts)
2099 # follow or not follow?
2099 # follow or not follow?
2100 follow = opts.get('follow') or opts.get('follow_first')
2100 follow = opts.get('follow') or opts.get('follow_first')
2101 if opts.get('follow_first'):
2101 if opts.get('follow_first'):
2102 followfirst = 1
2102 followfirst = 1
2103 else:
2103 else:
2104 followfirst = 0
2104 followfirst = 0
2105 # --follow with FILE behavior depends on revs...
2105 # --follow with FILE behavior depends on revs...
2106 it = iter(revs)
2106 it = iter(revs)
2107 startrev = next(it)
2107 startrev = next(it)
2108 followdescendants = startrev < next(it, startrev)
2108 followdescendants = startrev < next(it, startrev)
2109
2109
2110 # branch and only_branch are really aliases and must be handled at
2110 # branch and only_branch are really aliases and must be handled at
2111 # the same time
2111 # the same time
2112 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2112 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2113 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2113 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2114 # pats/include/exclude are passed to match.match() directly in
2114 # pats/include/exclude are passed to match.match() directly in
2115 # _matchfiles() revset but walkchangerevs() builds its matcher with
2115 # _matchfiles() revset but walkchangerevs() builds its matcher with
2116 # scmutil.match(). The difference is input pats are globbed on
2116 # scmutil.match(). The difference is input pats are globbed on
2117 # platforms without shell expansion (windows).
2117 # platforms without shell expansion (windows).
2118 wctx = repo[None]
2118 wctx = repo[None]
2119 match, pats = scmutil.matchandpats(wctx, pats, opts)
2119 match, pats = scmutil.matchandpats(wctx, pats, opts)
2120 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2120 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2121 opts.get('removed'))
2121 opts.get('removed'))
2122 if not slowpath:
2122 if not slowpath:
2123 for f in match.files():
2123 for f in match.files():
2124 if follow and f not in wctx:
2124 if follow and f not in wctx:
2125 # If the file exists, it may be a directory, so let it
2125 # If the file exists, it may be a directory, so let it
2126 # take the slow path.
2126 # take the slow path.
2127 if os.path.exists(repo.wjoin(f)):
2127 if os.path.exists(repo.wjoin(f)):
2128 slowpath = True
2128 slowpath = True
2129 continue
2129 continue
2130 else:
2130 else:
2131 raise error.Abort(_('cannot follow file not in parent '
2131 raise error.Abort(_('cannot follow file not in parent '
2132 'revision: "%s"') % f)
2132 'revision: "%s"') % f)
2133 filelog = repo.file(f)
2133 filelog = repo.file(f)
2134 if not filelog:
2134 if not filelog:
2135 # A zero count may be a directory or deleted file, so
2135 # A zero count may be a directory or deleted file, so
2136 # try to find matching entries on the slow path.
2136 # try to find matching entries on the slow path.
2137 if follow:
2137 if follow:
2138 raise error.Abort(
2138 raise error.Abort(
2139 _('cannot follow nonexistent file: "%s"') % f)
2139 _('cannot follow nonexistent file: "%s"') % f)
2140 slowpath = True
2140 slowpath = True
2141
2141
2142 # We decided to fall back to the slowpath because at least one
2142 # We decided to fall back to the slowpath because at least one
2143 # of the paths was not a file. Check to see if at least one of them
2143 # of the paths was not a file. Check to see if at least one of them
2144 # existed in history - in that case, we'll continue down the
2144 # existed in history - in that case, we'll continue down the
2145 # slowpath; otherwise, we can turn off the slowpath
2145 # slowpath; otherwise, we can turn off the slowpath
2146 if slowpath:
2146 if slowpath:
2147 for path in match.files():
2147 for path in match.files():
2148 if path == '.' or path in repo.store:
2148 if path == '.' or path in repo.store:
2149 break
2149 break
2150 else:
2150 else:
2151 slowpath = False
2151 slowpath = False
2152
2152
2153 fpats = ('_patsfollow', '_patsfollowfirst')
2153 fpats = ('_patsfollow', '_patsfollowfirst')
2154 fnopats = (('_ancestors', '_fancestors'),
2154 fnopats = (('_ancestors', '_fancestors'),
2155 ('_descendants', '_fdescendants'))
2155 ('_descendants', '_fdescendants'))
2156 if slowpath:
2156 if slowpath:
2157 # See walkchangerevs() slow path.
2157 # See walkchangerevs() slow path.
2158 #
2158 #
2159 # pats/include/exclude cannot be represented as separate
2159 # pats/include/exclude cannot be represented as separate
2160 # revset expressions as their filtering logic applies at file
2160 # revset expressions as their filtering logic applies at file
2161 # level. For instance "-I a -X a" matches a revision touching
2161 # level. For instance "-I a -X a" matches a revision touching
2162 # "a" and "b" while "file(a) and not file(b)" does
2162 # "a" and "b" while "file(a) and not file(b)" does
2163 # not. Besides, filesets are evaluated against the working
2163 # not. Besides, filesets are evaluated against the working
2164 # directory.
2164 # directory.
2165 matchargs = ['r:', 'd:relpath']
2165 matchargs = ['r:', 'd:relpath']
2166 for p in pats:
2166 for p in pats:
2167 matchargs.append('p:' + p)
2167 matchargs.append('p:' + p)
2168 for p in opts.get('include', []):
2168 for p in opts.get('include', []):
2169 matchargs.append('i:' + p)
2169 matchargs.append('i:' + p)
2170 for p in opts.get('exclude', []):
2170 for p in opts.get('exclude', []):
2171 matchargs.append('x:' + p)
2171 matchargs.append('x:' + p)
2172 matchargs = ','.join(('%r' % p) for p in matchargs)
2172 matchargs = ','.join(('%r' % p) for p in matchargs)
2173 opts['_matchfiles'] = matchargs
2173 opts['_matchfiles'] = matchargs
2174 if follow:
2174 if follow:
2175 opts[fnopats[0][followfirst]] = '.'
2175 opts[fnopats[0][followfirst]] = '.'
2176 else:
2176 else:
2177 if follow:
2177 if follow:
2178 if pats:
2178 if pats:
2179 # follow() revset interprets its file argument as a
2179 # follow() revset interprets its file argument as a
2180 # manifest entry, so use match.files(), not pats.
2180 # manifest entry, so use match.files(), not pats.
2181 opts[fpats[followfirst]] = list(match.files())
2181 opts[fpats[followfirst]] = list(match.files())
2182 else:
2182 else:
2183 op = fnopats[followdescendants][followfirst]
2183 op = fnopats[followdescendants][followfirst]
2184 opts[op] = 'rev(%d)' % startrev
2184 opts[op] = 'rev(%d)' % startrev
2185 else:
2185 else:
2186 opts['_patslog'] = list(pats)
2186 opts['_patslog'] = list(pats)
2187
2187
2188 filematcher = None
2188 filematcher = None
2189 if opts.get('patch') or opts.get('stat'):
2189 if opts.get('patch') or opts.get('stat'):
2190 # When following files, track renames via a special matcher.
2190 # When following files, track renames via a special matcher.
2191 # If we're forced to take the slowpath it means we're following
2191 # If we're forced to take the slowpath it means we're following
2192 # at least one pattern/directory, so don't bother with rename tracking.
2192 # at least one pattern/directory, so don't bother with rename tracking.
2193 if follow and not match.always() and not slowpath:
2193 if follow and not match.always() and not slowpath:
2194 # _makefollowlogfilematcher expects its files argument to be
2194 # _makefollowlogfilematcher expects its files argument to be
2195 # relative to the repo root, so use match.files(), not pats.
2195 # relative to the repo root, so use match.files(), not pats.
2196 filematcher = _makefollowlogfilematcher(repo, match.files(),
2196 filematcher = _makefollowlogfilematcher(repo, match.files(),
2197 followfirst)
2197 followfirst)
2198 else:
2198 else:
2199 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2199 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2200 if filematcher is None:
2200 if filematcher is None:
2201 filematcher = lambda rev: match
2201 filematcher = lambda rev: match
2202
2202
2203 expr = []
2203 expr = []
2204 for op, val in sorted(opts.iteritems()):
2204 for op, val in sorted(opts.iteritems()):
2205 if not val:
2205 if not val:
2206 continue
2206 continue
2207 if op not in opt2revset:
2207 if op not in opt2revset:
2208 continue
2208 continue
2209 revop, andor = opt2revset[op]
2209 revop, andor = opt2revset[op]
2210 if '%(val)' not in revop:
2210 if '%(val)' not in revop:
2211 expr.append(revop)
2211 expr.append(revop)
2212 else:
2212 else:
2213 if not isinstance(val, list):
2213 if not isinstance(val, list):
2214 e = revop % {'val': val}
2214 e = revop % {'val': val}
2215 else:
2215 else:
2216 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2216 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2217 expr.append(e)
2217 expr.append(e)
2218
2218
2219 if expr:
2219 if expr:
2220 expr = '(' + ' and '.join(expr) + ')'
2220 expr = '(' + ' and '.join(expr) + ')'
2221 else:
2221 else:
2222 expr = None
2222 expr = None
2223 return expr, filematcher
2223 return expr, filematcher
2224
2224
2225 def _logrevs(repo, opts):
2225 def _logrevs(repo, opts):
2226 # Default --rev value depends on --follow but --follow behavior
2226 # Default --rev value depends on --follow but --follow behavior
2227 # depends on revisions resolved from --rev...
2227 # depends on revisions resolved from --rev...
2228 follow = opts.get('follow') or opts.get('follow_first')
2228 follow = opts.get('follow') or opts.get('follow_first')
2229 if opts.get('rev'):
2229 if opts.get('rev'):
2230 revs = scmutil.revrange(repo, opts['rev'])
2230 revs = scmutil.revrange(repo, opts['rev'])
2231 elif follow and repo.dirstate.p1() == nullid:
2231 elif follow and repo.dirstate.p1() == nullid:
2232 revs = smartset.baseset()
2232 revs = smartset.baseset()
2233 elif follow:
2233 elif follow:
2234 revs = repo.revs('reverse(:.)')
2234 revs = repo.revs('reverse(:.)')
2235 else:
2235 else:
2236 revs = smartset.spanset(repo)
2236 revs = smartset.spanset(repo)
2237 revs.reverse()
2237 revs.reverse()
2238 return revs
2238 return revs
2239
2239
2240 def getgraphlogrevs(repo, pats, opts):
2240 def getgraphlogrevs(repo, pats, opts):
2241 """Return (revs, expr, filematcher) where revs is an iterable of
2241 """Return (revs, expr, filematcher) where revs is an iterable of
2242 revision numbers, expr is a revset string built from log options
2242 revision numbers, expr is a revset string built from log options
2243 and file patterns or None, and used to filter 'revs'. If --stat or
2243 and file patterns or None, and used to filter 'revs'. If --stat or
2244 --patch are not passed filematcher is None. Otherwise it is a
2244 --patch are not passed filematcher is None. Otherwise it is a
2245 callable taking a revision number and returning a match objects
2245 callable taking a revision number and returning a match objects
2246 filtering the files to be detailed when displaying the revision.
2246 filtering the files to be detailed when displaying the revision.
2247 """
2247 """
2248 limit = loglimit(opts)
2248 limit = loglimit(opts)
2249 revs = _logrevs(repo, opts)
2249 revs = _logrevs(repo, opts)
2250 if not revs:
2250 if not revs:
2251 return smartset.baseset(), None, None
2251 return smartset.baseset(), None, None
2252 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2252 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2253 if opts.get('rev'):
2253 if opts.get('rev'):
2254 # User-specified revs might be unsorted, but don't sort before
2254 # User-specified revs might be unsorted, but don't sort before
2255 # _makelogrevset because it might depend on the order of revs
2255 # _makelogrevset because it might depend on the order of revs
2256 if not (revs.isdescending() or revs.istopo()):
2256 if not (revs.isdescending() or revs.istopo()):
2257 revs.sort(reverse=True)
2257 revs.sort(reverse=True)
2258 if expr:
2258 if expr:
2259 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2259 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2260 revs = matcher(repo, revs)
2260 revs = matcher(repo, revs)
2261 if limit is not None:
2261 if limit is not None:
2262 limitedrevs = []
2262 limitedrevs = []
2263 for idx, rev in enumerate(revs):
2263 for idx, rev in enumerate(revs):
2264 if idx >= limit:
2264 if idx >= limit:
2265 break
2265 break
2266 limitedrevs.append(rev)
2266 limitedrevs.append(rev)
2267 revs = smartset.baseset(limitedrevs)
2267 revs = smartset.baseset(limitedrevs)
2268
2268
2269 return revs, expr, filematcher
2269 return revs, expr, filematcher
2270
2270
2271 def getlogrevs(repo, pats, opts):
2271 def getlogrevs(repo, pats, opts):
2272 """Return (revs, expr, filematcher) where revs is an iterable of
2272 """Return (revs, expr, filematcher) where revs is an iterable of
2273 revision numbers, expr is a revset string built from log options
2273 revision numbers, expr is a revset string built from log options
2274 and file patterns or None, and used to filter 'revs'. If --stat or
2274 and file patterns or None, and used to filter 'revs'. If --stat or
2275 --patch are not passed filematcher is None. Otherwise it is a
2275 --patch are not passed filematcher is None. Otherwise it is a
2276 callable taking a revision number and returning a match objects
2276 callable taking a revision number and returning a match objects
2277 filtering the files to be detailed when displaying the revision.
2277 filtering the files to be detailed when displaying the revision.
2278 """
2278 """
2279 limit = loglimit(opts)
2279 limit = loglimit(opts)
2280 revs = _logrevs(repo, opts)
2280 revs = _logrevs(repo, opts)
2281 if not revs:
2281 if not revs:
2282 return smartset.baseset([]), None, None
2282 return smartset.baseset([]), None, None
2283 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2283 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2284 if expr:
2284 if expr:
2285 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2285 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2286 revs = matcher(repo, revs)
2286 revs = matcher(repo, revs)
2287 if limit is not None:
2287 if limit is not None:
2288 limitedrevs = []
2288 limitedrevs = []
2289 for idx, r in enumerate(revs):
2289 for idx, r in enumerate(revs):
2290 if limit <= idx:
2290 if limit <= idx:
2291 break
2291 break
2292 limitedrevs.append(r)
2292 limitedrevs.append(r)
2293 revs = smartset.baseset(limitedrevs)
2293 revs = smartset.baseset(limitedrevs)
2294
2294
2295 return revs, expr, filematcher
2295 return revs, expr, filematcher
2296
2296
2297 def _graphnodeformatter(ui, displayer):
2297 def _graphnodeformatter(ui, displayer):
2298 spec = ui.config('ui', 'graphnodetemplate')
2298 spec = ui.config('ui', 'graphnodetemplate')
2299 if not spec:
2299 if not spec:
2300 return templatekw.showgraphnode # fast path for "{graphnode}"
2300 return templatekw.showgraphnode # fast path for "{graphnode}"
2301
2301
2302 spec = templater.unquotestring(spec)
2302 spec = templater.unquotestring(spec)
2303 templ = formatter.maketemplater(ui, spec)
2303 templ = formatter.maketemplater(ui, spec)
2304 cache = {}
2304 cache = {}
2305 if isinstance(displayer, changeset_templater):
2305 if isinstance(displayer, changeset_templater):
2306 cache = displayer.cache # reuse cache of slow templates
2306 cache = displayer.cache # reuse cache of slow templates
2307 props = templatekw.keywords.copy()
2307 props = templatekw.keywords.copy()
2308 props['templ'] = templ
2308 props['templ'] = templ
2309 props['cache'] = cache
2309 props['cache'] = cache
2310 def formatnode(repo, ctx):
2310 def formatnode(repo, ctx):
2311 props['ctx'] = ctx
2311 props['ctx'] = ctx
2312 props['repo'] = repo
2312 props['repo'] = repo
2313 props['ui'] = repo.ui
2313 props['ui'] = repo.ui
2314 props['revcache'] = {}
2314 props['revcache'] = {}
2315 return templ.render(props)
2315 return templ.render(props)
2316 return formatnode
2316 return formatnode
2317
2317
2318 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2318 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2319 filematcher=None):
2319 filematcher=None):
2320 formatnode = _graphnodeformatter(ui, displayer)
2320 formatnode = _graphnodeformatter(ui, displayer)
2321 state = graphmod.asciistate()
2321 state = graphmod.asciistate()
2322 styles = state['styles']
2322 styles = state['styles']
2323
2323
2324 # only set graph styling if HGPLAIN is not set.
2324 # only set graph styling if HGPLAIN is not set.
2325 if ui.plain('graph'):
2325 if ui.plain('graph'):
2326 # set all edge styles to |, the default pre-3.8 behaviour
2326 # set all edge styles to |, the default pre-3.8 behaviour
2327 styles.update(dict.fromkeys(styles, '|'))
2327 styles.update(dict.fromkeys(styles, '|'))
2328 else:
2328 else:
2329 edgetypes = {
2329 edgetypes = {
2330 'parent': graphmod.PARENT,
2330 'parent': graphmod.PARENT,
2331 'grandparent': graphmod.GRANDPARENT,
2331 'grandparent': graphmod.GRANDPARENT,
2332 'missing': graphmod.MISSINGPARENT
2332 'missing': graphmod.MISSINGPARENT
2333 }
2333 }
2334 for name, key in edgetypes.items():
2334 for name, key in edgetypes.items():
2335 # experimental config: experimental.graphstyle.*
2335 # experimental config: experimental.graphstyle.*
2336 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2336 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2337 styles[key])
2337 styles[key])
2338 if not styles[key]:
2338 if not styles[key]:
2339 styles[key] = None
2339 styles[key] = None
2340
2340
2341 # experimental config: experimental.graphshorten
2341 # experimental config: experimental.graphshorten
2342 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2342 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2343
2343
2344 for rev, type, ctx, parents in dag:
2344 for rev, type, ctx, parents in dag:
2345 char = formatnode(repo, ctx)
2345 char = formatnode(repo, ctx)
2346 copies = None
2346 copies = None
2347 if getrenamed and ctx.rev():
2347 if getrenamed and ctx.rev():
2348 copies = []
2348 copies = []
2349 for fn in ctx.files():
2349 for fn in ctx.files():
2350 rename = getrenamed(fn, ctx.rev())
2350 rename = getrenamed(fn, ctx.rev())
2351 if rename:
2351 if rename:
2352 copies.append((fn, rename[0]))
2352 copies.append((fn, rename[0]))
2353 revmatchfn = None
2353 revmatchfn = None
2354 if filematcher is not None:
2354 if filematcher is not None:
2355 revmatchfn = filematcher(ctx.rev())
2355 revmatchfn = filematcher(ctx.rev())
2356 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2356 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2357 lines = displayer.hunk.pop(rev).split('\n')
2357 lines = displayer.hunk.pop(rev).split('\n')
2358 if not lines[-1]:
2358 if not lines[-1]:
2359 del lines[-1]
2359 del lines[-1]
2360 displayer.flush(ctx)
2360 displayer.flush(ctx)
2361 edges = edgefn(type, char, lines, state, rev, parents)
2361 edges = edgefn(type, char, lines, state, rev, parents)
2362 for type, char, lines, coldata in edges:
2362 for type, char, lines, coldata in edges:
2363 graphmod.ascii(ui, state, type, char, lines, coldata)
2363 graphmod.ascii(ui, state, type, char, lines, coldata)
2364 displayer.close()
2364 displayer.close()
2365
2365
2366 def graphlog(ui, repo, pats, opts):
2366 def graphlog(ui, repo, pats, opts):
2367 # Parameters are identical to log command ones
2367 # Parameters are identical to log command ones
2368 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2368 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2369 revdag = graphmod.dagwalker(repo, revs)
2369 revdag = graphmod.dagwalker(repo, revs)
2370
2370
2371 getrenamed = None
2371 getrenamed = None
2372 if opts.get('copies'):
2372 if opts.get('copies'):
2373 endrev = None
2373 endrev = None
2374 if opts.get('rev'):
2374 if opts.get('rev'):
2375 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2375 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2376 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2376 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2377
2377
2378 ui.pager('log')
2378 ui.pager('log')
2379 displayer = show_changeset(ui, repo, opts, buffered=True)
2379 displayer = show_changeset(ui, repo, opts, buffered=True)
2380 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2380 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2381 filematcher)
2381 filematcher)
2382
2382
2383 def checkunsupportedgraphflags(pats, opts):
2383 def checkunsupportedgraphflags(pats, opts):
2384 for op in ["newest_first"]:
2384 for op in ["newest_first"]:
2385 if op in opts and opts[op]:
2385 if op in opts and opts[op]:
2386 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2386 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2387 % op.replace("_", "-"))
2387 % op.replace("_", "-"))
2388
2388
2389 def graphrevs(repo, nodes, opts):
2389 def graphrevs(repo, nodes, opts):
2390 limit = loglimit(opts)
2390 limit = loglimit(opts)
2391 nodes.reverse()
2391 nodes.reverse()
2392 if limit is not None:
2392 if limit is not None:
2393 nodes = nodes[:limit]
2393 nodes = nodes[:limit]
2394 return graphmod.nodes(repo, nodes)
2394 return graphmod.nodes(repo, nodes)
2395
2395
2396 def add(ui, repo, match, prefix, explicitonly, **opts):
2396 def add(ui, repo, match, prefix, explicitonly, **opts):
2397 join = lambda f: os.path.join(prefix, f)
2397 join = lambda f: os.path.join(prefix, f)
2398 bad = []
2398 bad = []
2399
2399
2400 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2400 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2401 names = []
2401 names = []
2402 wctx = repo[None]
2402 wctx = repo[None]
2403 cca = None
2403 cca = None
2404 abort, warn = scmutil.checkportabilityalert(ui)
2404 abort, warn = scmutil.checkportabilityalert(ui)
2405 if abort or warn:
2405 if abort or warn:
2406 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2406 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2407
2407
2408 badmatch = matchmod.badmatch(match, badfn)
2408 badmatch = matchmod.badmatch(match, badfn)
2409 dirstate = repo.dirstate
2409 dirstate = repo.dirstate
2410 # We don't want to just call wctx.walk here, since it would return a lot of
2410 # We don't want to just call wctx.walk here, since it would return a lot of
2411 # clean files, which we aren't interested in and takes time.
2411 # clean files, which we aren't interested in and takes time.
2412 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2412 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2413 True, False, full=False)):
2413 True, False, full=False)):
2414 exact = match.exact(f)
2414 exact = match.exact(f)
2415 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2415 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2416 if cca:
2416 if cca:
2417 cca(f)
2417 cca(f)
2418 names.append(f)
2418 names.append(f)
2419 if ui.verbose or not exact:
2419 if ui.verbose or not exact:
2420 ui.status(_('adding %s\n') % match.rel(f))
2420 ui.status(_('adding %s\n') % match.rel(f))
2421
2421
2422 for subpath in sorted(wctx.substate):
2422 for subpath in sorted(wctx.substate):
2423 sub = wctx.sub(subpath)
2423 sub = wctx.sub(subpath)
2424 try:
2424 try:
2425 submatch = matchmod.subdirmatcher(subpath, match)
2425 submatch = matchmod.subdirmatcher(subpath, match)
2426 if opts.get(r'subrepos'):
2426 if opts.get(r'subrepos'):
2427 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2427 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2428 else:
2428 else:
2429 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2429 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2430 except error.LookupError:
2430 except error.LookupError:
2431 ui.status(_("skipping missing subrepository: %s\n")
2431 ui.status(_("skipping missing subrepository: %s\n")
2432 % join(subpath))
2432 % join(subpath))
2433
2433
2434 if not opts.get(r'dry_run'):
2434 if not opts.get(r'dry_run'):
2435 rejected = wctx.add(names, prefix)
2435 rejected = wctx.add(names, prefix)
2436 bad.extend(f for f in rejected if f in match.files())
2436 bad.extend(f for f in rejected if f in match.files())
2437 return bad
2437 return bad
2438
2438
2439 def addwebdirpath(repo, serverpath, webconf):
2439 def addwebdirpath(repo, serverpath, webconf):
2440 webconf[serverpath] = repo.root
2440 webconf[serverpath] = repo.root
2441 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2441 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2442
2442
2443 for r in repo.revs('filelog("path:.hgsub")'):
2443 for r in repo.revs('filelog("path:.hgsub")'):
2444 ctx = repo[r]
2444 ctx = repo[r]
2445 for subpath in ctx.substate:
2445 for subpath in ctx.substate:
2446 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2446 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2447
2447
2448 def forget(ui, repo, match, prefix, explicitonly):
2448 def forget(ui, repo, match, prefix, explicitonly):
2449 join = lambda f: os.path.join(prefix, f)
2449 join = lambda f: os.path.join(prefix, f)
2450 bad = []
2450 bad = []
2451 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2451 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2452 wctx = repo[None]
2452 wctx = repo[None]
2453 forgot = []
2453 forgot = []
2454
2454
2455 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2455 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2456 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2456 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2457 if explicitonly:
2457 if explicitonly:
2458 forget = [f for f in forget if match.exact(f)]
2458 forget = [f for f in forget if match.exact(f)]
2459
2459
2460 for subpath in sorted(wctx.substate):
2460 for subpath in sorted(wctx.substate):
2461 sub = wctx.sub(subpath)
2461 sub = wctx.sub(subpath)
2462 try:
2462 try:
2463 submatch = matchmod.subdirmatcher(subpath, match)
2463 submatch = matchmod.subdirmatcher(subpath, match)
2464 subbad, subforgot = sub.forget(submatch, prefix)
2464 subbad, subforgot = sub.forget(submatch, prefix)
2465 bad.extend([subpath + '/' + f for f in subbad])
2465 bad.extend([subpath + '/' + f for f in subbad])
2466 forgot.extend([subpath + '/' + f for f in subforgot])
2466 forgot.extend([subpath + '/' + f for f in subforgot])
2467 except error.LookupError:
2467 except error.LookupError:
2468 ui.status(_("skipping missing subrepository: %s\n")
2468 ui.status(_("skipping missing subrepository: %s\n")
2469 % join(subpath))
2469 % join(subpath))
2470
2470
2471 if not explicitonly:
2471 if not explicitonly:
2472 for f in match.files():
2472 for f in match.files():
2473 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2473 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2474 if f not in forgot:
2474 if f not in forgot:
2475 if repo.wvfs.exists(f):
2475 if repo.wvfs.exists(f):
2476 # Don't complain if the exact case match wasn't given.
2476 # Don't complain if the exact case match wasn't given.
2477 # But don't do this until after checking 'forgot', so
2477 # But don't do this until after checking 'forgot', so
2478 # that subrepo files aren't normalized, and this op is
2478 # that subrepo files aren't normalized, and this op is
2479 # purely from data cached by the status walk above.
2479 # purely from data cached by the status walk above.
2480 if repo.dirstate.normalize(f) in repo.dirstate:
2480 if repo.dirstate.normalize(f) in repo.dirstate:
2481 continue
2481 continue
2482 ui.warn(_('not removing %s: '
2482 ui.warn(_('not removing %s: '
2483 'file is already untracked\n')
2483 'file is already untracked\n')
2484 % match.rel(f))
2484 % match.rel(f))
2485 bad.append(f)
2485 bad.append(f)
2486
2486
2487 for f in forget:
2487 for f in forget:
2488 if ui.verbose or not match.exact(f):
2488 if ui.verbose or not match.exact(f):
2489 ui.status(_('removing %s\n') % match.rel(f))
2489 ui.status(_('removing %s\n') % match.rel(f))
2490
2490
2491 rejected = wctx.forget(forget, prefix)
2491 rejected = wctx.forget(forget, prefix)
2492 bad.extend(f for f in rejected if f in match.files())
2492 bad.extend(f for f in rejected if f in match.files())
2493 forgot.extend(f for f in forget if f not in rejected)
2493 forgot.extend(f for f in forget if f not in rejected)
2494 return bad, forgot
2494 return bad, forgot
2495
2495
2496 def files(ui, ctx, m, fm, fmt, subrepos):
2496 def files(ui, ctx, m, fm, fmt, subrepos):
2497 rev = ctx.rev()
2497 rev = ctx.rev()
2498 ret = 1
2498 ret = 1
2499 ds = ctx.repo().dirstate
2499 ds = ctx.repo().dirstate
2500
2500
2501 for f in ctx.matches(m):
2501 for f in ctx.matches(m):
2502 if rev is None and ds[f] == 'r':
2502 if rev is None and ds[f] == 'r':
2503 continue
2503 continue
2504 fm.startitem()
2504 fm.startitem()
2505 if ui.verbose:
2505 if ui.verbose:
2506 fc = ctx[f]
2506 fc = ctx[f]
2507 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2507 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2508 fm.data(abspath=f)
2508 fm.data(abspath=f)
2509 fm.write('path', fmt, m.rel(f))
2509 fm.write('path', fmt, m.rel(f))
2510 ret = 0
2510 ret = 0
2511
2511
2512 for subpath in sorted(ctx.substate):
2512 for subpath in sorted(ctx.substate):
2513 submatch = matchmod.subdirmatcher(subpath, m)
2513 submatch = matchmod.subdirmatcher(subpath, m)
2514 if (subrepos or m.exact(subpath) or any(submatch.files())):
2514 if (subrepos or m.exact(subpath) or any(submatch.files())):
2515 sub = ctx.sub(subpath)
2515 sub = ctx.sub(subpath)
2516 try:
2516 try:
2517 recurse = m.exact(subpath) or subrepos
2517 recurse = m.exact(subpath) or subrepos
2518 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2518 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2519 ret = 0
2519 ret = 0
2520 except error.LookupError:
2520 except error.LookupError:
2521 ui.status(_("skipping missing subrepository: %s\n")
2521 ui.status(_("skipping missing subrepository: %s\n")
2522 % m.abs(subpath))
2522 % m.abs(subpath))
2523
2523
2524 return ret
2524 return ret
2525
2525
2526 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2526 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2527 join = lambda f: os.path.join(prefix, f)
2527 join = lambda f: os.path.join(prefix, f)
2528 ret = 0
2528 ret = 0
2529 s = repo.status(match=m, clean=True)
2529 s = repo.status(match=m, clean=True)
2530 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2530 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2531
2531
2532 wctx = repo[None]
2532 wctx = repo[None]
2533
2533
2534 if warnings is None:
2534 if warnings is None:
2535 warnings = []
2535 warnings = []
2536 warn = True
2536 warn = True
2537 else:
2537 else:
2538 warn = False
2538 warn = False
2539
2539
2540 subs = sorted(wctx.substate)
2540 subs = sorted(wctx.substate)
2541 total = len(subs)
2541 total = len(subs)
2542 count = 0
2542 count = 0
2543 for subpath in subs:
2543 for subpath in subs:
2544 count += 1
2544 count += 1
2545 submatch = matchmod.subdirmatcher(subpath, m)
2545 submatch = matchmod.subdirmatcher(subpath, m)
2546 if subrepos or m.exact(subpath) or any(submatch.files()):
2546 if subrepos or m.exact(subpath) or any(submatch.files()):
2547 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2547 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2548 sub = wctx.sub(subpath)
2548 sub = wctx.sub(subpath)
2549 try:
2549 try:
2550 if sub.removefiles(submatch, prefix, after, force, subrepos,
2550 if sub.removefiles(submatch, prefix, after, force, subrepos,
2551 warnings):
2551 warnings):
2552 ret = 1
2552 ret = 1
2553 except error.LookupError:
2553 except error.LookupError:
2554 warnings.append(_("skipping missing subrepository: %s\n")
2554 warnings.append(_("skipping missing subrepository: %s\n")
2555 % join(subpath))
2555 % join(subpath))
2556 ui.progress(_('searching'), None)
2556 ui.progress(_('searching'), None)
2557
2557
2558 # warn about failure to delete explicit files/dirs
2558 # warn about failure to delete explicit files/dirs
2559 deleteddirs = util.dirs(deleted)
2559 deleteddirs = util.dirs(deleted)
2560 files = m.files()
2560 files = m.files()
2561 total = len(files)
2561 total = len(files)
2562 count = 0
2562 count = 0
2563 for f in files:
2563 for f in files:
2564 def insubrepo():
2564 def insubrepo():
2565 for subpath in wctx.substate:
2565 for subpath in wctx.substate:
2566 if f.startswith(subpath + '/'):
2566 if f.startswith(subpath + '/'):
2567 return True
2567 return True
2568 return False
2568 return False
2569
2569
2570 count += 1
2570 count += 1
2571 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2571 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2572 isdir = f in deleteddirs or wctx.hasdir(f)
2572 isdir = f in deleteddirs or wctx.hasdir(f)
2573 if (f in repo.dirstate or isdir or f == '.'
2573 if (f in repo.dirstate or isdir or f == '.'
2574 or insubrepo() or f in subs):
2574 or insubrepo() or f in subs):
2575 continue
2575 continue
2576
2576
2577 if repo.wvfs.exists(f):
2577 if repo.wvfs.exists(f):
2578 if repo.wvfs.isdir(f):
2578 if repo.wvfs.isdir(f):
2579 warnings.append(_('not removing %s: no tracked files\n')
2579 warnings.append(_('not removing %s: no tracked files\n')
2580 % m.rel(f))
2580 % m.rel(f))
2581 else:
2581 else:
2582 warnings.append(_('not removing %s: file is untracked\n')
2582 warnings.append(_('not removing %s: file is untracked\n')
2583 % m.rel(f))
2583 % m.rel(f))
2584 # missing files will generate a warning elsewhere
2584 # missing files will generate a warning elsewhere
2585 ret = 1
2585 ret = 1
2586 ui.progress(_('deleting'), None)
2586 ui.progress(_('deleting'), None)
2587
2587
2588 if force:
2588 if force:
2589 list = modified + deleted + clean + added
2589 list = modified + deleted + clean + added
2590 elif after:
2590 elif after:
2591 list = deleted
2591 list = deleted
2592 remaining = modified + added + clean
2592 remaining = modified + added + clean
2593 total = len(remaining)
2593 total = len(remaining)
2594 count = 0
2594 count = 0
2595 for f in remaining:
2595 for f in remaining:
2596 count += 1
2596 count += 1
2597 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2597 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2598 warnings.append(_('not removing %s: file still exists\n')
2598 warnings.append(_('not removing %s: file still exists\n')
2599 % m.rel(f))
2599 % m.rel(f))
2600 ret = 1
2600 ret = 1
2601 ui.progress(_('skipping'), None)
2601 ui.progress(_('skipping'), None)
2602 else:
2602 else:
2603 list = deleted + clean
2603 list = deleted + clean
2604 total = len(modified) + len(added)
2604 total = len(modified) + len(added)
2605 count = 0
2605 count = 0
2606 for f in modified:
2606 for f in modified:
2607 count += 1
2607 count += 1
2608 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2608 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2609 warnings.append(_('not removing %s: file is modified (use -f'
2609 warnings.append(_('not removing %s: file is modified (use -f'
2610 ' to force removal)\n') % m.rel(f))
2610 ' to force removal)\n') % m.rel(f))
2611 ret = 1
2611 ret = 1
2612 for f in added:
2612 for f in added:
2613 count += 1
2613 count += 1
2614 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2614 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2615 warnings.append(_("not removing %s: file has been marked for add"
2615 warnings.append(_("not removing %s: file has been marked for add"
2616 " (use 'hg forget' to undo add)\n") % m.rel(f))
2616 " (use 'hg forget' to undo add)\n") % m.rel(f))
2617 ret = 1
2617 ret = 1
2618 ui.progress(_('skipping'), None)
2618 ui.progress(_('skipping'), None)
2619
2619
2620 list = sorted(list)
2620 list = sorted(list)
2621 total = len(list)
2621 total = len(list)
2622 count = 0
2622 count = 0
2623 for f in list:
2623 for f in list:
2624 count += 1
2624 count += 1
2625 if ui.verbose or not m.exact(f):
2625 if ui.verbose or not m.exact(f):
2626 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2626 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2627 ui.status(_('removing %s\n') % m.rel(f))
2627 ui.status(_('removing %s\n') % m.rel(f))
2628 ui.progress(_('deleting'), None)
2628 ui.progress(_('deleting'), None)
2629
2629
2630 with repo.wlock():
2630 with repo.wlock():
2631 if not after:
2631 if not after:
2632 for f in list:
2632 for f in list:
2633 if f in added:
2633 if f in added:
2634 continue # we never unlink added files on remove
2634 continue # we never unlink added files on remove
2635 repo.wvfs.unlinkpath(f, ignoremissing=True)
2635 repo.wvfs.unlinkpath(f, ignoremissing=True)
2636 repo[None].forget(list)
2636 repo[None].forget(list)
2637
2637
2638 if warn:
2638 if warn:
2639 for warning in warnings:
2639 for warning in warnings:
2640 ui.warn(warning)
2640 ui.warn(warning)
2641
2641
2642 return ret
2642 return ret
2643
2643
2644 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2644 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2645 err = 1
2645 err = 1
2646
2646
2647 def write(path):
2647 def write(path):
2648 filename = None
2648 filename = None
2649 if fntemplate:
2649 if fntemplate:
2650 filename = makefilename(repo, fntemplate, ctx.node(),
2650 filename = makefilename(repo, fntemplate, ctx.node(),
2651 pathname=os.path.join(prefix, path))
2651 pathname=os.path.join(prefix, path))
2652 with formatter.maybereopen(basefm, filename, opts) as fm:
2652 with formatter.maybereopen(basefm, filename, opts) as fm:
2653 data = ctx[path].data()
2653 data = ctx[path].data()
2654 if opts.get('decode'):
2654 if opts.get('decode'):
2655 data = repo.wwritedata(path, data)
2655 data = repo.wwritedata(path, data)
2656 fm.startitem()
2656 fm.startitem()
2657 fm.write('data', '%s', data)
2657 fm.write('data', '%s', data)
2658 fm.data(abspath=path, path=matcher.rel(path))
2658 fm.data(abspath=path, path=matcher.rel(path))
2659
2659
2660 # Automation often uses hg cat on single files, so special case it
2660 # Automation often uses hg cat on single files, so special case it
2661 # for performance to avoid the cost of parsing the manifest.
2661 # for performance to avoid the cost of parsing the manifest.
2662 if len(matcher.files()) == 1 and not matcher.anypats():
2662 if len(matcher.files()) == 1 and not matcher.anypats():
2663 file = matcher.files()[0]
2663 file = matcher.files()[0]
2664 mfl = repo.manifestlog
2664 mfl = repo.manifestlog
2665 mfnode = ctx.manifestnode()
2665 mfnode = ctx.manifestnode()
2666 try:
2666 try:
2667 if mfnode and mfl[mfnode].find(file)[0]:
2667 if mfnode and mfl[mfnode].find(file)[0]:
2668 write(file)
2668 write(file)
2669 return 0
2669 return 0
2670 except KeyError:
2670 except KeyError:
2671 pass
2671 pass
2672
2672
2673 for abs in ctx.walk(matcher):
2673 for abs in ctx.walk(matcher):
2674 write(abs)
2674 write(abs)
2675 err = 0
2675 err = 0
2676
2676
2677 for subpath in sorted(ctx.substate):
2677 for subpath in sorted(ctx.substate):
2678 sub = ctx.sub(subpath)
2678 sub = ctx.sub(subpath)
2679 try:
2679 try:
2680 submatch = matchmod.subdirmatcher(subpath, matcher)
2680 submatch = matchmod.subdirmatcher(subpath, matcher)
2681
2681
2682 if not sub.cat(submatch, basefm, fntemplate,
2682 if not sub.cat(submatch, basefm, fntemplate,
2683 os.path.join(prefix, sub._path), **opts):
2683 os.path.join(prefix, sub._path), **opts):
2684 err = 0
2684 err = 0
2685 except error.RepoLookupError:
2685 except error.RepoLookupError:
2686 ui.status(_("skipping missing subrepository: %s\n")
2686 ui.status(_("skipping missing subrepository: %s\n")
2687 % os.path.join(prefix, subpath))
2687 % os.path.join(prefix, subpath))
2688
2688
2689 return err
2689 return err
2690
2690
2691 def commit(ui, repo, commitfunc, pats, opts):
2691 def commit(ui, repo, commitfunc, pats, opts):
2692 '''commit the specified files or all outstanding changes'''
2692 '''commit the specified files or all outstanding changes'''
2693 date = opts.get('date')
2693 date = opts.get('date')
2694 if date:
2694 if date:
2695 opts['date'] = util.parsedate(date)
2695 opts['date'] = util.parsedate(date)
2696 message = logmessage(ui, opts)
2696 message = logmessage(ui, opts)
2697 matcher = scmutil.match(repo[None], pats, opts)
2697 matcher = scmutil.match(repo[None], pats, opts)
2698
2698
2699 # extract addremove carefully -- this function can be called from a command
2699 # extract addremove carefully -- this function can be called from a command
2700 # that doesn't support addremove
2700 # that doesn't support addremove
2701 if opts.get('addremove'):
2701 if opts.get('addremove'):
2702 if scmutil.addremove(repo, matcher, "", opts) != 0:
2702 if scmutil.addremove(repo, matcher, "", opts) != 0:
2703 raise error.Abort(
2703 raise error.Abort(
2704 _("failed to mark all new/missing files as added/removed"))
2704 _("failed to mark all new/missing files as added/removed"))
2705
2705
2706 return commitfunc(ui, repo, message, matcher, opts)
2706 return commitfunc(ui, repo, message, matcher, opts)
2707
2707
2708 def samefile(f, ctx1, ctx2):
2708 def samefile(f, ctx1, ctx2):
2709 if f in ctx1.manifest():
2709 if f in ctx1.manifest():
2710 a = ctx1.filectx(f)
2710 a = ctx1.filectx(f)
2711 if f in ctx2.manifest():
2711 if f in ctx2.manifest():
2712 b = ctx2.filectx(f)
2712 b = ctx2.filectx(f)
2713 return (not a.cmp(b)
2713 return (not a.cmp(b)
2714 and a.flags() == b.flags())
2714 and a.flags() == b.flags())
2715 else:
2715 else:
2716 return False
2716 return False
2717 else:
2717 else:
2718 return f not in ctx2.manifest()
2718 return f not in ctx2.manifest()
2719
2719
2720 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2720 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2721 # avoid cycle context -> subrepo -> cmdutil
2721 # avoid cycle context -> subrepo -> cmdutil
2722 from . import context
2722 from . import context
2723
2723
2724 # amend will reuse the existing user if not specified, but the obsolete
2724 # amend will reuse the existing user if not specified, but the obsolete
2725 # marker creation requires that the current user's name is specified.
2725 # marker creation requires that the current user's name is specified.
2726 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2726 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2727 ui.username() # raise exception if username not set
2727 ui.username() # raise exception if username not set
2728
2728
2729 ui.note(_('amending changeset %s\n') % old)
2729 ui.note(_('amending changeset %s\n') % old)
2730 base = old.p1()
2730 base = old.p1()
2731 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2731 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2732
2732
2733 wlock = lock = newid = None
2733 wlock = lock = newid = None
2734 try:
2734 try:
2735 wlock = repo.wlock()
2735 wlock = repo.wlock()
2736 lock = repo.lock()
2736 lock = repo.lock()
2737 with repo.transaction('amend') as tr:
2737 with repo.transaction('amend') as tr:
2738 # See if we got a message from -m or -l, if not, open the editor
2738 # See if we got a message from -m or -l, if not, open the editor
2739 # with the message of the changeset to amend
2739 # with the message of the changeset to amend
2740 message = logmessage(ui, opts)
2740 message = logmessage(ui, opts)
2741 # ensure logfile does not conflict with later enforcement of the
2741 # ensure logfile does not conflict with later enforcement of the
2742 # message. potential logfile content has been processed by
2742 # message. potential logfile content has been processed by
2743 # `logmessage` anyway.
2743 # `logmessage` anyway.
2744 opts.pop('logfile')
2744 opts.pop('logfile')
2745 # First, do a regular commit to record all changes in the working
2745 # First, do a regular commit to record all changes in the working
2746 # directory (if there are any)
2746 # directory (if there are any)
2747 ui.callhooks = False
2747 ui.callhooks = False
2748 activebookmark = repo._bookmarks.active
2748 activebookmark = repo._bookmarks.active
2749 try:
2749 try:
2750 repo._bookmarks.active = None
2750 repo._bookmarks.active = None
2751 opts['message'] = 'temporary amend commit for %s' % old
2751 opts['message'] = 'temporary amend commit for %s' % old
2752 node = commit(ui, repo, commitfunc, pats, opts)
2752 node = commit(ui, repo, commitfunc, pats, opts)
2753 finally:
2753 finally:
2754 repo._bookmarks.active = activebookmark
2754 repo._bookmarks.active = activebookmark
2755 repo._bookmarks.recordchange(tr)
2755 repo._bookmarks.recordchange(tr)
2756 ui.callhooks = True
2756 ui.callhooks = True
2757 ctx = repo[node]
2757 ctx = repo[node]
2758
2758
2759 # Participating changesets:
2759 # Participating changesets:
2760 #
2760 #
2761 # node/ctx o - new (intermediate) commit that contains changes
2761 # node/ctx o - new (intermediate) commit that contains changes
2762 # | from working dir to go into amending commit
2762 # | from working dir to go into amending commit
2763 # | (or a workingctx if there were no changes)
2763 # | (or a workingctx if there were no changes)
2764 # |
2764 # |
2765 # old o - changeset to amend
2765 # old o - changeset to amend
2766 # |
2766 # |
2767 # base o - parent of amending changeset
2767 # base o - parent of amending changeset
2768
2768
2769 # Update extra dict from amended commit (e.g. to preserve graft
2769 # Update extra dict from amended commit (e.g. to preserve graft
2770 # source)
2770 # source)
2771 extra.update(old.extra())
2771 extra.update(old.extra())
2772
2772
2773 # Also update it from the intermediate commit or from the wctx
2773 # Also update it from the intermediate commit or from the wctx
2774 extra.update(ctx.extra())
2774 extra.update(ctx.extra())
2775
2775
2776 if len(old.parents()) > 1:
2776 if len(old.parents()) > 1:
2777 # ctx.files() isn't reliable for merges, so fall back to the
2777 # ctx.files() isn't reliable for merges, so fall back to the
2778 # slower repo.status() method
2778 # slower repo.status() method
2779 files = set([fn for st in repo.status(base, old)[:3]
2779 files = set([fn for st in repo.status(base, old)[:3]
2780 for fn in st])
2780 for fn in st])
2781 else:
2781 else:
2782 files = set(old.files())
2782 files = set(old.files())
2783
2783
2784 # Second, we use either the commit we just did, or if there were no
2784 # Second, we use either the commit we just did, or if there were no
2785 # changes the parent of the working directory as the version of the
2785 # changes the parent of the working directory as the version of the
2786 # files in the final amend commit
2786 # files in the final amend commit
2787 if node:
2787 if node:
2788 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2788 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2789
2789
2790 user = ctx.user()
2790 user = ctx.user()
2791 date = ctx.date()
2791 date = ctx.date()
2792 # Recompute copies (avoid recording a -> b -> a)
2792 # Recompute copies (avoid recording a -> b -> a)
2793 copied = copies.pathcopies(base, ctx)
2793 copied = copies.pathcopies(base, ctx)
2794 if old.p2:
2794 if old.p2:
2795 copied.update(copies.pathcopies(old.p2(), ctx))
2795 copied.update(copies.pathcopies(old.p2(), ctx))
2796
2796
2797 # Prune files which were reverted by the updates: if old
2797 # Prune files which were reverted by the updates: if old
2798 # introduced file X and our intermediate commit, node,
2798 # introduced file X and our intermediate commit, node,
2799 # renamed that file, then those two files are the same and
2799 # renamed that file, then those two files are the same and
2800 # we can discard X from our list of files. Likewise if X
2800 # we can discard X from our list of files. Likewise if X
2801 # was deleted, it's no longer relevant
2801 # was deleted, it's no longer relevant
2802 files.update(ctx.files())
2802 files.update(ctx.files())
2803 files = [f for f in files if not samefile(f, ctx, base)]
2803 files = [f for f in files if not samefile(f, ctx, base)]
2804
2804
2805 def filectxfn(repo, ctx_, path):
2805 def filectxfn(repo, ctx_, path):
2806 try:
2806 try:
2807 fctx = ctx[path]
2807 fctx = ctx[path]
2808 flags = fctx.flags()
2808 flags = fctx.flags()
2809 mctx = context.memfilectx(repo,
2809 mctx = context.memfilectx(repo,
2810 fctx.path(), fctx.data(),
2810 fctx.path(), fctx.data(),
2811 islink='l' in flags,
2811 islink='l' in flags,
2812 isexec='x' in flags,
2812 isexec='x' in flags,
2813 copied=copied.get(path))
2813 copied=copied.get(path))
2814 return mctx
2814 return mctx
2815 except KeyError:
2815 except KeyError:
2816 return None
2816 return None
2817 else:
2817 else:
2818 ui.note(_('copying changeset %s to %s\n') % (old, base))
2818 ui.note(_('copying changeset %s to %s\n') % (old, base))
2819
2819
2820 # Use version of files as in the old cset
2820 # Use version of files as in the old cset
2821 def filectxfn(repo, ctx_, path):
2821 def filectxfn(repo, ctx_, path):
2822 try:
2822 try:
2823 return old.filectx(path)
2823 return old.filectx(path)
2824 except KeyError:
2824 except KeyError:
2825 return None
2825 return None
2826
2826
2827 user = opts.get('user') or old.user()
2827 user = opts.get('user') or old.user()
2828 date = opts.get('date') or old.date()
2828 date = opts.get('date') or old.date()
2829 editform = mergeeditform(old, 'commit.amend')
2829 editform = mergeeditform(old, 'commit.amend')
2830 editor = getcommiteditor(editform=editform, **opts)
2830 editor = getcommiteditor(editform=editform, **opts)
2831 if not message:
2831 if not message:
2832 editor = getcommiteditor(edit=True, editform=editform)
2832 editor = getcommiteditor(edit=True, editform=editform)
2833 message = old.description()
2833 message = old.description()
2834
2834
2835 pureextra = extra.copy()
2835 pureextra = extra.copy()
2836 extra['amend_source'] = old.hex()
2836 extra['amend_source'] = old.hex()
2837
2837
2838 new = context.memctx(repo,
2838 new = context.memctx(repo,
2839 parents=[base.node(), old.p2().node()],
2839 parents=[base.node(), old.p2().node()],
2840 text=message,
2840 text=message,
2841 files=files,
2841 files=files,
2842 filectxfn=filectxfn,
2842 filectxfn=filectxfn,
2843 user=user,
2843 user=user,
2844 date=date,
2844 date=date,
2845 extra=extra,
2845 extra=extra,
2846 editor=editor)
2846 editor=editor)
2847
2847
2848 newdesc = changelog.stripdesc(new.description())
2848 newdesc = changelog.stripdesc(new.description())
2849 if ((not node)
2849 if ((not node)
2850 and newdesc == old.description()
2850 and newdesc == old.description()
2851 and user == old.user()
2851 and user == old.user()
2852 and date == old.date()
2852 and date == old.date()
2853 and pureextra == old.extra()):
2853 and pureextra == old.extra()):
2854 # nothing changed. continuing here would create a new node
2854 # nothing changed. continuing here would create a new node
2855 # anyway because of the amend_source noise.
2855 # anyway because of the amend_source noise.
2856 #
2856 #
2857 # This not what we expect from amend.
2857 # This not what we expect from amend.
2858 return old.node()
2858 return old.node()
2859
2859
2860 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2860 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2861 try:
2861 try:
2862 if opts.get('secret'):
2862 if opts.get('secret'):
2863 commitphase = 'secret'
2863 commitphase = 'secret'
2864 else:
2864 else:
2865 commitphase = old.phase()
2865 commitphase = old.phase()
2866 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2866 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2867 newid = repo.commitctx(new)
2867 newid = repo.commitctx(new)
2868 finally:
2868 finally:
2869 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2869 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2870 if newid != old.node():
2870 if newid != old.node():
2871 # Reroute the working copy parent to the new changeset
2871 # Reroute the working copy parent to the new changeset
2872 repo.setparents(newid, nullid)
2872 repo.setparents(newid, nullid)
2873
2873
2874 # Move bookmarks from old parent to amend commit
2874 # Move bookmarks from old parent to amend commit
2875 bms = repo.nodebookmarks(old.node())
2875 bms = repo.nodebookmarks(old.node())
2876 if bms:
2876 if bms:
2877 marks = repo._bookmarks
2877 marks = repo._bookmarks
2878 for bm in bms:
2878 for bm in bms:
2879 ui.debug('moving bookmarks %r from %s to %s\n' %
2879 ui.debug('moving bookmarks %r from %s to %s\n' %
2880 (marks, old.hex(), hex(newid)))
2880 (marks, old.hex(), hex(newid)))
2881 marks[bm] = newid
2881 marks[bm] = newid
2882 marks.recordchange(tr)
2882 marks.recordchange(tr)
2883 #commit the whole amend process
2883 #commit the whole amend process
2884 if createmarkers:
2884 if createmarkers:
2885 # mark the new changeset as successor of the rewritten one
2885 # mark the new changeset as successor of the rewritten one
2886 new = repo[newid]
2886 new = repo[newid]
2887 obs = [(old, (new,))]
2887 obs = [(old, (new,))]
2888 if node:
2888 if node:
2889 obs.append((ctx, ()))
2889 obs.append((ctx, ()))
2890
2890
2891 obsolete.createmarkers(repo, obs, operation='amend')
2891 obsolete.createmarkers(repo, obs, operation='amend')
2892 if not createmarkers and newid != old.node():
2892 if not createmarkers and newid != old.node():
2893 # Strip the intermediate commit (if there was one) and the amended
2893 # Strip the intermediate commit (if there was one) and the amended
2894 # commit
2894 # commit
2895 if node:
2895 if node:
2896 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2896 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2897 ui.note(_('stripping amended changeset %s\n') % old)
2897 ui.note(_('stripping amended changeset %s\n') % old)
2898 repair.strip(ui, repo, old.node(), topic='amend-backup')
2898 repair.strip(ui, repo, old.node(), topic='amend-backup')
2899 finally:
2899 finally:
2900 lockmod.release(lock, wlock)
2900 lockmod.release(lock, wlock)
2901 return newid
2901 return newid
2902
2902
2903 def commiteditor(repo, ctx, subs, editform=''):
2903 def commiteditor(repo, ctx, subs, editform=''):
2904 if ctx.description():
2904 if ctx.description():
2905 return ctx.description()
2905 return ctx.description()
2906 return commitforceeditor(repo, ctx, subs, editform=editform,
2906 return commitforceeditor(repo, ctx, subs, editform=editform,
2907 unchangedmessagedetection=True)
2907 unchangedmessagedetection=True)
2908
2908
2909 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2909 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2910 editform='', unchangedmessagedetection=False):
2910 editform='', unchangedmessagedetection=False):
2911 if not extramsg:
2911 if not extramsg:
2912 extramsg = _("Leave message empty to abort commit.")
2912 extramsg = _("Leave message empty to abort commit.")
2913
2913
2914 forms = [e for e in editform.split('.') if e]
2914 forms = [e for e in editform.split('.') if e]
2915 forms.insert(0, 'changeset')
2915 forms.insert(0, 'changeset')
2916 templatetext = None
2916 templatetext = None
2917 while forms:
2917 while forms:
2918 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2918 ref = '.'.join(forms)
2919 if tmpl:
2919 if repo.ui.config('committemplate', ref):
2920 tmpl = templater.unquotestring(tmpl)
2921 templatetext = committext = buildcommittemplate(
2920 templatetext = committext = buildcommittemplate(
2922 repo, ctx, subs, extramsg, tmpl)
2921 repo, ctx, subs, extramsg, ref)
2923 break
2922 break
2924 forms.pop()
2923 forms.pop()
2925 else:
2924 else:
2926 committext = buildcommittext(repo, ctx, subs, extramsg)
2925 committext = buildcommittext(repo, ctx, subs, extramsg)
2927
2926
2928 # run editor in the repository root
2927 # run editor in the repository root
2929 olddir = pycompat.getcwd()
2928 olddir = pycompat.getcwd()
2930 os.chdir(repo.root)
2929 os.chdir(repo.root)
2931
2930
2932 # make in-memory changes visible to external process
2931 # make in-memory changes visible to external process
2933 tr = repo.currenttransaction()
2932 tr = repo.currenttransaction()
2934 repo.dirstate.write(tr)
2933 repo.dirstate.write(tr)
2935 pending = tr and tr.writepending() and repo.root
2934 pending = tr and tr.writepending() and repo.root
2936
2935
2937 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2936 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2938 editform=editform, pending=pending,
2937 editform=editform, pending=pending,
2939 repopath=repo.path)
2938 repopath=repo.path)
2940 text = editortext
2939 text = editortext
2941
2940
2942 # strip away anything below this special string (used for editors that want
2941 # strip away anything below this special string (used for editors that want
2943 # to display the diff)
2942 # to display the diff)
2944 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2943 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2945 if stripbelow:
2944 if stripbelow:
2946 text = text[:stripbelow.start()]
2945 text = text[:stripbelow.start()]
2947
2946
2948 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2947 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2949 os.chdir(olddir)
2948 os.chdir(olddir)
2950
2949
2951 if finishdesc:
2950 if finishdesc:
2952 text = finishdesc(text)
2951 text = finishdesc(text)
2953 if not text.strip():
2952 if not text.strip():
2954 raise error.Abort(_("empty commit message"))
2953 raise error.Abort(_("empty commit message"))
2955 if unchangedmessagedetection and editortext == templatetext:
2954 if unchangedmessagedetection and editortext == templatetext:
2956 raise error.Abort(_("commit message unchanged"))
2955 raise error.Abort(_("commit message unchanged"))
2957
2956
2958 return text
2957 return text
2959
2958
2960 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2959 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2961 ui = repo.ui
2960 ui = repo.ui
2962 spec = logtemplatespec(tmpl, None)
2961 spec = formatter.templatespec(ref, None, None)
2963 t = changeset_templater(ui, repo, spec, None, {}, False)
2962 t = changeset_templater(ui, repo, spec, None, {}, False)
2964
2963 t.t.cache.update((k, templater.unquotestring(v))
2965 for k, v in repo.ui.configitems('committemplate'):
2964 for k, v in repo.ui.configitems('committemplate'))
2966 if k != 'changeset':
2967 t.t.cache[k] = v
2968
2965
2969 if not extramsg:
2966 if not extramsg:
2970 extramsg = '' # ensure that extramsg is string
2967 extramsg = '' # ensure that extramsg is string
2971
2968
2972 ui.pushbuffer()
2969 ui.pushbuffer()
2973 t.show(ctx, extramsg=extramsg)
2970 t.show(ctx, extramsg=extramsg)
2974 return ui.popbuffer()
2971 return ui.popbuffer()
2975
2972
2976 def hgprefix(msg):
2973 def hgprefix(msg):
2977 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2974 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2978
2975
2979 def buildcommittext(repo, ctx, subs, extramsg):
2976 def buildcommittext(repo, ctx, subs, extramsg):
2980 edittext = []
2977 edittext = []
2981 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2978 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2982 if ctx.description():
2979 if ctx.description():
2983 edittext.append(ctx.description())
2980 edittext.append(ctx.description())
2984 edittext.append("")
2981 edittext.append("")
2985 edittext.append("") # Empty line between message and comments.
2982 edittext.append("") # Empty line between message and comments.
2986 edittext.append(hgprefix(_("Enter commit message."
2983 edittext.append(hgprefix(_("Enter commit message."
2987 " Lines beginning with 'HG:' are removed.")))
2984 " Lines beginning with 'HG:' are removed.")))
2988 edittext.append(hgprefix(extramsg))
2985 edittext.append(hgprefix(extramsg))
2989 edittext.append("HG: --")
2986 edittext.append("HG: --")
2990 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2987 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2991 if ctx.p2():
2988 if ctx.p2():
2992 edittext.append(hgprefix(_("branch merge")))
2989 edittext.append(hgprefix(_("branch merge")))
2993 if ctx.branch():
2990 if ctx.branch():
2994 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2991 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2995 if bookmarks.isactivewdirparent(repo):
2992 if bookmarks.isactivewdirparent(repo):
2996 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2993 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2997 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2994 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2998 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2995 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2999 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2996 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3000 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2997 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3001 if not added and not modified and not removed:
2998 if not added and not modified and not removed:
3002 edittext.append(hgprefix(_("no files changed")))
2999 edittext.append(hgprefix(_("no files changed")))
3003 edittext.append("")
3000 edittext.append("")
3004
3001
3005 return "\n".join(edittext)
3002 return "\n".join(edittext)
3006
3003
3007 def commitstatus(repo, node, branch, bheads=None, opts=None):
3004 def commitstatus(repo, node, branch, bheads=None, opts=None):
3008 if opts is None:
3005 if opts is None:
3009 opts = {}
3006 opts = {}
3010 ctx = repo[node]
3007 ctx = repo[node]
3011 parents = ctx.parents()
3008 parents = ctx.parents()
3012
3009
3013 if (not opts.get('amend') and bheads and node not in bheads and not
3010 if (not opts.get('amend') and bheads and node not in bheads and not
3014 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3011 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3015 repo.ui.status(_('created new head\n'))
3012 repo.ui.status(_('created new head\n'))
3016 # The message is not printed for initial roots. For the other
3013 # The message is not printed for initial roots. For the other
3017 # changesets, it is printed in the following situations:
3014 # changesets, it is printed in the following situations:
3018 #
3015 #
3019 # Par column: for the 2 parents with ...
3016 # Par column: for the 2 parents with ...
3020 # N: null or no parent
3017 # N: null or no parent
3021 # B: parent is on another named branch
3018 # B: parent is on another named branch
3022 # C: parent is a regular non head changeset
3019 # C: parent is a regular non head changeset
3023 # H: parent was a branch head of the current branch
3020 # H: parent was a branch head of the current branch
3024 # Msg column: whether we print "created new head" message
3021 # Msg column: whether we print "created new head" message
3025 # In the following, it is assumed that there already exists some
3022 # In the following, it is assumed that there already exists some
3026 # initial branch heads of the current branch, otherwise nothing is
3023 # initial branch heads of the current branch, otherwise nothing is
3027 # printed anyway.
3024 # printed anyway.
3028 #
3025 #
3029 # Par Msg Comment
3026 # Par Msg Comment
3030 # N N y additional topo root
3027 # N N y additional topo root
3031 #
3028 #
3032 # B N y additional branch root
3029 # B N y additional branch root
3033 # C N y additional topo head
3030 # C N y additional topo head
3034 # H N n usual case
3031 # H N n usual case
3035 #
3032 #
3036 # B B y weird additional branch root
3033 # B B y weird additional branch root
3037 # C B y branch merge
3034 # C B y branch merge
3038 # H B n merge with named branch
3035 # H B n merge with named branch
3039 #
3036 #
3040 # C C y additional head from merge
3037 # C C y additional head from merge
3041 # C H n merge with a head
3038 # C H n merge with a head
3042 #
3039 #
3043 # H H n head merge: head count decreases
3040 # H H n head merge: head count decreases
3044
3041
3045 if not opts.get('close_branch'):
3042 if not opts.get('close_branch'):
3046 for r in parents:
3043 for r in parents:
3047 if r.closesbranch() and r.branch() == branch:
3044 if r.closesbranch() and r.branch() == branch:
3048 repo.ui.status(_('reopening closed branch head %d\n') % r)
3045 repo.ui.status(_('reopening closed branch head %d\n') % r)
3049
3046
3050 if repo.ui.debugflag:
3047 if repo.ui.debugflag:
3051 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3048 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3052 elif repo.ui.verbose:
3049 elif repo.ui.verbose:
3053 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3050 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3054
3051
3055 def postcommitstatus(repo, pats, opts):
3052 def postcommitstatus(repo, pats, opts):
3056 return repo.status(match=scmutil.match(repo[None], pats, opts))
3053 return repo.status(match=scmutil.match(repo[None], pats, opts))
3057
3054
3058 def revert(ui, repo, ctx, parents, *pats, **opts):
3055 def revert(ui, repo, ctx, parents, *pats, **opts):
3059 parent, p2 = parents
3056 parent, p2 = parents
3060 node = ctx.node()
3057 node = ctx.node()
3061
3058
3062 mf = ctx.manifest()
3059 mf = ctx.manifest()
3063 if node == p2:
3060 if node == p2:
3064 parent = p2
3061 parent = p2
3065
3062
3066 # need all matching names in dirstate and manifest of target rev,
3063 # need all matching names in dirstate and manifest of target rev,
3067 # so have to walk both. do not print errors if files exist in one
3064 # so have to walk both. do not print errors if files exist in one
3068 # but not other. in both cases, filesets should be evaluated against
3065 # but not other. in both cases, filesets should be evaluated against
3069 # workingctx to get consistent result (issue4497). this means 'set:**'
3066 # workingctx to get consistent result (issue4497). this means 'set:**'
3070 # cannot be used to select missing files from target rev.
3067 # cannot be used to select missing files from target rev.
3071
3068
3072 # `names` is a mapping for all elements in working copy and target revision
3069 # `names` is a mapping for all elements in working copy and target revision
3073 # The mapping is in the form:
3070 # The mapping is in the form:
3074 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3071 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3075 names = {}
3072 names = {}
3076
3073
3077 with repo.wlock():
3074 with repo.wlock():
3078 ## filling of the `names` mapping
3075 ## filling of the `names` mapping
3079 # walk dirstate to fill `names`
3076 # walk dirstate to fill `names`
3080
3077
3081 interactive = opts.get('interactive', False)
3078 interactive = opts.get('interactive', False)
3082 wctx = repo[None]
3079 wctx = repo[None]
3083 m = scmutil.match(wctx, pats, opts)
3080 m = scmutil.match(wctx, pats, opts)
3084
3081
3085 # we'll need this later
3082 # we'll need this later
3086 targetsubs = sorted(s for s in wctx.substate if m(s))
3083 targetsubs = sorted(s for s in wctx.substate if m(s))
3087
3084
3088 if not m.always():
3085 if not m.always():
3089 matcher = matchmod.badmatch(m, lambda x, y: False)
3086 matcher = matchmod.badmatch(m, lambda x, y: False)
3090 for abs in wctx.walk(matcher):
3087 for abs in wctx.walk(matcher):
3091 names[abs] = m.rel(abs), m.exact(abs)
3088 names[abs] = m.rel(abs), m.exact(abs)
3092
3089
3093 # walk target manifest to fill `names`
3090 # walk target manifest to fill `names`
3094
3091
3095 def badfn(path, msg):
3092 def badfn(path, msg):
3096 if path in names:
3093 if path in names:
3097 return
3094 return
3098 if path in ctx.substate:
3095 if path in ctx.substate:
3099 return
3096 return
3100 path_ = path + '/'
3097 path_ = path + '/'
3101 for f in names:
3098 for f in names:
3102 if f.startswith(path_):
3099 if f.startswith(path_):
3103 return
3100 return
3104 ui.warn("%s: %s\n" % (m.rel(path), msg))
3101 ui.warn("%s: %s\n" % (m.rel(path), msg))
3105
3102
3106 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3103 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3107 if abs not in names:
3104 if abs not in names:
3108 names[abs] = m.rel(abs), m.exact(abs)
3105 names[abs] = m.rel(abs), m.exact(abs)
3109
3106
3110 # Find status of all file in `names`.
3107 # Find status of all file in `names`.
3111 m = scmutil.matchfiles(repo, names)
3108 m = scmutil.matchfiles(repo, names)
3112
3109
3113 changes = repo.status(node1=node, match=m,
3110 changes = repo.status(node1=node, match=m,
3114 unknown=True, ignored=True, clean=True)
3111 unknown=True, ignored=True, clean=True)
3115 else:
3112 else:
3116 changes = repo.status(node1=node, match=m)
3113 changes = repo.status(node1=node, match=m)
3117 for kind in changes:
3114 for kind in changes:
3118 for abs in kind:
3115 for abs in kind:
3119 names[abs] = m.rel(abs), m.exact(abs)
3116 names[abs] = m.rel(abs), m.exact(abs)
3120
3117
3121 m = scmutil.matchfiles(repo, names)
3118 m = scmutil.matchfiles(repo, names)
3122
3119
3123 modified = set(changes.modified)
3120 modified = set(changes.modified)
3124 added = set(changes.added)
3121 added = set(changes.added)
3125 removed = set(changes.removed)
3122 removed = set(changes.removed)
3126 _deleted = set(changes.deleted)
3123 _deleted = set(changes.deleted)
3127 unknown = set(changes.unknown)
3124 unknown = set(changes.unknown)
3128 unknown.update(changes.ignored)
3125 unknown.update(changes.ignored)
3129 clean = set(changes.clean)
3126 clean = set(changes.clean)
3130 modadded = set()
3127 modadded = set()
3131
3128
3132 # We need to account for the state of the file in the dirstate,
3129 # We need to account for the state of the file in the dirstate,
3133 # even when we revert against something else than parent. This will
3130 # even when we revert against something else than parent. This will
3134 # slightly alter the behavior of revert (doing back up or not, delete
3131 # slightly alter the behavior of revert (doing back up or not, delete
3135 # or just forget etc).
3132 # or just forget etc).
3136 if parent == node:
3133 if parent == node:
3137 dsmodified = modified
3134 dsmodified = modified
3138 dsadded = added
3135 dsadded = added
3139 dsremoved = removed
3136 dsremoved = removed
3140 # store all local modifications, useful later for rename detection
3137 # store all local modifications, useful later for rename detection
3141 localchanges = dsmodified | dsadded
3138 localchanges = dsmodified | dsadded
3142 modified, added, removed = set(), set(), set()
3139 modified, added, removed = set(), set(), set()
3143 else:
3140 else:
3144 changes = repo.status(node1=parent, match=m)
3141 changes = repo.status(node1=parent, match=m)
3145 dsmodified = set(changes.modified)
3142 dsmodified = set(changes.modified)
3146 dsadded = set(changes.added)
3143 dsadded = set(changes.added)
3147 dsremoved = set(changes.removed)
3144 dsremoved = set(changes.removed)
3148 # store all local modifications, useful later for rename detection
3145 # store all local modifications, useful later for rename detection
3149 localchanges = dsmodified | dsadded
3146 localchanges = dsmodified | dsadded
3150
3147
3151 # only take into account for removes between wc and target
3148 # only take into account for removes between wc and target
3152 clean |= dsremoved - removed
3149 clean |= dsremoved - removed
3153 dsremoved &= removed
3150 dsremoved &= removed
3154 # distinct between dirstate remove and other
3151 # distinct between dirstate remove and other
3155 removed -= dsremoved
3152 removed -= dsremoved
3156
3153
3157 modadded = added & dsmodified
3154 modadded = added & dsmodified
3158 added -= modadded
3155 added -= modadded
3159
3156
3160 # tell newly modified apart.
3157 # tell newly modified apart.
3161 dsmodified &= modified
3158 dsmodified &= modified
3162 dsmodified |= modified & dsadded # dirstate added may need backup
3159 dsmodified |= modified & dsadded # dirstate added may need backup
3163 modified -= dsmodified
3160 modified -= dsmodified
3164
3161
3165 # We need to wait for some post-processing to update this set
3162 # We need to wait for some post-processing to update this set
3166 # before making the distinction. The dirstate will be used for
3163 # before making the distinction. The dirstate will be used for
3167 # that purpose.
3164 # that purpose.
3168 dsadded = added
3165 dsadded = added
3169
3166
3170 # in case of merge, files that are actually added can be reported as
3167 # in case of merge, files that are actually added can be reported as
3171 # modified, we need to post process the result
3168 # modified, we need to post process the result
3172 if p2 != nullid:
3169 if p2 != nullid:
3173 mergeadd = set(dsmodified)
3170 mergeadd = set(dsmodified)
3174 for path in dsmodified:
3171 for path in dsmodified:
3175 if path in mf:
3172 if path in mf:
3176 mergeadd.remove(path)
3173 mergeadd.remove(path)
3177 dsadded |= mergeadd
3174 dsadded |= mergeadd
3178 dsmodified -= mergeadd
3175 dsmodified -= mergeadd
3179
3176
3180 # if f is a rename, update `names` to also revert the source
3177 # if f is a rename, update `names` to also revert the source
3181 cwd = repo.getcwd()
3178 cwd = repo.getcwd()
3182 for f in localchanges:
3179 for f in localchanges:
3183 src = repo.dirstate.copied(f)
3180 src = repo.dirstate.copied(f)
3184 # XXX should we check for rename down to target node?
3181 # XXX should we check for rename down to target node?
3185 if src and src not in names and repo.dirstate[src] == 'r':
3182 if src and src not in names and repo.dirstate[src] == 'r':
3186 dsremoved.add(src)
3183 dsremoved.add(src)
3187 names[src] = (repo.pathto(src, cwd), True)
3184 names[src] = (repo.pathto(src, cwd), True)
3188
3185
3189 # determine the exact nature of the deleted changesets
3186 # determine the exact nature of the deleted changesets
3190 deladded = set(_deleted)
3187 deladded = set(_deleted)
3191 for path in _deleted:
3188 for path in _deleted:
3192 if path in mf:
3189 if path in mf:
3193 deladded.remove(path)
3190 deladded.remove(path)
3194 deleted = _deleted - deladded
3191 deleted = _deleted - deladded
3195
3192
3196 # distinguish between file to forget and the other
3193 # distinguish between file to forget and the other
3197 added = set()
3194 added = set()
3198 for abs in dsadded:
3195 for abs in dsadded:
3199 if repo.dirstate[abs] != 'a':
3196 if repo.dirstate[abs] != 'a':
3200 added.add(abs)
3197 added.add(abs)
3201 dsadded -= added
3198 dsadded -= added
3202
3199
3203 for abs in deladded:
3200 for abs in deladded:
3204 if repo.dirstate[abs] == 'a':
3201 if repo.dirstate[abs] == 'a':
3205 dsadded.add(abs)
3202 dsadded.add(abs)
3206 deladded -= dsadded
3203 deladded -= dsadded
3207
3204
3208 # For files marked as removed, we check if an unknown file is present at
3205 # For files marked as removed, we check if an unknown file is present at
3209 # the same path. If a such file exists it may need to be backed up.
3206 # the same path. If a such file exists it may need to be backed up.
3210 # Making the distinction at this stage helps have simpler backup
3207 # Making the distinction at this stage helps have simpler backup
3211 # logic.
3208 # logic.
3212 removunk = set()
3209 removunk = set()
3213 for abs in removed:
3210 for abs in removed:
3214 target = repo.wjoin(abs)
3211 target = repo.wjoin(abs)
3215 if os.path.lexists(target):
3212 if os.path.lexists(target):
3216 removunk.add(abs)
3213 removunk.add(abs)
3217 removed -= removunk
3214 removed -= removunk
3218
3215
3219 dsremovunk = set()
3216 dsremovunk = set()
3220 for abs in dsremoved:
3217 for abs in dsremoved:
3221 target = repo.wjoin(abs)
3218 target = repo.wjoin(abs)
3222 if os.path.lexists(target):
3219 if os.path.lexists(target):
3223 dsremovunk.add(abs)
3220 dsremovunk.add(abs)
3224 dsremoved -= dsremovunk
3221 dsremoved -= dsremovunk
3225
3222
3226 # action to be actually performed by revert
3223 # action to be actually performed by revert
3227 # (<list of file>, message>) tuple
3224 # (<list of file>, message>) tuple
3228 actions = {'revert': ([], _('reverting %s\n')),
3225 actions = {'revert': ([], _('reverting %s\n')),
3229 'add': ([], _('adding %s\n')),
3226 'add': ([], _('adding %s\n')),
3230 'remove': ([], _('removing %s\n')),
3227 'remove': ([], _('removing %s\n')),
3231 'drop': ([], _('removing %s\n')),
3228 'drop': ([], _('removing %s\n')),
3232 'forget': ([], _('forgetting %s\n')),
3229 'forget': ([], _('forgetting %s\n')),
3233 'undelete': ([], _('undeleting %s\n')),
3230 'undelete': ([], _('undeleting %s\n')),
3234 'noop': (None, _('no changes needed to %s\n')),
3231 'noop': (None, _('no changes needed to %s\n')),
3235 'unknown': (None, _('file not managed: %s\n')),
3232 'unknown': (None, _('file not managed: %s\n')),
3236 }
3233 }
3237
3234
3238 # "constant" that convey the backup strategy.
3235 # "constant" that convey the backup strategy.
3239 # All set to `discard` if `no-backup` is set do avoid checking
3236 # All set to `discard` if `no-backup` is set do avoid checking
3240 # no_backup lower in the code.
3237 # no_backup lower in the code.
3241 # These values are ordered for comparison purposes
3238 # These values are ordered for comparison purposes
3242 backupinteractive = 3 # do backup if interactively modified
3239 backupinteractive = 3 # do backup if interactively modified
3243 backup = 2 # unconditionally do backup
3240 backup = 2 # unconditionally do backup
3244 check = 1 # check if the existing file differs from target
3241 check = 1 # check if the existing file differs from target
3245 discard = 0 # never do backup
3242 discard = 0 # never do backup
3246 if opts.get('no_backup'):
3243 if opts.get('no_backup'):
3247 backupinteractive = backup = check = discard
3244 backupinteractive = backup = check = discard
3248 if interactive:
3245 if interactive:
3249 dsmodifiedbackup = backupinteractive
3246 dsmodifiedbackup = backupinteractive
3250 else:
3247 else:
3251 dsmodifiedbackup = backup
3248 dsmodifiedbackup = backup
3252 tobackup = set()
3249 tobackup = set()
3253
3250
3254 backupanddel = actions['remove']
3251 backupanddel = actions['remove']
3255 if not opts.get('no_backup'):
3252 if not opts.get('no_backup'):
3256 backupanddel = actions['drop']
3253 backupanddel = actions['drop']
3257
3254
3258 disptable = (
3255 disptable = (
3259 # dispatch table:
3256 # dispatch table:
3260 # file state
3257 # file state
3261 # action
3258 # action
3262 # make backup
3259 # make backup
3263
3260
3264 ## Sets that results that will change file on disk
3261 ## Sets that results that will change file on disk
3265 # Modified compared to target, no local change
3262 # Modified compared to target, no local change
3266 (modified, actions['revert'], discard),
3263 (modified, actions['revert'], discard),
3267 # Modified compared to target, but local file is deleted
3264 # Modified compared to target, but local file is deleted
3268 (deleted, actions['revert'], discard),
3265 (deleted, actions['revert'], discard),
3269 # Modified compared to target, local change
3266 # Modified compared to target, local change
3270 (dsmodified, actions['revert'], dsmodifiedbackup),
3267 (dsmodified, actions['revert'], dsmodifiedbackup),
3271 # Added since target
3268 # Added since target
3272 (added, actions['remove'], discard),
3269 (added, actions['remove'], discard),
3273 # Added in working directory
3270 # Added in working directory
3274 (dsadded, actions['forget'], discard),
3271 (dsadded, actions['forget'], discard),
3275 # Added since target, have local modification
3272 # Added since target, have local modification
3276 (modadded, backupanddel, backup),
3273 (modadded, backupanddel, backup),
3277 # Added since target but file is missing in working directory
3274 # Added since target but file is missing in working directory
3278 (deladded, actions['drop'], discard),
3275 (deladded, actions['drop'], discard),
3279 # Removed since target, before working copy parent
3276 # Removed since target, before working copy parent
3280 (removed, actions['add'], discard),
3277 (removed, actions['add'], discard),
3281 # Same as `removed` but an unknown file exists at the same path
3278 # Same as `removed` but an unknown file exists at the same path
3282 (removunk, actions['add'], check),
3279 (removunk, actions['add'], check),
3283 # Removed since targe, marked as such in working copy parent
3280 # Removed since targe, marked as such in working copy parent
3284 (dsremoved, actions['undelete'], discard),
3281 (dsremoved, actions['undelete'], discard),
3285 # Same as `dsremoved` but an unknown file exists at the same path
3282 # Same as `dsremoved` but an unknown file exists at the same path
3286 (dsremovunk, actions['undelete'], check),
3283 (dsremovunk, actions['undelete'], check),
3287 ## the following sets does not result in any file changes
3284 ## the following sets does not result in any file changes
3288 # File with no modification
3285 # File with no modification
3289 (clean, actions['noop'], discard),
3286 (clean, actions['noop'], discard),
3290 # Existing file, not tracked anywhere
3287 # Existing file, not tracked anywhere
3291 (unknown, actions['unknown'], discard),
3288 (unknown, actions['unknown'], discard),
3292 )
3289 )
3293
3290
3294 for abs, (rel, exact) in sorted(names.items()):
3291 for abs, (rel, exact) in sorted(names.items()):
3295 # target file to be touch on disk (relative to cwd)
3292 # target file to be touch on disk (relative to cwd)
3296 target = repo.wjoin(abs)
3293 target = repo.wjoin(abs)
3297 # search the entry in the dispatch table.
3294 # search the entry in the dispatch table.
3298 # if the file is in any of these sets, it was touched in the working
3295 # if the file is in any of these sets, it was touched in the working
3299 # directory parent and we are sure it needs to be reverted.
3296 # directory parent and we are sure it needs to be reverted.
3300 for table, (xlist, msg), dobackup in disptable:
3297 for table, (xlist, msg), dobackup in disptable:
3301 if abs not in table:
3298 if abs not in table:
3302 continue
3299 continue
3303 if xlist is not None:
3300 if xlist is not None:
3304 xlist.append(abs)
3301 xlist.append(abs)
3305 if dobackup:
3302 if dobackup:
3306 # If in interactive mode, don't automatically create
3303 # If in interactive mode, don't automatically create
3307 # .orig files (issue4793)
3304 # .orig files (issue4793)
3308 if dobackup == backupinteractive:
3305 if dobackup == backupinteractive:
3309 tobackup.add(abs)
3306 tobackup.add(abs)
3310 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3307 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3311 bakname = scmutil.origpath(ui, repo, rel)
3308 bakname = scmutil.origpath(ui, repo, rel)
3312 ui.note(_('saving current version of %s as %s\n') %
3309 ui.note(_('saving current version of %s as %s\n') %
3313 (rel, bakname))
3310 (rel, bakname))
3314 if not opts.get('dry_run'):
3311 if not opts.get('dry_run'):
3315 if interactive:
3312 if interactive:
3316 util.copyfile(target, bakname)
3313 util.copyfile(target, bakname)
3317 else:
3314 else:
3318 util.rename(target, bakname)
3315 util.rename(target, bakname)
3319 if ui.verbose or not exact:
3316 if ui.verbose or not exact:
3320 if not isinstance(msg, basestring):
3317 if not isinstance(msg, basestring):
3321 msg = msg(abs)
3318 msg = msg(abs)
3322 ui.status(msg % rel)
3319 ui.status(msg % rel)
3323 elif exact:
3320 elif exact:
3324 ui.warn(msg % rel)
3321 ui.warn(msg % rel)
3325 break
3322 break
3326
3323
3327 if not opts.get('dry_run'):
3324 if not opts.get('dry_run'):
3328 needdata = ('revert', 'add', 'undelete')
3325 needdata = ('revert', 'add', 'undelete')
3329 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3326 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3330 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3327 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3331
3328
3332 if targetsubs:
3329 if targetsubs:
3333 # Revert the subrepos on the revert list
3330 # Revert the subrepos on the revert list
3334 for sub in targetsubs:
3331 for sub in targetsubs:
3335 try:
3332 try:
3336 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3333 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3337 except KeyError:
3334 except KeyError:
3338 raise error.Abort("subrepository '%s' does not exist in %s!"
3335 raise error.Abort("subrepository '%s' does not exist in %s!"
3339 % (sub, short(ctx.node())))
3336 % (sub, short(ctx.node())))
3340
3337
3341 def _revertprefetch(repo, ctx, *files):
3338 def _revertprefetch(repo, ctx, *files):
3342 """Let extension changing the storage layer prefetch content"""
3339 """Let extension changing the storage layer prefetch content"""
3343 pass
3340 pass
3344
3341
3345 def _performrevert(repo, parents, ctx, actions, interactive=False,
3342 def _performrevert(repo, parents, ctx, actions, interactive=False,
3346 tobackup=None):
3343 tobackup=None):
3347 """function that actually perform all the actions computed for revert
3344 """function that actually perform all the actions computed for revert
3348
3345
3349 This is an independent function to let extension to plug in and react to
3346 This is an independent function to let extension to plug in and react to
3350 the imminent revert.
3347 the imminent revert.
3351
3348
3352 Make sure you have the working directory locked when calling this function.
3349 Make sure you have the working directory locked when calling this function.
3353 """
3350 """
3354 parent, p2 = parents
3351 parent, p2 = parents
3355 node = ctx.node()
3352 node = ctx.node()
3356 excluded_files = []
3353 excluded_files = []
3357 matcher_opts = {"exclude": excluded_files}
3354 matcher_opts = {"exclude": excluded_files}
3358
3355
3359 def checkout(f):
3356 def checkout(f):
3360 fc = ctx[f]
3357 fc = ctx[f]
3361 repo.wwrite(f, fc.data(), fc.flags())
3358 repo.wwrite(f, fc.data(), fc.flags())
3362
3359
3363 def doremove(f):
3360 def doremove(f):
3364 try:
3361 try:
3365 repo.wvfs.unlinkpath(f)
3362 repo.wvfs.unlinkpath(f)
3366 except OSError:
3363 except OSError:
3367 pass
3364 pass
3368 repo.dirstate.remove(f)
3365 repo.dirstate.remove(f)
3369
3366
3370 audit_path = pathutil.pathauditor(repo.root)
3367 audit_path = pathutil.pathauditor(repo.root)
3371 for f in actions['forget'][0]:
3368 for f in actions['forget'][0]:
3372 if interactive:
3369 if interactive:
3373 choice = repo.ui.promptchoice(
3370 choice = repo.ui.promptchoice(
3374 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3371 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3375 if choice == 0:
3372 if choice == 0:
3376 repo.dirstate.drop(f)
3373 repo.dirstate.drop(f)
3377 else:
3374 else:
3378 excluded_files.append(repo.wjoin(f))
3375 excluded_files.append(repo.wjoin(f))
3379 else:
3376 else:
3380 repo.dirstate.drop(f)
3377 repo.dirstate.drop(f)
3381 for f in actions['remove'][0]:
3378 for f in actions['remove'][0]:
3382 audit_path(f)
3379 audit_path(f)
3383 if interactive:
3380 if interactive:
3384 choice = repo.ui.promptchoice(
3381 choice = repo.ui.promptchoice(
3385 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3382 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3386 if choice == 0:
3383 if choice == 0:
3387 doremove(f)
3384 doremove(f)
3388 else:
3385 else:
3389 excluded_files.append(repo.wjoin(f))
3386 excluded_files.append(repo.wjoin(f))
3390 else:
3387 else:
3391 doremove(f)
3388 doremove(f)
3392 for f in actions['drop'][0]:
3389 for f in actions['drop'][0]:
3393 audit_path(f)
3390 audit_path(f)
3394 repo.dirstate.remove(f)
3391 repo.dirstate.remove(f)
3395
3392
3396 normal = None
3393 normal = None
3397 if node == parent:
3394 if node == parent:
3398 # We're reverting to our parent. If possible, we'd like status
3395 # We're reverting to our parent. If possible, we'd like status
3399 # to report the file as clean. We have to use normallookup for
3396 # to report the file as clean. We have to use normallookup for
3400 # merges to avoid losing information about merged/dirty files.
3397 # merges to avoid losing information about merged/dirty files.
3401 if p2 != nullid:
3398 if p2 != nullid:
3402 normal = repo.dirstate.normallookup
3399 normal = repo.dirstate.normallookup
3403 else:
3400 else:
3404 normal = repo.dirstate.normal
3401 normal = repo.dirstate.normal
3405
3402
3406 newlyaddedandmodifiedfiles = set()
3403 newlyaddedandmodifiedfiles = set()
3407 if interactive:
3404 if interactive:
3408 # Prompt the user for changes to revert
3405 # Prompt the user for changes to revert
3409 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3406 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3410 m = scmutil.match(ctx, torevert, matcher_opts)
3407 m = scmutil.match(ctx, torevert, matcher_opts)
3411 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3408 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3412 diffopts.nodates = True
3409 diffopts.nodates = True
3413 diffopts.git = True
3410 diffopts.git = True
3414 operation = 'discard'
3411 operation = 'discard'
3415 reversehunks = True
3412 reversehunks = True
3416 if node != parent:
3413 if node != parent:
3417 operation = 'revert'
3414 operation = 'revert'
3418 reversehunks = repo.ui.configbool('experimental',
3415 reversehunks = repo.ui.configbool('experimental',
3419 'revertalternateinteractivemode',
3416 'revertalternateinteractivemode',
3420 True)
3417 True)
3421 if reversehunks:
3418 if reversehunks:
3422 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3419 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3423 else:
3420 else:
3424 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3421 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3425 originalchunks = patch.parsepatch(diff)
3422 originalchunks = patch.parsepatch(diff)
3426
3423
3427 try:
3424 try:
3428
3425
3429 chunks, opts = recordfilter(repo.ui, originalchunks,
3426 chunks, opts = recordfilter(repo.ui, originalchunks,
3430 operation=operation)
3427 operation=operation)
3431 if reversehunks:
3428 if reversehunks:
3432 chunks = patch.reversehunks(chunks)
3429 chunks = patch.reversehunks(chunks)
3433
3430
3434 except patch.PatchError as err:
3431 except patch.PatchError as err:
3435 raise error.Abort(_('error parsing patch: %s') % err)
3432 raise error.Abort(_('error parsing patch: %s') % err)
3436
3433
3437 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3434 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3438 if tobackup is None:
3435 if tobackup is None:
3439 tobackup = set()
3436 tobackup = set()
3440 # Apply changes
3437 # Apply changes
3441 fp = stringio()
3438 fp = stringio()
3442 for c in chunks:
3439 for c in chunks:
3443 # Create a backup file only if this hunk should be backed up
3440 # Create a backup file only if this hunk should be backed up
3444 if ishunk(c) and c.header.filename() in tobackup:
3441 if ishunk(c) and c.header.filename() in tobackup:
3445 abs = c.header.filename()
3442 abs = c.header.filename()
3446 target = repo.wjoin(abs)
3443 target = repo.wjoin(abs)
3447 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3444 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3448 util.copyfile(target, bakname)
3445 util.copyfile(target, bakname)
3449 tobackup.remove(abs)
3446 tobackup.remove(abs)
3450 c.write(fp)
3447 c.write(fp)
3451 dopatch = fp.tell()
3448 dopatch = fp.tell()
3452 fp.seek(0)
3449 fp.seek(0)
3453 if dopatch:
3450 if dopatch:
3454 try:
3451 try:
3455 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3452 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3456 except patch.PatchError as err:
3453 except patch.PatchError as err:
3457 raise error.Abort(str(err))
3454 raise error.Abort(str(err))
3458 del fp
3455 del fp
3459 else:
3456 else:
3460 for f in actions['revert'][0]:
3457 for f in actions['revert'][0]:
3461 checkout(f)
3458 checkout(f)
3462 if normal:
3459 if normal:
3463 normal(f)
3460 normal(f)
3464
3461
3465 for f in actions['add'][0]:
3462 for f in actions['add'][0]:
3466 # Don't checkout modified files, they are already created by the diff
3463 # Don't checkout modified files, they are already created by the diff
3467 if f not in newlyaddedandmodifiedfiles:
3464 if f not in newlyaddedandmodifiedfiles:
3468 checkout(f)
3465 checkout(f)
3469 repo.dirstate.add(f)
3466 repo.dirstate.add(f)
3470
3467
3471 normal = repo.dirstate.normallookup
3468 normal = repo.dirstate.normallookup
3472 if node == parent and p2 == nullid:
3469 if node == parent and p2 == nullid:
3473 normal = repo.dirstate.normal
3470 normal = repo.dirstate.normal
3474 for f in actions['undelete'][0]:
3471 for f in actions['undelete'][0]:
3475 checkout(f)
3472 checkout(f)
3476 normal(f)
3473 normal(f)
3477
3474
3478 copied = copies.pathcopies(repo[parent], ctx)
3475 copied = copies.pathcopies(repo[parent], ctx)
3479
3476
3480 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3477 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3481 if f in copied:
3478 if f in copied:
3482 repo.dirstate.copy(copied[f], f)
3479 repo.dirstate.copy(copied[f], f)
3483
3480
3484 class command(registrar.command):
3481 class command(registrar.command):
3485 def _doregister(self, func, name, *args, **kwargs):
3482 def _doregister(self, func, name, *args, **kwargs):
3486 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3483 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3487 return super(command, self)._doregister(func, name, *args, **kwargs)
3484 return super(command, self)._doregister(func, name, *args, **kwargs)
3488
3485
3489 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3486 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3490 # commands.outgoing. "missing" is "missing" of the result of
3487 # commands.outgoing. "missing" is "missing" of the result of
3491 # "findcommonoutgoing()"
3488 # "findcommonoutgoing()"
3492 outgoinghooks = util.hooks()
3489 outgoinghooks = util.hooks()
3493
3490
3494 # a list of (ui, repo) functions called by commands.summary
3491 # a list of (ui, repo) functions called by commands.summary
3495 summaryhooks = util.hooks()
3492 summaryhooks = util.hooks()
3496
3493
3497 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3494 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3498 #
3495 #
3499 # functions should return tuple of booleans below, if 'changes' is None:
3496 # functions should return tuple of booleans below, if 'changes' is None:
3500 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3497 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3501 #
3498 #
3502 # otherwise, 'changes' is a tuple of tuples below:
3499 # otherwise, 'changes' is a tuple of tuples below:
3503 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3500 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3504 # - (desturl, destbranch, destpeer, outgoing)
3501 # - (desturl, destbranch, destpeer, outgoing)
3505 summaryremotehooks = util.hooks()
3502 summaryremotehooks = util.hooks()
3506
3503
3507 # A list of state files kept by multistep operations like graft.
3504 # A list of state files kept by multistep operations like graft.
3508 # Since graft cannot be aborted, it is considered 'clearable' by update.
3505 # Since graft cannot be aborted, it is considered 'clearable' by update.
3509 # note: bisect is intentionally excluded
3506 # note: bisect is intentionally excluded
3510 # (state file, clearable, allowcommit, error, hint)
3507 # (state file, clearable, allowcommit, error, hint)
3511 unfinishedstates = [
3508 unfinishedstates = [
3512 ('graftstate', True, False, _('graft in progress'),
3509 ('graftstate', True, False, _('graft in progress'),
3513 _("use 'hg graft --continue' or 'hg update' to abort")),
3510 _("use 'hg graft --continue' or 'hg update' to abort")),
3514 ('updatestate', True, False, _('last update was interrupted'),
3511 ('updatestate', True, False, _('last update was interrupted'),
3515 _("use 'hg update' to get a consistent checkout"))
3512 _("use 'hg update' to get a consistent checkout"))
3516 ]
3513 ]
3517
3514
3518 def checkunfinished(repo, commit=False):
3515 def checkunfinished(repo, commit=False):
3519 '''Look for an unfinished multistep operation, like graft, and abort
3516 '''Look for an unfinished multistep operation, like graft, and abort
3520 if found. It's probably good to check this right before
3517 if found. It's probably good to check this right before
3521 bailifchanged().
3518 bailifchanged().
3522 '''
3519 '''
3523 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3520 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3524 if commit and allowcommit:
3521 if commit and allowcommit:
3525 continue
3522 continue
3526 if repo.vfs.exists(f):
3523 if repo.vfs.exists(f):
3527 raise error.Abort(msg, hint=hint)
3524 raise error.Abort(msg, hint=hint)
3528
3525
3529 def clearunfinished(repo):
3526 def clearunfinished(repo):
3530 '''Check for unfinished operations (as above), and clear the ones
3527 '''Check for unfinished operations (as above), and clear the ones
3531 that are clearable.
3528 that are clearable.
3532 '''
3529 '''
3533 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3530 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3534 if not clearable and repo.vfs.exists(f):
3531 if not clearable and repo.vfs.exists(f):
3535 raise error.Abort(msg, hint=hint)
3532 raise error.Abort(msg, hint=hint)
3536 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3533 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3537 if clearable and repo.vfs.exists(f):
3534 if clearable and repo.vfs.exists(f):
3538 util.unlink(repo.vfs.join(f))
3535 util.unlink(repo.vfs.join(f))
3539
3536
3540 afterresolvedstates = [
3537 afterresolvedstates = [
3541 ('graftstate',
3538 ('graftstate',
3542 _('hg graft --continue')),
3539 _('hg graft --continue')),
3543 ]
3540 ]
3544
3541
3545 def howtocontinue(repo):
3542 def howtocontinue(repo):
3546 '''Check for an unfinished operation and return the command to finish
3543 '''Check for an unfinished operation and return the command to finish
3547 it.
3544 it.
3548
3545
3549 afterresolvedstates tuples define a .hg/{file} and the corresponding
3546 afterresolvedstates tuples define a .hg/{file} and the corresponding
3550 command needed to finish it.
3547 command needed to finish it.
3551
3548
3552 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3549 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3553 a boolean.
3550 a boolean.
3554 '''
3551 '''
3555 contmsg = _("continue: %s")
3552 contmsg = _("continue: %s")
3556 for f, msg in afterresolvedstates:
3553 for f, msg in afterresolvedstates:
3557 if repo.vfs.exists(f):
3554 if repo.vfs.exists(f):
3558 return contmsg % msg, True
3555 return contmsg % msg, True
3559 workingctx = repo[None]
3556 workingctx = repo[None]
3560 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3557 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3561 for s in workingctx.substate)
3558 for s in workingctx.substate)
3562 if dirty:
3559 if dirty:
3563 return contmsg % _("hg commit"), False
3560 return contmsg % _("hg commit"), False
3564 return None, None
3561 return None, None
3565
3562
3566 def checkafterresolved(repo):
3563 def checkafterresolved(repo):
3567 '''Inform the user about the next action after completing hg resolve
3564 '''Inform the user about the next action after completing hg resolve
3568
3565
3569 If there's a matching afterresolvedstates, howtocontinue will yield
3566 If there's a matching afterresolvedstates, howtocontinue will yield
3570 repo.ui.warn as the reporter.
3567 repo.ui.warn as the reporter.
3571
3568
3572 Otherwise, it will yield repo.ui.note.
3569 Otherwise, it will yield repo.ui.note.
3573 '''
3570 '''
3574 msg, warning = howtocontinue(repo)
3571 msg, warning = howtocontinue(repo)
3575 if msg is not None:
3572 if msg is not None:
3576 if warning:
3573 if warning:
3577 repo.ui.warn("%s\n" % msg)
3574 repo.ui.warn("%s\n" % msg)
3578 else:
3575 else:
3579 repo.ui.note("%s\n" % msg)
3576 repo.ui.note("%s\n" % msg)
3580
3577
3581 def wrongtooltocontinue(repo, task):
3578 def wrongtooltocontinue(repo, task):
3582 '''Raise an abort suggesting how to properly continue if there is an
3579 '''Raise an abort suggesting how to properly continue if there is an
3583 active task.
3580 active task.
3584
3581
3585 Uses howtocontinue() to find the active task.
3582 Uses howtocontinue() to find the active task.
3586
3583
3587 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3584 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3588 a hint.
3585 a hint.
3589 '''
3586 '''
3590 after = howtocontinue(repo)
3587 after = howtocontinue(repo)
3591 hint = None
3588 hint = None
3592 if after[1]:
3589 if after[1]:
3593 hint = after[0]
3590 hint = after[0]
3594 raise error.Abort(_('no %s in progress') % task, hint=hint)
3591 raise error.Abort(_('no %s in progress') % task, hint=hint)
General Comments 0
You need to be logged in to leave comments. Login now