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