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