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