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