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