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