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