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