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