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