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