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